import datetime import apis from helper import scale, pos_to_color, rgb_dec565 from pages import LuiPagesGen from luibackend.config import Card class LuiController(object): def __init__(self, config, send_mqtt_msg): self._config = config self._send_mqtt_msg = send_mqtt_msg self._current_card = self._config._config_screensaver self._previous_cards = [] # first card (default, after startup) self._previous_cards.append(self._config.get_default_card()) self._pages_gen = LuiPagesGen(config, send_mqtt_msg) # send panel back to startup page on restart of this script self._pages_gen.page_type("pageStartup") # calculate current brightness self.current_screensaver_brightness = self.calc_current_brightness(self._config.get("sleepBrightness")) self.current_screen_brightness = self.calc_current_brightness(self._config.get("screenBrightness")) # register callbacks self.register_callbacks() def startup(self): apis.ha_api.log(f"Startup Event") # send time and date on startup self._pages_gen.update_time("") self._pages_gen.update_date("") # set current screensaver brightness self.update_screensaver_brightness(kwargs={"ssbr": self.current_screensaver_brightness, "sbr": self.current_screen_brightness}) # send panel to screensaver self._pages_gen.render_card(self._current_card) def update_screensaver_brightness_state_callback(self, entity, attribute, old, new, kwargs): self.current_screensaver_brightness = self.calc_current_brightness(self._config.get("sleepBrightness")) self.current_screen_brightness = self.calc_current_brightness(self._config.get("screenBrightness")) self.update_screensaver_brightness(kwargs={"ssbr": self.current_screensaver_brightness, "sbr": self.current_screen_brightness}) def update_screensaver_brightness(self, kwargs): bst = self._config.get("sleepTracking") sleepOverride = self._config.get("sleepOverride") sOEntity = None sOBrightness = None if sleepOverride is not None and type(sleepOverride) is dict: sOEntity = sleepOverride["entity"] sOBrightness = sleepOverride["brightness"] sleepBrightness = 0 brightness = self.calc_current_brightness(self._config.get("screenBrightness")) if bst is not None and apis.ha_api.entity_exists(bst) and apis.ha_api.get_entity(bst).state in self._config.get("sleepTrackingZones"): apis.ha_api.log(f"sleepTracking setting brightness to 0") sleepBrightness = 0 elif sOEntity is not None and sOBrightness is not None and apis.ha_api.entity_exists(sOEntity) and apis.ha_api.get_entity(sOEntity).state in ["on", "true", "home"]: apis.ha_api.log(f"sleepOverride setting brightness to {sOBrightness}") sleepBrightness = sOBrightness else: self.current_screensaver_brightness = kwargs['ssbr'] sleepBrightness = self.current_screensaver_brightness self.current_screen_brightness = kwargs['sbr'] brightness = self.current_screen_brightness # same value for both values will break sleep timer of the firmware if sleepBrightness==brightness: sleepBrightness = sleepBrightness-1 # background color dbc = 0 defaultBackgroundColor = self._config.get("defaultBackgroundColor") if type(defaultBackgroundColor) is str: if defaultBackgroundColor == "ha-dark": dbc = 6371 elif defaultBackgroundColor == "black": dbc = 0 elif type(defaultBackgroundColor) is list: dbc = rgb_dec565(defaultBackgroundColor) featureExperimentalSliders=0 if self._config.get("featureExperimentalSliders"): featureExperimentalSliders=1 self._send_mqtt_msg(f"dimmode~{sleepBrightness}~{brightness}~{dbc}~~{featureExperimentalSliders}") def calc_current_brightness(self, sleep_brightness_config): current_screensaver_brightness = 20 #sleep_brightness_config = self._config.get("sleepBrightness") # set brightness of screensaver if type(sleep_brightness_config) == int: current_screensaver_brightness = sleep_brightness_config elif type(sleep_brightness_config) == str: current_screensaver_brightness = int(float(apis.ha_api.get_state(sleep_brightness_config))) elif type(sleep_brightness_config) == list: sorted_timesets = sorted(sleep_brightness_config, key=lambda d: apis.ha_api.parse_time(d['time'])) # calc current screensaver brightness found_current_dim_value = False for i in range(len(sorted_timesets)): found = apis.ha_api.now_is_between(sorted_timesets[i-1]['time'], sorted_timesets[i]['time']) if found: found_current_dim_value = True current_screensaver_brightness = sorted_timesets[i-1]['value'] # still no dim value if not found_current_dim_value: apis.ha_api.log("Chooseing %s as fallback", sorted_timesets[0]) current_screensaver_brightness = sorted_timesets[0]["value"] return current_screensaver_brightness def register_callbacks(self): # time update callback time = datetime.time(0, 0, 0) apis.ha_api.run_minutely(self._pages_gen.update_time, time) # Setup date callback apis.ha_api.run_hourly(self._pages_gen.update_date, time) # register callbacks for each time if type(self._config.get("sleepBrightness")) == list: for index, timeset in enumerate(self._config.get("sleepBrightness")): apis.ha_api.run_daily(self.update_screensaver_brightness, timeset["time"], ssbr=timeset["value"], sbr=self.current_screen_brightness) # call update_screensaver_brightness on changes of entity configured in sleepTracking bst = self._config.get("sleepTracking") if bst is not None and apis.ha_api.entity_exists(bst): apis.ha_api.listen_state(self.update_screensaver_brightness_state_callback, entity_id=bst) # call update_screensaver_brightness on entity configured in sleepOverride sleepOverride = self._config.get("sleepOverride") if sleepOverride is not None and type(sleepOverride) is dict and sleepOverride["entity"] is not None and sleepOverride["brightness"] is not None and apis.ha_api.entity_exists(sleepOverride["entity"]): apis.ha_api.log(f"Configuring Sleep Override. Config is {sleepOverride}") apis.ha_api.listen_state(self.update_screensaver_brightness_state_callback, entity_id=sleepOverride["entity"]) # register callback for state changes on tracked value (for input_number) - sleepBrightness sleep_brightness_config = self._config.get("sleepBrightness") if type(sleep_brightness_config) == str and apis.ha_api.entity_exists(sleep_brightness_config): apis.ha_api.listen_state(self.update_screensaver_brightness_state_callback, entity_id=sleep_brightness_config) # register callback for state changes on tracked value (for input_number) - screenBrightness screen_brightness_config = self._config.get("screenBrightness") if type(screen_brightness_config) == str and apis.ha_api.entity_exists(screen_brightness_config): apis.ha_api.listen_state(self.update_screensaver_brightness_state_callback, entity_id=screen_brightness_config) items = self._config.get_all_entity_names() apis.ha_api.log(f"gtest123: {items}") prefixes = ("navigate.", "delete", "iText") items = [x for x in items if not (x is None or x.startswith(prefixes))] apis.ha_api.log(f"Registering callbacks for the following items: {items}") for item in items: if apis.ha_api.entity_exists(item): apis.ha_api.listen_state(self.state_change_callback, entity_id=item, attribute="all") def state_change_callback(self, entity, attribute, old, new, kwargs): #apis.ha_api.log(f"Got callback for: {entity}") #apis.ha_api.log(f"Current page has the following items: {self._current_card.get_entity_names(uuid=True)}") entities_on_card = self._current_card.get_entity_names(uuid=True) res_uuid = "uuid.notfound" if entity in sum(entities_on_card.values(), []): for uuid, names in entities_on_card.items(): #apis.ha_api.log(f"test124 items: {entities_on_card.items()} names: {names}") #apis.ha_api.log(f"State change callback matched for entity on current page: {names}") if entity in names: res_uuid = uuid #apis.ha_api.log(f"Callback Entity is on current page: {entity}") self._pages_gen.render_card(self._current_card, send_page_type=False) # send detail page update, just in case if self._current_card.cardType in ["cardGrid", "cardGrid2", "cardEntities", "cardMedia"]: if entity.startswith("light"): self._pages_gen.generate_light_detail_page(res_uuid) if entity.startswith("cover"): self._pages_gen.generate_shutter_detail_page(entity) if entity.startswith("fan"): self._pages_gen.generate_fan_detail_page(entity) if entity.startswith("input_select") or entity.startswith("select"): self._pages_gen.generate_input_select_detail_page(entity) if entity.startswith("media_player"): self._pages_gen.generate_input_select_detail_page(entity) if entity.startswith("timer"): self._pages_gen.generate_timer_detail_page(entity) if self._current_card.cardType == "cardThermo": if entity.startswith("climate"): self._pages_gen.generate_thermo_detail_page(entity) def detail_open(self, detail_type, entity_id): if detail_type == "popupShutter": self._pages_gen.generate_shutter_detail_page(entity_id, True) if detail_type == "popupLight": self._pages_gen.generate_light_detail_page(entity_id, True) if detail_type == "popupFan": self._pages_gen.generate_fan_detail_page(entity_id, True) if detail_type == "popupThermo": self._pages_gen.generate_thermo_detail_page(entity_id, True) if detail_type == "popupInSel": self._pages_gen.generate_input_select_detail_page(entity_id, True) if detail_type == "popupTimer": self._pages_gen.generate_timer_detail_page(entity_id, True) def button_press(self, entity_id, button_type, value): apis.ha_api.log(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ") if entity_id.startswith('uuid'): entity_config = self._config._config_entites_table.get(entity_id) if entity_config is not None: entity_id = entity_config.entityId # internal buttons if entity_id == "screensaver" and button_type == "bExit": # get default card if there is one defaultCard = self._config.get("screensaver.defaultCard") if defaultCard is not None: defaultCard = apis.ha_api.render_template(defaultCard) apis.ha_api.log(f"Searching for the following page as defaultPage: {defaultCard}") dstCard = self._config.search_card(defaultCard) apis.ha_api.log(f"Result for the following page as defaultPage: {dstCard}") if dstCard is not None: self._previous_cards = [] self._previous_cards.append(dstCard) # set _previous_cards to default page in case it's empty if len(self._previous_cards) == 0: self._previous_cards.append(self._config.get_default_card()) # check for double tap if configured and render current page if self._config.get("screensaver.doubleTapToUnlock") and int(value) >= 2: self._current_card = self._previous_cards.pop() self._pages_gen.render_card(self._current_card) elif not self._config.get("screensaver.doubleTapToUnlock"): self._current_card = self._previous_cards.pop() self._pages_gen.render_card(self._current_card) return if button_type == "sleepReached": self._previous_cards.append(self._current_card) self._current_card = self._config._config_screensaver self._pages_gen.render_card(self._current_card) return if button_type == "bExit": self._pages_gen.render_card(self._current_card) elif entity_id == "updateDisplayNoYes" and value == "no": self._pages_gen.render_card(self._current_card) # buttons with actions on HA if button_type == "OnOff": if value == "1": apis.ha_api.turn_on(entity_id) else: apis.ha_api.turn_off(entity_id) if button_type == "number-set": if entity_id.startswith('fan'): entity = apis.ha_api.get_entity(entity_id) value = float(value)*float(entity.attributes.get("percentage_step", 0)) entity.call_service("set_percentage", percentage=value) else: apis.ha_api.get_entity(entity_id).call_service("set_value", value=value) # for shutter / covers if button_type == "up": apis.ha_api.get_entity(entity_id).call_service("open_cover") if button_type == "stop": apis.ha_api.get_entity(entity_id).call_service("stop_cover") if button_type == "down": apis.ha_api.get_entity(entity_id).call_service("close_cover") if button_type == "positionSlider": pos = int(value) apis.ha_api.get_entity(entity_id).call_service("set_cover_position", position=pos) if button_type == "tiltOpen": apis.ha_api.get_entity(entity_id).call_service("open_cover_tilt") if button_type == "tiltStop": apis.ha_api.get_entity(entity_id).call_service("stop_cover_tilt") if button_type == "tiltClose": apis.ha_api.get_entity(entity_id).call_service("close_cover_tilt") if button_type == "tiltSlider": pos = int(value) apis.ha_api.get_entity(entity_id).call_service("set_cover_tilt_position", tilt_position=pos) if button_type == "button": if entity_id.startswith('navigate'): # internal navigation for next/prev if entity_id.startswith('navigate.uuid'): dstCard = self._config.get_card_by_uuid(entity_id.replace('navigate.','')) # internal for navigation to nested pages else: dstCard = self._config.search_card(entity_id) if dstCard is not None: if dstCard.hidden: self._previous_cards.append(self._current_card) self._current_card = dstCard self._pages_gen.render_card(self._current_card) else: apis.ha_api.log(f"No page with key {entity_id} found") if entity_id.startswith('navUp'): if self._previous_cards: self._current_card = self._previous_cards.pop() else: self._current_card = self._config.get_default_card() self._pages_gen.render_card(self._current_card) if entity_id.startswith('navPrev'): if self._current_card.uuid_prev: self._current_card = self._config.get_card_by_uuid(self._current_card.uuid_prev) self._pages_gen.render_card(self._current_card) if entity_id.startswith('navNext'): if self._current_card.uuid_next: self._current_card = self._config.get_card_by_uuid(self._current_card.uuid_next) self._pages_gen.render_card(self._current_card) elif entity_id.startswith('scene'): apis.ha_api.get_entity(entity_id).call_service("turn_on") elif entity_id.startswith('script'): apis.ha_api.get_entity(entity_id).call_service("turn_on") elif entity_id.startswith('light') or entity_id.startswith('switch') or entity_id.startswith('input_boolean') or entity_id.startswith('automation') or entity_id.startswith('fan'): apis.ha_api.get_entity(entity_id).call_service("toggle") elif entity_id.startswith('lock'): if apis.ha_api.get_entity(entity_id).state == "locked": apis.ha_api.get_entity(entity_id).call_service("unlock") else: apis.ha_api.get_entity(entity_id).call_service("lock") elif entity_id.startswith('button') or entity_id.startswith('input_button'): apis.ha_api.get_entity(entity_id).call_service("press") elif entity_id.startswith('input_select') or entity_id.startswith('select'): apis.ha_api.get_entity(entity_id).call_service("select_next") elif entity_id.startswith('vacuum'): if apis.ha_api.get_entity(entity_id).state == "docked": apis.ha_api.get_entity(entity_id).call_service("start") else: apis.ha_api.get_entity(entity_id).call_service("return_to_base") elif entity_id.startswith('service'): apis.ha_api.call_service(entity_id.replace('service.', '', 1).replace('.','/', 1), **entity_config.data) # for media page if button_type == "media-next": apis.ha_api.get_entity(entity_id).call_service("media_next_track") if button_type == "media-back": apis.ha_api.get_entity(entity_id).call_service("media_previous_track") if button_type == "media-pause": apis.ha_api.get_entity(entity_id).call_service("media_play_pause") if button_type == "media-OnOff": if apis.ha_api.get_entity(entity_id).state == "off": apis.ha_api.get_entity(entity_id).call_service("turn_on") else: apis.ha_api.get_entity(entity_id).call_service("turn_off") if button_type == "media-shuffle": suffle = not apis.ha_api.get_entity(entity_id).attributes.shuffle apis.ha_api.get_entity(entity_id).call_service("shuffle_set", shuffle=suffle) if button_type == "volumeSlider": pos = int(value) # HA wants this value between 0 and 1 as float pos = pos/100 apis.ha_api.get_entity(entity_id).call_service("volume_set", volume_level=pos) if button_type == "speaker-sel": apis.ha_api.get_entity(entity_id).call_service("select_source", source=value) # for light detail page if button_type == "brightnessSlider": # scale 0-100 to ha brightness range brightness = int(scale(int(value),(0,100),(0,255))) apis.ha_api.get_entity(entity_id).call_service("turn_on", brightness=brightness) if button_type == "colorTempSlider": entity = apis.ha_api.get_entity(entity_id) #scale 0-100 from slider to color range of lamp color_val = scale(int(value), (0, 100), (entity.attributes.min_mireds, entity.attributes.max_mireds)) apis.ha_api.get_entity(entity_id).call_service("turn_on", color_temp=color_val) if button_type == "colorWheel": apis.ha_api.log(value) value = value.split('|') color = pos_to_color(int(value[0]), int(value[1]), int(value[2])) apis.ha_api.log(color) apis.ha_api.get_entity(entity_id).call_service("turn_on", rgb_color=color) # for climate page if button_type == "tempUpd": temp = int(value)/10 apis.ha_api.get_entity(entity_id).call_service("set_temperature", temperature=temp) if button_type == "tempUpdHighLow": value = value.split("|") temp_high = int(value[0])/10 temp_low = int(value[1])/10 apis.ha_api.get_entity(entity_id).call_service("set_temperature", target_temp_high=temp_high, target_temp_low=temp_low) if button_type == "hvac_action": apis.ha_api.get_entity(entity_id).call_service("set_hvac_mode", hvac_mode=value) # for alarm page if button_type in ["disarm", "arm_home", "arm_away", "arm_night", "arm_vacation"]: apis.ha_api.get_entity(entity_id).call_service(f"alarm_{button_type}", code=value) if button_type == "opnSensorNotify": msg = "" entity = apis.ha_api.get_entity(entity_id) if "open_sensors" in entity.attributes and entity.attributes.open_sensors is not None: for e in entity.attributes.open_sensors: msg += f"- {apis.ha_api.get_entity(e).attributes.friendly_name}\r\n" self._pages_gen.send_message_page("opnSensorNotifyRes", "", msg, "", "") # for cardUnlock if button_type == "cardUnlock-unlock": curCard = self._config.get_card_by_uuid(entity_id.replace('navigate.','')) if curCard is not None: if int(curCard.raw_config.get("pin")) == int(value): dstCard = self._config.search_card(curCard.raw_config.get("destination")) if dstCard is not None: if dstCard.hidden: self._previous_cards.append(self._current_card) self._current_card = dstCard self._pages_gen.render_card(self._current_card) if button_type == "mode-preset_modes": entity = apis.ha_api.get_entity(entity_id) preset_mode = entity.attributes.preset_modes[int(value)] entity.call_service("set_preset_mode", preset_mode=preset_mode) if button_type == "mode-swing_modes": entity = apis.ha_api.get_entity(entity_id) swing_mode = entity.attributes.swing_modes[int(value)] entity.call_service("set_swing_mode", swing_mode=swing_mode) if button_type == "mode-fan_modes": entity = apis.ha_api.get_entity(entity_id) fan_mode = entity.attributes.fan_modes[int(value)] entity.call_service("set_fan_mode", fan_mode=fan_mode) if button_type in ["mode-input_select", "mode-select"]: entity = apis.ha_api.get_entity(entity_id) option = entity.attributes.options[int(value)] entity.call_service("select_option", option=option) if button_type == "mode-light": if entity_id.startswith('uuid'): entity_config = self._config._config_entites_table.get(entity_id) entity_id = entity_config.entityId entity = apis.ha_api.get_entity(entity_id) options_list = entity_config.entity_input_config.get("effectList") if options_list is not None: option = options_list[int(value)] else: option = entity.attributes.effect_list[int(value)] entity.call_service("turn_on", effect=option) if button_type == "mode-media_player": entity = apis.ha_api.get_entity(entity_id) option = entity.attributes.source_list[int(value)] entity.call_service("select_source", source=option) # timer detail page if button_type == "timer-start": if value is not None: apis.ha_api.get_entity(entity_id).call_service("start", duration=value) else: apis.ha_api.get_entity(entity_id).call_service("start") if button_type == "timer-cancel": apis.ha_api.get_entity(entity_id).call_service("cancel") if button_type == "timer-pause": apis.ha_api.get_entity(entity_id).call_service("pause") if button_type == "timer-finish": apis.ha_api.get_entity(entity_id).call_service("finish") @property def current_card(self) -> Card: """Used to get the current card""" return self._current_card