Das hat dann doch den letzten Ruck gegeben und ich habe die Versuche nun eingestellt, das Ding wieder in seine Verpackung gesteckt und nun geht es zurück an Amazon… leider, weil optisch immer noch die hübscheste Lösung 
Unten noch das Protokoll, fängt leider nicht ganz zu Beginn an, sondern erst, nachdem ich die Uhr erfolgreich verbunden und den “Notify” Befehl getestet habe.
Natürlich alles mit Unterstützung von Claude (will mich nicht mit KI-Federn schmücken)
Protokoll
Glance Clock – Zeitpunkte Dashboard: Chatprotokoll & aktueller Stand
Stand: 9. August 2026
Übersicht / Konzept
Dashboard zur Verwaltung von 8 Zeitpunkten/Zeiträumen (Schlafenszeit, Freizeit, Abfahrzeit, 5 frei benennbare), jeweils mit:
Aktiv/Inaktiv-Schalter
Typ: Zeitpunkt (einzelner Alarm) oder Zeitraum (Von–Bis)
Farbe (7 Optionen)
Icon (14 Optionen)
Bei Erreichen: Nachricht an die Glance Clock via glance_clock.send_notice
Wichtige technische Erkenntnis (offener Punkt): Die Glance-Clock-Integration kann laut Code-Analyse keine dauerhafte farbige Ringanzeige über einen Zeitraum anzeigen. glance_clock.send_notice ist ein kurzer Blitz-Hinweis (Text+Farbe+Animation, aber nur für Sekunden). glance_clock.send_timer zeigt einen Countdown (Zahlen runterzählend), aber ohne Farbparameter und der Text wird nur kurz als Ankündigung eingeblendet, nicht dauerhaft.
Offene Entscheidung: Wie soll bei “Zeitraum”-Typ verfahren werden? Optionen waren: (a) nur kurzer Blitz bei Start+Ende, (b) zusätzlich alle X Minuten während der Dauer erneut blitzen, (c) send_timer als Countdown nutzen (ohne Wunschfarbe), (d) Idee verwerfen, nur Start/Ende reicht. → Noch nicht final entschieden, aktuelle Automation sendet bei “Zeitraum” einen Blitz bei Start UND bei Ende (Variante ähnlich zu a).
Ebenfalls offen: Sound-Schalter (input_boolean.glance_clock_sound_aktiv) wurde vorgeschlagen, aber noch nicht final in Dashboard/Automation eingebaut bestätigt – Code unten ist vorbereitet, aber prüfen ob schon übernommen.
- Helpers – /config/packages/zeitpunkte.yaml
yaml
input_datetime:
zeitpunkt_schlafenszeit:
name: “Schlafenszeit (Von)”
has_time: true
has_date: false
icon: mdi:bed
zeitpunkt_schlafenszeit_bis:
name: “Schlafenszeit (Bis)”
has_time: true
has_date: false
icon: mdi:bed
zeitpunkt_freizeit:
name: “Freizeit (Von)”
has_time: true
has_date: false
icon: mdi:gamepad-variant
zeitpunkt_freizeit_bis:
name: “Freizeit (Bis)”
has_time: true
has_date: false
zeitpunkt_abfahrzeit:
name: “Abfahrzeit (Von)”
has_time: true
has_date: false
icon: mdi:car
zeitpunkt_abfahrzeit_bis:
name: “Abfahrzeit (Bis)”
has_time: true
has_date: false
zeitpunkt_frei_1:
name: “Frei 1 (Von)”
has_time: true
has_date: false
zeitpunkt_frei_1_bis:
name: “Frei 1 (Bis)”
has_time: true
has_date: false
zeitpunkt_frei_2:
name: “Frei 2 (Von)”
has_time: true
has_date: false
zeitpunkt_frei_2_bis:
name: “Frei 2 (Bis)”
has_time: true
has_date: false
zeitpunkt_frei_3:
name: “Frei 3 (Von)”
has_time: true
has_date: false
zeitpunkt_frei_3_bis:
name: “Frei 3 (Bis)”
has_time: true
has_date: false
zeitpunkt_frei_4:
name: “Frei 4 (Von)”
has_time: true
has_date: false
zeitpunkt_frei_4_bis:
name: “Frei 4 (Bis)”
has_time: true
has_date: false
zeitpunkt_frei_5:
name: “Frei 5 (Von)”
has_time: true
has_date: false
zeitpunkt_frei_5_bis:
name: “Frei 5 (Bis)”
has_time: true
has_date: false
input_text:
zeitpunkt_frei_1_name:
name: “Name Frei 1”
initial: “Zeitpunkt 1”
max: 30
zeitpunkt_frei_2_name:
name: “Name Frei 2”
initial: “Zeitpunkt 2”
max: 30
zeitpunkt_frei_3_name:
name: “Name Frei 3”
initial: “Zeitpunkt 3”
max: 30
zeitpunkt_frei_4_name:
name: “Name Frei 4”
initial: “Zeitpunkt 4”
max: 30
zeitpunkt_frei_5_name:
name: “Name Frei 5”
initial: “Zeitpunkt 5”
max: 30
input_boolean:
zeitpunkt_schlafenszeit_aktiv:
name: “Schlafenszeit aktiv”
icon: mdi:toggle-switch
zeitpunkt_freizeit_aktiv:
name: “Freizeit aktiv”
icon: mdi:toggle-switch
zeitpunkt_abfahrzeit_aktiv:
name: “Abfahrzeit aktiv”
icon: mdi:toggle-switch
zeitpunkt_frei_1_aktiv:
name: “Frei 1 aktiv”
icon: mdi:toggle-switch
zeitpunkt_frei_2_aktiv:
name: “Frei 2 aktiv”
icon: mdi:toggle-switch
zeitpunkt_frei_3_aktiv:
name: “Frei 3 aktiv”
icon: mdi:toggle-switch
zeitpunkt_frei_4_aktiv:
name: “Frei 4 aktiv”
icon: mdi:toggle-switch
zeitpunkt_frei_5_aktiv:
name: “Frei 5 aktiv”
icon: mdi:toggle-switch
Vorgeschlagen, Status “noch einzubauen” prüfen:
glance_clock_sound_aktiv:
name: “Glance Clock Sound aktiv”
icon: mdi:volume-high
input_select:
zeitpunkt_schlafenszeit_typ: &typ_select
name: “Typ”
options: [“Zeitpunkt”, “Zeitraum”]
initial: “Zeitpunkt”
zeitpunkt_freizeit_typ: *typ_select
zeitpunkt_abfahrzeit_typ: *typ_select
zeitpunkt_frei_1_typ: *typ_select
zeitpunkt_frei_2_typ: *typ_select
zeitpunkt_frei_3_typ: *typ_select
zeitpunkt_frei_4_typ: *typ_select
zeitpunkt_frei_5_typ: *typ_select
zeitpunkt_schlafenszeit_farbe: &farbe_select
name: “Farbe”
options: [“Weiß”, “Rot”, “Blau”, “Grün”, “Orange”, “Lila”, “Gelb”]
initial: “Blau”
zeitpunkt_freizeit_farbe: *farbe_select
zeitpunkt_abfahrzeit_farbe: *farbe_select
zeitpunkt_frei_1_farbe: *farbe_select
zeitpunkt_frei_2_farbe: *farbe_select
zeitpunkt_frei_3_farbe: *farbe_select
zeitpunkt_frei_4_farbe: *farbe_select
zeitpunkt_frei_5_farbe: *farbe_select
zeitpunkt_schlafenszeit_icon: &icon_select
name: “Icon”
options: [“Bett”, “Controller”, “Auto”, “Wecker”, “Mond”, “Sonne”, “Schule”, “Essen”, “Sport”, “Fernseher”, “Buch”, “Dusche”, “Zähne”, “Stern”]
initial: “Bett”
zeitpunkt_freizeit_icon: *icon_select
zeitpunkt_abfahrzeit_icon: *icon_select
zeitpunkt_frei_1_icon: *icon_select
zeitpunkt_frei_2_icon: *icon_select
zeitpunkt_frei_3_icon: *icon_select
zeitpunkt_frei_4_icon: *icon_select
zeitpunkt_frei_5_icon: *icon_select
Aktivierung in configuration.yaml (bereits vorhanden bei dir):
yaml
homeassistant:
packages: !include_dir_named packages
2. Dashboard-Template – button_card_templates: (ganz oben in der Dashboard-Raw-YAML, vor views:)
Aktueller Stand inkl. korrigiertem state_display (Zeitzonen-Fix) und korrigiertem browser_mod-Popup (JS-Objekt-Fix):
yaml
button_card_templates:
zeitpunkt_kachel:
show_name: true
show_state: true
show_icon: true
size: 40%
styles:
card:
- width: 85px
- height: 85px
- border-radius: 16px
- padding: 6px
- opacity: |
[[[
const aktiv = states[‘input_boolean.’ + variables.entity_prefix + ‘_aktiv’]?.state;
return aktiv === ‘on’ ? ‘1’ : ‘0.35’;
]]]
icon:
- color: |
[[[
const farben = {
“Weiß”: “#FFFFFF”, “Rot”: “#FF3B30”, “Blau”: “#2196F3”,
“Grün”: “#8BC34A”, “Orange”: “#FF8C00”, “Lila”: “#8A2BE2”, “Gelb”: “#CDDC39”
};
const farbe = states[‘input_select.’ + variables.entity_prefix + ‘_farbe’]?.state;
return farben[farbe] || ‘#FFFFFF’;
]]]
name:
- font-size: 11px
- font-weight: 500
state:
- font-size: 13px
- font-weight: 600
icon: |
[[[
const icons = {
“Bett”: “mdi:bed”, “Controller”: “mdi:gamepad-variant”, “Auto”: “mdi:car”,
“Wecker”: “mdi:alarm”, “Mond”: “mdi:weather-night”, “Sonne”: “mdi:white-balance-sunny”,
“Schule”: “mdi:school”, “Essen”: “mdi:food”, “Sport”: “mdi:dumbbell”,
“Fernseher”: “mdi:television”, “Buch”: “mdi:book-open-variant”, “Dusche”: “mdi:shower”,
“Zähne”: “mdi:toothbrush”, “Stern”: “mdi:star”
};
const icon = states[‘input_select.’ + variables.entity_prefix + ‘_icon’]?.state;
return icons[icon] || ‘mdi:clock-outline’;
]]]
name: |
[[[
if (variables.fixed_name) return variables.fixed_name;
return states[‘input_text.’ + variables.entity_prefix + ‘_name’]?.state || ‘’;
]]]
state_display: |
[[[
const prefix = variables.entity_prefix;
const typ = states[‘input_select.’ + prefix + ‘_typ’]?.state;
const fmt = (ent) => ent ? ent.state.substring(0,5) : ‘–:–’;
const von = fmt(states[‘input_datetime.’ + prefix]);
if (typ === ‘Zeitraum’) {
const bis = fmt(states[‘input_datetime.’ + prefix + ‘_bis’]);
return von + ‘–’ + bis;
}
return von;
]]]
tap_action:
action: fire-dom-event
browser_mod: |
[[[
const prefix = variables.entity_prefix;
const title = variables.fixed_name || (states[‘input_text.’ + prefix + ‘_name’]?.state || ‘’);
return {
service: ‘browser_mod.popup’,
data: {
title: title,
content: {
type: ‘entities’,
entities: [
{ entity: ‘input_boolean.’ + prefix + ‘_aktiv’, name: ‘Aktiv’ },
{ entity: ‘input_select.’ + prefix + ‘_typ’, name: ‘Typ’ },
{ entity: ‘input_datetime.’ + prefix, name: ‘Von’ },
{ entity: ‘input_datetime.’ + prefix + ‘_bis’, name: ‘Bis (nur bei Zeitraum)’ },
{ entity: ‘input_select.’ + prefix + ‘_farbe’, name: ‘Farbe’ },
{ entity: ‘input_select.’ + prefix + ‘_icon’, name: ‘Icon’ }
]
}
}
};
]]]
Vorgeschlagene Verbesserung (Zeit-Picker-Card statt Textfelder) – noch nicht final bestätigt ob installiert/übernommen:
Benötigt HACS-Installation: time-picker-card von GeorgeSG (Frontend-Kategorie in HACS).
yaml
content:
type: entities
entities:
- entity: input_boolean.[[[ return variables.entity_prefix ]]]_aktiv
name: “Aktiv”
- entity: input_select.[[[ return variables.entity_prefix ]]]_typ
name: “Typ”
- type: custom:time-picker-card
entity: input_datetime.[[[ return variables.entity_prefix ]]]
layout: {name: inside}
- type: custom:time-picker-card
entity: input_datetime.[[[ return variables.entity_prefix ]]]_bis
layout: {name: inside}
- entity: input_select.[[[ return variables.entity_prefix ]]]_farbe
name: “Farbe”
- entity: input_select.[[[ return variables.entity_prefix ]]]_icon
name: “Icon”
(Hinweis: Diese Variante nutzt noch die alte, fehlerhafte Template-Syntax für Entity-IDs innerhalb der entities-Liste, aus einer früheren Nachricht – müsste wie oben beim browser_mod-Fix auf die JS-Objekt-Methode umgestellt werden, falls sie noch verwendet wird. Bitte vor Einsatz prüfen/anpassen.)
- Automation 1 – “Zeitpunkte - Trigger Event”
Aktueller, funktionierender Stand (mit korrigiertem event:-Schlüssel statt action: event.fire):
yaml
alias: Zeitpunkte - Trigger Event
triggers:
- trigger: time
at: input_datetime.zeitpunkt_schlafenszeit
id: zeitpunkt_schlafenszeit
- trigger: time
at: input_datetime.zeitpunkt_schlafenszeit_bis
id: zeitpunkt_schlafenszeit_ende
- trigger: time
at: input_datetime.zeitpunkt_freizeit
id: zeitpunkt_freizeit
- trigger: time
at: input_datetime.zeitpunkt_freizeit_bis
id: zeitpunkt_freizeit_ende
- trigger: time
at: input_datetime.zeitpunkt_abfahrzeit
id: zeitpunkt_abfahrzeit
- trigger: time
at: input_datetime.zeitpunkt_abfahrzeit_bis
id: zeitpunkt_abfahrzeit_ende
- trigger: time
at: input_datetime.zeitpunkt_frei_1
id: zeitpunkt_frei_1
- trigger: time
at: input_datetime.zeitpunkt_frei_1_bis
id: zeitpunkt_frei_1_ende
- trigger: time
at: input_datetime.zeitpunkt_frei_2
id: zeitpunkt_frei_2
- trigger: time
at: input_datetime.zeitpunkt_frei_2_bis
id: zeitpunkt_frei_2_ende
- trigger: time
at: input_datetime.zeitpunkt_frei_3
id: zeitpunkt_frei_3
- trigger: time
at: input_datetime.zeitpunkt_frei_3_bis
id: zeitpunkt_frei_3_ende
- trigger: time
at: input_datetime.zeitpunkt_frei_4
id: zeitpunkt_frei_4
- trigger: time
at: input_datetime.zeitpunkt_frei_4_bis
id: zeitpunkt_frei_4_ende
- trigger: time
at: input_datetime.zeitpunkt_frei_5
id: zeitpunkt_frei_5
- trigger: time
at: input_datetime.zeitpunkt_frei_5_bis
id: zeitpunkt_frei_5_ende
actions:
- variables:
is_end: ‘{{ trigger.id.endswith(’‘_ende’‘) }}’
base: ‘{{ trigger.id[:-5] if trigger.id.endswith(’‘_ende’‘) else trigger.id }}’
- condition: template
value_template: |
{{ is_state(‘input_boolean.’ ~ base ~ ‘_aktiv’, ‘on’)
and (not is_end or states(‘input_select.’ ~ base ~ ‘_typ’) == ‘Zeitraum’) }}
- variables:
zp_name: >
{% set feste_namen =
{‘zeitpunkt_schlafenszeit’:‘Schlafenszeit’,‘zeitpunkt_freizeit’:‘Freizeit’,‘zeitpunkt_abfahrzeit’:‘Abfahrzeit’}
%} {{ feste_namen.get(base, states(‘input_text.’ ~ base ~ ‘_name’)) }}
zp_farbe: ‘{{ states(’‘input_select.’’ ~ base ~ ‘’_farbe’‘) }}’
zp_icon: ‘{{ states(’‘input_select.’’ ~ base ~ ‘’_icon’‘) }}’
- event: zeitpunkt_ausgeloest
event_data:
id: ‘{{ base }}’
phase: ‘{{ ‘‘ende’’ if is_end else ‘‘start’’ }}’
name: ‘{{ zp_name }}’
farbe: ‘{{ zp_farbe }}’
icon: ‘{{ zp_icon }}’
mode: parallel
Bestätigt funktionierend (Trace erfolgreich durchgelaufen laut Chatverlauf).
- Automation 2 – “Zeitpunkt → Glance Clock”
Mit optionalem Sound-Schalter (Status: vorgeschlagen, Übernahme noch bestätigen):
yaml
alias: “Zeitpunkt → Glance Clock”
triggers:
- trigger: event
event_type: zeitpunkt_ausgeloest
actions:
- action: glance_clock.send_notice
data:
text: >-
{{ trigger.event.data.name }}{{ ’ – Ende’ if trigger.event.data.phase == ‘ende’ else ‘’ }}
animation: pulse
sound: >-
{{ ‘bells’ if is_state(‘input_boolean.glance_clock_sound_aktiv’, ‘on’) else ‘none’ }}
color: >-
{{ {‘Weiß’:‘white’,‘Rot’:‘red’,‘Blau’:‘blue’,‘Grün’:‘lime’,
‘Orange’:‘dark_orange’,‘Lila’:‘blue_violet’,‘Gelb’:‘lawn_green’}
[trigger.event.data.farbe] }}
priority: medium
mode: parallel
Falls Sound-Schalter noch NICHT eingebaut werden soll, einfach sound: none fest statt der Template-Zeile.
- Dashboard – View “Glance Clock” (8 Kacheln im Grid)
yaml
- type: masonry
path: glance-clock
title: Glance Clock
icon: far:clock
cards:
- type: grid
columns: 4
square: false
cards:
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_schlafenszeit, fixed_name: “Schlafenszeit”}
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_freizeit, fixed_name: “Freizeit”}
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_abfahrzeit, fixed_name: “Abfahrzeit”}
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_frei_1}
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_frei_2}
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_frei_3}
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_frei_4}
- type: custom:button-card
template: zeitpunkt_kachel
variables: {entity_prefix: zeitpunkt_frei_5}
Bestätigt: Kacheln sind im Dashboard vorhanden und funktionsfähig (laut Chatverlauf, inkl. Farbe/Icon-Anzeige und Popup zum Bearbeiten).
- Erkenntnisse zu glance_clock.send_timer (Testprotokoll)
Getestet mit:
yaml
action: glance_clock.send_timer
data:
countdown: 10
intervals:
- text: “Schlafenszeit”
duration: 12
countdown: 5
final_text: “Test fertig”
Beobachtetes Verhalten auf der Uhr:
countdown (10s) läuft zuerst still ab, ohne Textmeldung
Automatischer Text “Start next” (Firmware-generiert)
Text “Schlafenszeit” erscheint kurz
Countdown-Zahlen von 5 auf 0 (countdown-Wert des Intervals)
Countdown-Zahlen von 12 auf 0 (duration-Wert des Intervals)
(vermutlich danach) final_text “Test fertig”
Schlussfolgerung: send_timer ist ein reiner Kitchen-Timer-Countdown ohne Farbparameter. Text wird nur kurz angekündigt, nicht dauerhaft angezeigt. Für “dauerhafte Farbanzeige während eines Zeitraums” (Wunsch 2) oder “Farbring an echten Ziffernblatt-Positionen” (Wunsch 3) bietet die Integration/Hardware keine passende Funktion.
Offene Entscheidung (zuletzt gestellte Frage, noch unbeantwortet): Wie soll bei Typ “Zeitraum” verfahren werden?
(a) Nur kurzer Blitz bei Start + Ende — aktuell in Automation 2 so umgesetzt
(b) Zusätzlich alle X Minuten während der Dauer erneut kurz aufblitzen
(c) send_timer als Countdown nutzen (zeigt Restzeit als Zahlen, ohne Wunschfarbe/-text dauerhaft)
(d) Idee verwerfen, nur Start/Ende reicht
7. Entscheidung zu Punkt 2/3: Custom-Scene-Feature (EXPERIMENTELL, in Entwicklung)
Entscheidung: Nutzer möchte das größere Entwicklungsprojekt verfolgen — echter farbiger Ring-Balken über die Dauer eines Zeitraums, via CustomScene-Protobuf-Nachricht (MovingBar-Methode), die in der Original-Integration nicht genutzt/exponiert wird.
Grundlage: Analyse der Readme von GitHub - Hypfer/glance-clock: Glance Clock BLE protocol reverse-engineered · GitHub (Reverse-Engineering-Doku der Hardware/Protokoll) zeigt: Die Uhr hat 4× 48-LED-RGB-Ringe und kennt “Scenes”, die (im Gegensatz zum kurzen Notice-Blitz) dauerhaft aktiv bleiben, bis gelöscht. CustomScene.Object.MovingBarData hat Felder area, frontColor, backColor, speed — genau passend für einen farbigen Ring-Abschnitt. lifeTime (Sekunden) sollte die Szene automatisch beenden.
Wichtig: CustomScene ist zwar im Protobuf-Schema (Glance.proto / glance_pb2.py) vollständig definiert, wird aber nirgendwo im originalen Python-Code der Integration verwendet (nur Notice, Timer, ForecastScene, Settings sind implementiert). Das Feature wurde daher komplett neu für diese Integration entwickelt.
7.1 Neuer Code — Status: Installiert, aber Ergebnis noch nicht wie erwartet
a) Neue Methode in notify.py (eingefügt nach async_send_timer):
python
async def async_send_time_range(self, text, color, duration_seconds, area=0, scene_slot=9) → bool:
“”“EXPERIMENTAL: Send a persistent colored ring bar for a given duration.”“”
if not self._connection_manager or not self._connection_manager.is_connected:
_LOGGER.warning(“Device not connected, cannot send time range scene”)
return False
try:
from .glance_pb2 import CustomScene, TextData # type: ignore
import re
def text_with_icons_to_bytes(t: str) -> bytes:
icon_regex = re.compile(r"\[icon:(\d+)\]")
parts = []
last_index = 0
for match in icon_regex.finditer(t):
for c in t[last_index:match.start()]:
parts.append(ord(c) & 0x7F)
parts.append(int(match.group(1)))
last_index = match.end()
for c in t[last_index:]:
parts.append(ord(c) & 0x7F)
return bytes(parts)
scene = CustomScene()
obj = scene.object.add()
obj.method = 5 # MovingBar
obj.lifeTime = int(duration_seconds)
obj.movingBar.area = int(area)
obj.movingBar.frontColor = int(color)
obj.movingBar.backColor = 0 # Black
obj.movingBar.speed = 0 # static, no movement
if text:
text_data = TextData()
text_data.text = text_with_icons_to_bytes(text)
obj.text.append(text_data)
scene_bytes = scene.SerializeToString()
# CustomScene command: 0,0,<display_mode 24=Ring&Text>,<scene_slot>
header = bytearray([0, 0, 24, scene_slot])
command = header + scene_bytes
_LOGGER.info(
f"Sending time range scene: text={text}, color={color}, "
f"duration={duration_seconds}s, area={area}, slot={scene_slot}")
_LOGGER.debug(f"CustomScene command: {command.hex()}")
success = await self._connection_manager.send_command(bytes(command))
if success:
_LOGGER.info("Time range scene sent successfully")
return True
else:
_LOGGER.error("Failed to send time range scene command")
return False
except Exception as e:
_LOGGER.error(f"Error sending time range scene: {e}")
import traceback
_LOGGER.error(f"Full traceback: {traceback.format_exc()}")
return False
b) Neue Datei services/time_range.py:
python
“”“EXPERIMENTAL: Time range scene service for Glance Clock.”“”
import logging
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.config_entries import ConfigEntry
from ..const import DOMAIN, COLORS
_LOGGER = logging.getLogger(name)
async def handle_send_time_range(hass: HomeAssistant, entry: ConfigEntry, call: ServiceCall):
“”“Handle sending a persistent time-range scene to the device.”“”
entry_data = hass.data[DOMAIN][entry.entry_id]
notify_service = hass.data.get(DOMAIN + “_notify”, {}).get(entry.entry_id)
connection_manager = entry_data.get(“connection_manager”)
if notify_service:
if connection_manager and not hasattr(notify_service, '_connection_manager'):
notify_service._connection_manager = connection_manager
text = call.data.get("text", "")
color_name = call.data.get("color", "white")
duration_seconds = call.data.get("duration_seconds", 60)
area = call.data.get("area", 0)
scene_slot = call.data.get("scene_slot", 9)
color = COLORS.get(color_name, 12)
success = await notify_service.async_send_time_range(
text=text,
color=color,
duration_seconds=duration_seconds,
area=area,
scene_slot=scene_slot
)
if success:
_LOGGER.info(f"Time range scene sent successfully: {text}")
else:
_LOGGER.error(f"Failed to send time range scene: {text}")
else:
_LOGGER.error("Notification service not found for sending time range scene")
c) Änderungen in services/init.py (bestätigt korrekt eingefügt, per cat verifiziert):
Import: from .time_range import handle_send_time_range
Wrapper: _handle_send_time_range
Registrierung: hass.services.async_register(DOMAIN, “send_time_range”, _handle_send_time_range)
d) Ergänzung services.yaml:
yaml
send_time_range:
name: “Send Time Range (Experimental)”
description: “EXPERIMENTELL: Zeigt einen dauerhaften farbigen Ring-Balken mit Text für eine Dauer. Zuordnung ‘area’ zu echten Uhrzeigerpositionen ist noch ungeklärt.”
fields:
text:
name: “Text”
selector:
text:
color:
name: “Farbe”
default: “blue”
selector:
select:
options: [“white”, “red”, “blue”, “lime”, “dark_orange”, “blue_violet”, “lawn_green”]
duration_seconds:
name: “Dauer (Sekunden)”
required: true
selector:
number:
min: 5
max: 86400
area:
name: “Ring-Segment (experimentell, Mapping unbekannt)”
default: 0
selector:
number:
min: 0
max: 100
scene_slot:
name: “Szenen-Slot”
default: 9
selector:
number:
min: 0
max: 15
7.2 Test-Protokoll & Beobachtungen (bisher problematisch)
Test Parameter Beobachtung
1 color: blue, duration: 30s, area: 0, scene_slot: 9 Blauer Balken erscheint am Ring, ca. Position “2–3 Uhr”. Farbe/Sichtbarkeit funktionieren grundsätzlich.
2–n Verschiedene area, color (rot, grün, blau), verschiedene duration_seconds Keine sichtbare Änderung — immer derselbe blaue Fleck an derselben Stelle (2–3 Uhr)
Test nach 12h — Blauer Fleck war immer noch da, sporadisch wiederkehrend — lifeTime hat NICHT wie erwartet zur automatischen Löschung geführt
Stromreset der Uhr Uhr kurz vom Strom getrennt Blauer Fleck war danach weg
BT-Interferenz gefunden Log zeigt 16:17–17:07 Uhr mehrfache Verbindungsabbrüche (“Interference/range”) Erklärt evtl., warum spätere Testaufrufe nicht ankamen — Verbindung war zeitweise tot
Test nach BT-Stabilisierung text: “Test3”, color: green, duration: 10s, area: 24, scene_slot: 7, Uhr laut UI “Verbunden” Ergebnis identisch zu Test 1 — derselbe blaue Fleck an derselben Stelle, ignoriert color: green und area: 24 komplett. Kommt “immer wieder”.
Zwischenfazit / Verdachtsmomente (noch ungeklärt):
Möglich, dass der color/area-Parameter aus nachfolgenden Aufrufen aus irgendeinem Grund nicht im tatsächlich gesendeten Byte-Kommando ankommt (z. B. Fehler in der Service-Datenübergabe, Standardwerte greifen fälschlich).
Möglich, dass scene_slot unterschiedlich ist (7 statt 9), aber die Uhr trotzdem nur eine alte, hängende Szene auf Slot 9 zyklisch mitanzeigt, weil diese nie sauber gelöscht wurde (fehlende Lösch-Funktion — es gibt noch keinen ScenesDelete/ScenesClear-Aufruf im Code!).
Möglich, dass das Byte-Encoding des Headers ([0, 0, 24, scene_slot]) oder die Protobuf-Serialisierung strukturell nicht korrekt ist und die Uhr defaultet auf eine Fallback-Anzeige.
Noch nicht verifiziert: Kommt der Log-Eintrag “Sending time range scene…” / “…sent successfully” bei Test 3 tatsächlich im Log an? (Letzter Diagnoseschritt vor dieser Aktualisierung noch nicht abgeschlossen.)
Nächste Diagnoseschritte (in Arbeit, noch nicht abgeschlossen):
Live-Log direkt nach einem frischen Testaufruf prüfen → Erledigt, siehe 7.3
Fehlende ScenesDelete/Cleanup-Funktion nachrüsten, um hängende Szenen sauber zu entfernen (aktuell einziger zuverlässiger Weg: Stromreset der Uhr)
Segment-Mapping (area → echte Ziffernblatt-Position) ist weiterhin komplett ungeklärt
7.3 Diagnose-Ergebnis (10.08., 23:17 Uhr): Code funktioniert, aber area wirkt nicht
Log-Level-Anpassung vorgenommen (falls noch nicht in configuration.yaml):
yaml
logger:
default: warning
logs:
custom_components.glance_clock: debug
Bestätigter Live-Test (text: “Diagnose2”, color: red, duration_seconds: 15, area: 40, scene_slot: 6):
2026-08-10 23:17:58.003 INFO [custom_components.glance_clock.notify] Sending time range scene: text=Diagnose2, color=5, duration=15s, area=40, slot=6
2026-08-10 23:17:58.357 INFO [custom_components.glance_clock.notify] Time range scene sent successfully
2026-08-10 23:17:58.357 INFO [custom_components.glance_clock.services.time_range] Time range scene sent successfully: Diagnose2
Parameterübergabe und BLE-Versand funktionieren zu 100% korrekt. Das Problem liegt zwischen “Kommando kommt bei der Uhr an” und “Uhr rendert es korrekt”.
Ergebnis auf der Uhr: Farbe wechselte korrekt von Blau → Rot
. Position blieb exakt gleich trotz area 0 → 24 → 40
.
Schlussfolgerung: MovingBarData.area steuert vermutlich nicht die Startposition auf dem Ring (evtl. Balkenbreite, Animationsbereich, oder wird nur bei speed > 0 überhaupt ausgewertet). Für “zünde exakt Segmente X bis Y” ist vermutlich CustomScene.Object.FillData.segment (Methode Fill, Wert 2 statt MovingBar=5) das richtige Feld — eine Liste konkreter LED-Segment-Indizes statt eines animierten Balkens mit unklarem Startpunkt.
Nächster Schritt (geplant, noch nicht umgesetzt): Neue Testfunktion mit method: Fill (2) und FillData.segment = [Liste von Ints], um herauszufinden, wie viele Segmente ein Vollkreis hat und wo Segment 0 auf dem Zifferblatt sitzt.
7.4 Test mit method: Fill — Ergebnis: Kein sichtbarer Effekt (11.08., 11:31–11:33 Uhr)
Neue Funktion async_send_fill_test in notify.py ergänzt (method=2/Fill statt method=5/MovingBar), Service glance_clock.send_fill_test registriert (analog zu send_time_range, in services/init.py Import/Wrapper/Registrierung ergänzt — bestätigt korrekt eingebaut).
Testreihe:
11:31:56 segments=[0], slot=6 → Command sent successfully (with response)
11:32:53 segments=[6], slot=6 → Command sent successfully (with response)
11:33:01 segments=[12], slot=6 → Command sent successfully (with response)
11:33:10 segments=[24], slot=6 → Command sent successfully (with response)
Alle vier BLE-Kommandos wurden von der Uhr auf Protokollebene bestätigt (“with response” = ACK erhalten). Aber: Die Uhr zeigte bei keinem einzigen Test irgendeine sichtbare Veränderung — nicht mal an der alten (blauen/roten) Position von den MovingBar-Tests. Kompletter visueller Nulleffekt.
Schlussfolgerung: method: Fill (allein, ohne weitere Felder) scheint von der Firmware entweder ignoriert oder benötigt zusätzliche Pflichtfelder, die uns unbekannt sind (evtl. muss die Szene separat “gestartet” werden, oder Fill ist nur in Kombination mit anderen Object-Feldern wirksam, oder das genutzte display_mode (24 = “Ring & Text”) passt nicht zu reinen Fill-Only-Szenen ohne Text).
Bilanz nach diesem Entwicklungsschritt:
Farbige Anzeige grundsätzlich möglich (MovingBar, Farbe funktioniert zuverlässig)
Position/Segment-Steuerung bisher nicht reproduzierbar herstellbar (area bei MovingBar wirkungslos, Fill komplett wirkungslos)
Automatisches Löschen der Szene nach lifeTime funktionierte in einem Test nicht zuverlässig (Szene blieb ~12h aktiv, nur Stromreset half)
Aufwand/Ergebnis: Deutlich umfangreicheres Reverse-Engineering nötig als ursprünglich angenommen, ohne Erfolgsgarantie — dies ist unerforschtes Terrain jenseits der offiziellen Doku/Community-Wissen zu diesem Gerät.
Offene Entscheidung: Weiter experimentieren (z. B. method: AreaAnimation, oder Kombinationen aus fill + movingBar + text im selben Object, oder rohe Byte-Analyse mit BLE-Sniffer) — oder vorerst pausieren und auf den einfacheren send_notice-Kompromiss (Start/Ende-Blitz) zurückfallen, der zuverlässig funktioniert.
7.5 Test mit ScenesStart (Kommando 31) nach Fill — Ergebnis: Ebenfalls kein Effekt (11.08.)
Aus der vollständigen Readme-Tabelle von GitHub - Hypfer/glance-clock: Glance Clock BLE protocol reverse-engineered · GitHub ergab sich der Verdacht, dass gespeicherte Szenen erst per ScenesStart (Kommando 31) aktiv “durchgeblättert” werden müssen, da diese laut Doku nur “Go to next scene slot” bewirkt und Szenen sonst nur im Speicher liegen.
Neue Methode async_scenes_start in notify.py ergänzt, Service glance_clock.scenes_start registriert (analog zu den anderen, in services/init.py korrekt eingebaut nach kurzer Nachbesserung des fehlenden Wrapper-/Registrierungs-Blocks).
Test:
yaml
action: glance_clock.send_fill_test
data: {segments: [0], scene_slot: 6}
action: glance_clock.scenes_start
data: {}
Ergebnis: Kein sichtbarer Effekt auf der Uhr — weder durch Fill allein, noch durch das nachgeschobene ScenesStart.
Fazit nach diesem Entwicklungsschritt:
MovingBar (method=5) ist bisher die einzige Methode, die überhaupt eine sichtbare Reaktion auslöst (Farbe ändert sich zuverlässig), aber Positionskontrolle (area) bleibt wirkungslos
Fill (method=2) zeigt nie einen sichtbaren Effekt, auch nicht in Kombination mit ScenesStart
Wir haben jetzt mehrere plausible Hypothesen durchgetestet (Positions-Parameter, fehlendes Aktivierungskommando) ohne Erfolg
Realistischer nächster Schritt wäre BLE-Traffic-Sniffing der offiziellen Glance-Clock-App (Android/iOS) beim Senden einer echten Szene, um das korrekte Byte-Format 1:1 abzugleichen — das erfordert zusätzliche Hardware/Software (BLE-Sniffer, gerootetes Testgerät oder HCI-Snoop-Log) und geht über das hinaus, was sich per Chat-gestützter Fehlersuche sinnvoll weiterverfolgen lässt
Empfehlung: Diesen experimentellen Pfad an dieser Stelle pausieren. Der zuverlässig funktionierende send_notice-Ansatz (kurzer Farbblitz bei Start/Ende eines Zeitraums) bleibt die praktikable Lösung für den produktiven Einsatz. Der bisherige Code (send_time_range, send_fill_test, scenes_start) bleibt als Grundlage im System erhalten, falls später mit BLE-Sniffing-Daten weitergemacht werden soll.
Update: Nutzer möchte doch noch einen letzten Ansatz testen (AreaAnimation/LightFlash, hat einen eigenen color-Parameter), siehe 7.6.
7.6 Neuer Versuch: AreaAnimation mit LightFlash (11.08., in Vorbereitung)
Begründung: AreaAnimation (method=7) mit type: LightFlash hat im Protobuf-Schema ein eigenes color-Feld direkt im LightFlashData-Objekt, kombiniert mit einer area-Liste (Segmente). Das könnte Position UND Farbe zuverlässiger verknüpfen als die bisherigen Versuche.
Neue Methode async_send_area_animation in notify.py ergänzt:
python
async def async_send_area_animation(self, segments, color, speed=0, duration_seconds=15, scene_slot=6) → bool:
“”“EXPERIMENTAL: Test AreaAnimation with LightFlash (has its own color field).”“”
if not self._connection_manager or not self._connection_manager.is_connected:
_LOGGER.warning(“Device not connected, cannot send area animation”)
return False
try:
from .glance_pb2 import CustomScene # type: ignore
scene = CustomScene()
obj = scene.object.add()
obj.method = 7 # AreaAnimation
obj.lifeTime = int(duration_seconds)
obj.areaAnimation.type = 2 # LightFlash
for seg in segments:
obj.areaAnimation.area.append(int(seg))
obj.areaAnimation.flashLight.color = int(color)
obj.areaAnimation.flashLight.speed = int(speed)
scene_bytes = scene.SerializeToString()
header = bytearray([0, 0, 24, scene_slot])
command = header + scene_bytes
_LOGGER.info(f"Sending area animation: segments={segments}, color={color}, speed={speed}, duration={duration_seconds}s, slot={scene_slot}“)
success = await self._connection_manager.send_command(bytes(command))
return success
except Exception as e:
_LOGGER.error(f"Error sending area animation: {e}”)
return False
Zugehöriger Service glance_clock.send_area_animation in services/time_range.py und services/init.py registriert (analog zu den vorherigen, Import/Wrapper/Registrierung ergänzt).
Status: Code steht, Test noch nicht durchgeführt (siehe 7.7 für wichtige Zwischenkorrektur, die vor dem Test nötig war).
7.7 Wichtiger Bugfix in notify.py (11.08.)
Beim Einbau der neuen Funktionen hat der Nutzer eigenständig notify.py aufgeräumt und dabei zwei echte Probleme gefunden und behoben:
Fehlender return im Exception-Handler von async_read_current_settings: Der ursprüngliche Code konnte in bestimmten Fehlerfällen zu einem NameError führen, da raw_data nicht in jedem Pfad initialisiert war. Fix: raw_data = b"" vor dem try-Block gesetzt und return None im except-Zweig ergänzt.
Doppelte/falsch platzierte Klassen-Docstring entfernt, die mitten im Dateikörper zwischen Methoden stand (Überbleibsel einer früheren Bearbeitung).
Kritischer Fund bei der Überprüfung: Die beiden Modul-Funktionen async_setup_entry und async_unload_entry (außerhalb der Klasse, am Dateiende) — die zwingend nötig sind, damit HA die notify-Plattform überhaupt lädt (das war exakt das Problem ganz zu Beginn dieser Session: notify.glance_clock not found) — fehlten in der vom Nutzer geposteten Version. Es war unklar, ob sie in der echten Datei nur beim Copy-Paste abgeschnitten wurden oder tatsächlich fehlten.
Maßnahme: Vollständige, korrigierte notify.py wurde als Datei bereitgestellt (inkl. Bugfix, allen experimentellen Methoden async_send_time_range, async_send_fill_test, async_scenes_start, async_send_area_animation, UND den beiden essenziellen Modul-Funktionen am Ende). Diese komplette Datei sollte die aktuell gültige /config/custom_components/glance_clock/notify.py ersetzen.
Noch zu tun:
Bestätigen, dass die bereitgestellte komplette Datei übernommen wurde
Nach Neustart: AreaAnimation-Test durchführen (siehe 7.6 für Service-Aufruf-Beispiel unten)
Test-Aufruf:
yaml
action: glance_clock.send_area_animation
data:
segments: [0]
color: red
speed: 0
duration_seconds: 15
scene_slot: 6
Falls speed: 0 nichts zeigt, zusätzlich mit speed: 500 testen.
Offene To-Dos (Zusammenfassung)
In Arbeit: Custom-Scene-Feature (send_time_range) — Grundgerüst steht, aber color/area-Parameter scheinen nicht anzukommen bzw. immer dieselbe (evtl. alte/hängende) Szene wird angezeigt. Debugging läuft (siehe Abschnitt 7.2).
Sound-Schalter (input_boolean.glance_clock_sound_aktiv) final in Packages-Datei und Automation 2 übernehmen (Code oben vorbereitet, Übernahmestatus unklar)
Optional: Zeit-Picker-Card (HACS) installieren und Popup-Content im Template darauf umstellen (Code oben vorbereitet, Fix für Template-Syntax nötig)
Ggf. Schalter für Sound irgendwo im Dashboard sichtbar platzieren
Fehlende Scene-Cleanup-Funktion (ScenesDelete) für send_time_range ergänzen, um hängende Szenen ohne Stromreset entfernen zu können
Ursache für “Parameter werden ignoriert” bei send_time_range finden (siehe Verdachtsmomente 7.2)