@@ -122,18 +122,23 @@ async def update_ipixel_display(hass: HomeAssistant, device_name: str, api, text
122122
123123 # Route to appropriate mode handler
124124 if mode == MODE_TEXT_IMAGE :
125- return await _update_textimage_mode (hass , device_name , api , text )
125+ success = await _update_textimage_mode (hass , device_name , api , text )
126126 elif mode == MODE_TEXT :
127- return await _update_text_mode (hass , device_name , api , text )
127+ success = await _update_text_mode (hass , device_name , api , text )
128128 elif mode == MODE_CLOCK :
129- return await _update_clock_mode (hass , device_name , api )
129+ success = await _update_clock_mode (hass , device_name , api )
130130 elif mode == MODE_TIMER :
131- # Timer mode is triggered by timer entity state changes, not manual updates
132- _LOGGER .debug ("Timer mode active - waiting for timer entity to start" )
133- return True
131+ success = await _update_timer_mode (hass , device_name , api )
134132 else :
135133 _LOGGER .warning ("Unknown mode: %s, falling back to textimage" , mode )
136- return await _update_textimage_mode (hass , device_name , api , text )
134+ success = await _update_textimage_mode (hass , device_name , api , text )
135+ mode = MODE_TEXT_IMAGE
136+
137+ # Store the active mode on successful update
138+ if success :
139+ api ._active_mode = mode
140+
141+ return success
137142
138143 except Exception as err :
139144 _LOGGER .error ("Error during display update: %s" , err )
@@ -355,6 +360,127 @@ async def _update_text_mode(hass: HomeAssistant, device_name: str, api, text: st
355360 return False
356361
357362
363+ async def _update_timer_mode (hass : HomeAssistant , device_name : str , api ) -> bool :
364+ """Update display in timer mode.
365+
366+ Args:
367+ hass: Home Assistant instance
368+ device_name: Device name for entity ID lookups
369+ api: iPIXEL API instance
370+
371+ Returns:
372+ True if update was successful
373+ """
374+ from datetime import datetime , timezone
375+
376+ try :
377+ # Get selected timer entity
378+ timer_entity_id = get_entity_id_by_unique_id (hass , api ._address , "timer_entity_select" , "select" )
379+ timer_select_state = hass .states .get (timer_entity_id ) if timer_entity_id else None
380+
381+ if not timer_select_state or timer_select_state .state == "none" :
382+ _LOGGER .warning ("No timer entity selected - skipping update" )
383+ return False
384+
385+ selected_timer = timer_select_state .state
386+ timer_state = hass .states .get (selected_timer )
387+
388+ if not timer_state :
389+ _LOGGER .warning ("Timer entity %s not found" , selected_timer )
390+ return False
391+
392+ # Determine seconds and static flag based on timer state
393+ state = timer_state .state
394+ attrs = timer_state .attributes
395+ seconds = 0
396+ static = True # Default to static
397+
398+ if state == "active" :
399+ # Calculate remaining time from finishes_at
400+ finishes_at = attrs .get ("finishes_at" )
401+ if finishes_at :
402+ try :
403+ finish_time = datetime .fromisoformat (finishes_at .replace ("Z" , "+00:00" ))
404+ now = datetime .now (timezone .utc )
405+ seconds = max (0 , int ((finish_time - now ).total_seconds ()))
406+ static = False # Animated countdown
407+ except (ValueError , TypeError ) as err :
408+ _LOGGER .error ("Failed to parse finishes_at '%s': %s" , finishes_at , err )
409+ elif state == "paused" :
410+ # Use remaining attribute
411+ remaining = attrs .get ("remaining" )
412+ if remaining :
413+ seconds = _parse_timer_duration (remaining )
414+ static = True # Static for paused
415+ else : # idle
416+ # Use duration attribute (time it will run when started)
417+ duration = attrs .get ("duration" )
418+ if duration :
419+ seconds = _parse_timer_duration (duration )
420+ static = True # Static for idle
421+
422+ if seconds <= 0 :
423+ _LOGGER .warning ("Timer has no valid duration" )
424+ return False
425+
426+ # Get font size setting
427+ font_size = await _get_entity_setting (hass , device_name , "number" , "font_size" , float , api ._address )
428+
429+ # Get colors from light entities
430+ text_color_hex = get_color_from_light_entity (hass , api ._address , "text_color" , default = "00ff00" )
431+ bg_color_hex = get_color_from_light_entity (hass , api ._address , "background_color" , default = "000000" )
432+
433+ text_color = (
434+ int (text_color_hex [0 :2 ], 16 ),
435+ int (text_color_hex [2 :4 ], 16 ),
436+ int (text_color_hex [4 :6 ], 16 ),
437+ )
438+ bg_color = (
439+ int (bg_color_hex [0 :2 ], 16 ),
440+ int (bg_color_hex [2 :4 ], 16 ),
441+ int (bg_color_hex [4 :6 ], 16 ),
442+ )
443+
444+ # Connect if needed
445+ if not api .is_connected :
446+ _LOGGER .debug ("Reconnecting to device for timer mode update" )
447+ await api .connect ()
448+
449+ # Display timer
450+ success = await api .display_timer_gif (
451+ duration_seconds = seconds ,
452+ text_color = text_color ,
453+ bg_color = bg_color ,
454+ static = static ,
455+ font_size = font_size ,
456+ )
457+
458+ if success :
459+ _LOGGER .info ("Timer mode update: %d sec, state=%s, static=%s" , seconds , state , static )
460+ else :
461+ _LOGGER .error ("Timer mode update failed" )
462+
463+ return success
464+
465+ except Exception as err :
466+ _LOGGER .error ("Error in timer mode update: %s" , err )
467+ return False
468+
469+
470+ def _parse_timer_duration (duration_str : str ) -> int :
471+ """Parse timer duration string to seconds."""
472+ try :
473+ parts = duration_str .split (":" )
474+ if len (parts ) == 3 :
475+ return int (parts [0 ]) * 3600 + int (parts [1 ]) * 60 + int (parts [2 ])
476+ elif len (parts ) == 2 :
477+ return int (parts [0 ]) * 60 + int (parts [1 ])
478+ else :
479+ return int (float (duration_str ))
480+ except (ValueError , IndexError ):
481+ return 0
482+
483+
358484async def _get_entity_setting (hass : HomeAssistant , device_name : str , platform : str , setting : str , value_type = str , address : str = None ):
359485 """Get setting from Home Assistant entity.
360486
0 commit comments