def __init__( self, hass: HomeAssistant, name_template: Template, value_template: Template, availability_template: Template | None, command_set_value: dict[str, Any], step_template: Template, minimum_template: Template | None, maximum_template: Template | None, optimistic: bool, unique_id: str | None, ) -> None: """Initialize the number.""" super().__init__(availability_template=availability_template) self._attr_name = DEFAULT_NAME self._name_template = name_template name_template.hass = hass with contextlib.suppress(TemplateError): self._attr_name = name_template.async_render(parse_result=False) self._value_template = value_template domain = __name__.split(".")[-2] self._command_set_value = Script(hass, command_set_value, self._attr_name, domain) self._step_template = step_template self._min_value_template = minimum_template self._max_value_template = maximum_template self._attr_assumed_state = self._optimistic = optimistic self._attr_unique_id = unique_id self._attr_value = None self._attr_step = None
async def validate_configs(hass, entity_configs): """Validate that entities exist and ensure templates are ready to use.""" entity_registry = await hass.helpers.entity_registry.async_get_registry() for entity_id, entity_config in entity_configs.items(): state = hass.states.get(entity_id) if state is None: _LOGGER.debug("Entity not found: %s", entity_id) continue entity = entity_registry.async_get(entity_id) if entity: entity_config[CONF_UNIQUE_ID] = get_system_unique_id(entity) else: entity_config[CONF_UNIQUE_ID] = entity_id if CONF_POWER in entity_config: power_val = entity_config[CONF_POWER] if isinstance(power_val, str) and is_template_string(power_val): entity_config[CONF_POWER] = Template(power_val, hass) elif isinstance(power_val, Template): entity_config[CONF_POWER].hass = hass elif CONF_POWER_ENTITY in entity_config: power_val = entity_config[CONF_POWER_ENTITY] if hass.states.get(power_val) is None: _LOGGER.debug("Sensor Entity not found: %s", power_val) else: entity_config[CONF_POWER] = power_val elif state.domain == SENSOR_DOMAIN: pass elif ATTR_CURRENT_POWER_W in state.attributes: pass else: _LOGGER.debug("No power value defined for: %s", entity_id)
def add_template_attribute( self, attribute: str, template: Template, validator: Callable[[Any], Any] = None, on_update: Callable[[Any], None] | None = None, none_on_template_error: bool = False, ) -> None: """ Call in the constructor to add a template linked to a attribute. Parameters ---------- attribute The name of the attribute to link to. This attribute must exist unless a custom on_update method is supplied. template The template to calculate. validator Validator function to parse the result and ensure it's valid. on_update Called to store the template result rather than storing it the supplied attribute. Passed the result of the validator, or None if the template or validator resulted in an error. """ assert self.hass is not None, "hass cannot be None" template.hass = self.hass attribute = _TemplateAttribute(self, attribute, template, validator, on_update, none_on_template_error) self._template_attrs.setdefault(template, []) self._template_attrs[template].append(attribute)
async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the SQL sensor entry.""" db_url: str = entry.options[CONF_DB_URL] name: str = entry.options[CONF_NAME] query_str: str = entry.options[CONF_QUERY] unit: str | None = entry.options.get(CONF_UNIT_OF_MEASUREMENT) template: str | None = entry.options.get(CONF_VALUE_TEMPLATE) column_name: str = entry.options[CONF_COLUMN_NAME] value_template: Template | None = None if template is not None: try: value_template = Template(template) value_template.ensure_valid() except TemplateError: value_template = None if value_template is not None: value_template.hass = hass try: engine = sqlalchemy.create_engine(db_url, future=True) sessmaker = scoped_session(sessionmaker(bind=engine, future=True)) except SQLAlchemyError as err: _LOGGER.error("Can not open database %s", {redact_credentials(str(err))}) return # MSSQL uses TOP and not LIMIT if not ("LIMIT" in query_str.upper() or "SELECT TOP" in query_str.upper()): if "mssql" in db_url: query_str = query_str.upper().replace("SELECT", "SELECT TOP 1") else: query_str = query_str.replace(";", "") + " LIMIT 1;" async_add_entities( [ SQLSensor( name, sessmaker, query_str, column_name, unit, value_template, entry.entry_id, ) ], True, )
def __init__( self, hass: HomeAssistant, name_template: Template, availability_template: Template | None, command_press: dict[str, Any], device_class: ButtonDeviceClass | None, unique_id: str | None, icon_template: Template | None, ) -> None: """Initialize the button.""" super().__init__(availability_template=availability_template, icon_template=icon_template) self._attr_name = DEFAULT_NAME self._name_template = name_template name_template.hass = hass with contextlib.suppress(TemplateError): self._attr_name = name_template.async_render(parse_result=False) self._command_press = Script(hass, command_press, self._attr_name, DOMAIN) self._attr_device_class = device_class self._attr_unique_id = unique_id self._attr_state = None
for entity_id, entity_config in entity_configs.items(): if (state := hass.states.get(entity_id)) is None: _LOGGER.debug("Entity not found: %s", entity_id) continue if entity := entity_registry.async_get(entity_id): entity_config[CONF_UNIQUE_ID] = get_system_unique_id(entity) else: entity_config[CONF_UNIQUE_ID] = entity_id if CONF_POWER in entity_config: power_val = entity_config[CONF_POWER] if isinstance(power_val, str) and is_template_string(power_val): entity_config[CONF_POWER] = Template(power_val, hass) elif isinstance(power_val, Template): entity_config[CONF_POWER].hass = hass elif CONF_POWER_ENTITY in entity_config: power_val = entity_config[CONF_POWER_ENTITY] if hass.states.get(power_val) is None: _LOGGER.debug("Sensor Entity not found: %s", power_val) else: entity_config[CONF_POWER] = power_val elif state.domain == SENSOR_DOMAIN: pass else: _LOGGER.debug("No power value defined for: %s", entity_id) def get_system_unique_id(entity: RegistryEntry): """Determine the system wide unique_id for an entity.""" return f"{entity.platform}.{entity.domain}.{entity.unique_id}"
def update_info(self): """update info and refresh info panel.""" info_config = self._info_config if info_config is None: return _LOGGER.debug("↓↓↓↓↓_update_info()↓↓↓↓↓") running_tasks = self._get_running_tasks() self._running_tasks_ids = [ entity['entity_id'] for entity in running_tasks ] info_row_num = len(running_tasks) if len(running_tasks) < info_config[ CONF_INFO_NUM_MAX] else info_config[CONF_INFO_NUM_MAX] new_rows = [] info_ui = [] default_state = Template('-') default_state.hass = self._hass default_icon = Template('mdi:calendar-check') default_icon.hass = self._hass # refresh every row for row in range(0, info_config[CONF_INFO_NUM_MAX]): info_entity_id = 'sensor.ct_record_{}'.format(row) info_entity = self._hass.data['sensor'].get_entity(info_entity_id) # rows show record if row < info_row_num: _LOGGER.debug("info_entity:%s, row=%s", info_entity, row) # info1 = '{0:{2}<12}{1:{2}>20}'.format(running_tasks[row]['friendly_name'], running_tasks[row]['exec_time'].strftime("%Y-%m-%d %H:%M:%S"),chr(12288)) # for test info1 = '{}{}'.format( align(running_tasks[row]['friendly_name'], 20), align( running_tasks[row]['exec_time'].strftime( "%Y-%m-%d %H:%M:%S"), 20)) # name+time info template loop_flag = CONF_LOOP_FLAG if 'temporary' in running_tasks[ row]['operation'] else '' info2 = Template('{} {} → {}'.format( loop_flag, self.get_state(running_tasks[row]['entity_id']), running_tasks[row] ['next_operation'])) # operation info template info2.hass = self._hass # info3 = Template('{{{{states.{}.{}.{}}}}}'.format(running_tasks[row]['entity_id'] ,'attributes' ,'icon')) # for test info3 = Template(running_tasks[row]['icon']) # icon template info3.hass = self._hass # row has record, update if info_entity is not None: _LOGGER.debug("row%s, record exist. <info_entity_id= %s >", row, info_entity_id) info_entity._name = info1 info_entity._template = info2 info_entity._icon_template = info3 info_entity.schedule_update_ha_state( True ) # force_refresh = True to call device_update to update sensor.template # row has record, add else: _LOGGER.debug( "row%s, no record. <info_entity_id = %s, state = %s>", row, info_entity_id, self._dic_operation.get( running_tasks[row]['operation'])) object_id = 'ct_record_{}'.format(row) sensor = SensorTemplate(hass=self._hass, device_id=object_id, friendly_name=info1, friendly_name_template=None, unit_of_measurement=None, state_template=info2, icon_template=info3, entity_picture_template=None, entity_ids=set(), device_class=None) new_rows.append(sensor) info_ui.append(info_entity_id) # rows show blank or should be remove else: if not any([ info_row_num, row ]) or row < info_config[CONF_INFO_NUM_MIN] or info_config[ CONF_INFO_NUM_MAX] == info_config[CONF_INFO_NUM_MIN]: info1 = '无定时任务' info_entity._name = info1 info_entity._template = default_state info_entity._icon_template = default_icon info_entity.schedule_update_ha_state( True ) # force_refresh = True to call device_update to update sensor.template info_ui.append(info_entity_id) else: yield from self._hass.data['sensor'].async_remove_entity( info_entity_id) if new_rows: yield from self._hass.data['sensor']._platforms[ PLATFORM_KEY].async_add_entities(new_rows, update_before_add=True) data = { ATTR_OBJECT_ID: info_config[CONF_NAME], ATTR_NAME: info_config[CONF_FRIENDLY_NAME], ATTR_ENTITIES: [entity_id for entity_id in info_ui] } yield from self._hass.services.async_call('group', SERVICE_SET, data) _LOGGER.debug("↑↑↑↑↑_update_info()↑↑↑↑↑")
def async_setup(hass, config): _LOGGER.debug("-------%s--------", config[DOMAIN]) """ setup up common_timer component """ ui = {} #save params of input components for getting input VALIDATED_CONF = BUILT_IN_CONFIG[CONF_UI] info_ui = [] #remove CONF_USE_FOR from BUILT_IN_CONFIG, otherwise raise a validate failure for domain in VALIDATED_CONF: if isinstance(VALIDATED_CONF[domain], list): for object_id in VALIDATED_CONF[domain][0]['sensors']: info_ui.append('{}.{}'.format(domain, object_id)) else: for object_id in VALIDATED_CONF[domain]: if CONF_USE_FOR in VALIDATED_CONF[domain][object_id]: user_for = VALIDATED_CONF[domain][object_id][CONF_USE_FOR] ui[user_for] = '{}.{}'.format(domain, object_id) VALIDATED_CONF[domain][object_id].pop(CONF_USE_FOR) components = set(key.split(' ')[0] for key in config.keys()) for setup_domain in [ 'input_select', 'input_text', 'input_boolean', 'sensor' ]: #config file contains info, let HA initailize if setup_domain in components: _LOGGER.debug( 'initialize component[%s]: config has this component', setup_domain) #wait for HA initialize component #maybe it can use discovery.discover(hass, service='load_component.{}'.format(setup_domain), discovered={}, component=setup_domain, hass_config=config) instead while setup_domain not in hass.config.components: yield from asyncio.sleep(1) _LOGGER.debug( "initialize component[%s]: wait for HA initialization.", setup_domain) if setup_domain in ['input_select', 'input_text', 'input_boolean']: #entity belongs to component _LOGGER.debug( "initialize component[%s]: component is ready, use component's method.", setup_domain) #add entity in component entities = [] for object_id, conf in VALIDATED_CONF.get(setup_domain, {}).items(): # _LOGGER.debug("setup %s.%s", setup_domain, object_id) if setup_domain == 'input_select': # InputSelect(object_id, name, initial, options, icon) entity = InputSelect(object_id, conf.get(CONF_NAME, object_id), conf.get(CONF_INITIAL), conf.get(CONF_OPTIONS) or [], conf.get(CONF_ICON)) elif setup_domain == 'input_text': # InputText(object_id, name, initial, minimum, maximum, icon, unit, pattern, mode) entity = InputText(object_id, conf.get(CONF_NAME, object_id), conf.get(CONF_INITIAL), conf.get(CONF_MIN), conf.get(CONF_MAX), conf.get(CONF_ICON), conf.get(ATTR_UNIT_OF_MEASUREMENT), conf.get(CONF_PATTERN), conf.get(CONF_MODE)) elif setup_domain == 'input_boolean': # InputBoolean(object_id, name, initial, icon) entity = InputBoolean(object_id, conf.get(CONF_NAME), conf.get(CONF_INITIAL), conf.get(CONF_ICON)) _LOGGER.debug("input_boolean.timer_button:%s,%s,%s,%s", object_id, conf.get(CONF_NAME), conf.get(CONF_INITIAL), conf.get(CONF_ICON)) else: pass # _LOGGER.debug("illegal component:%s", object_id, conf.get(CONF_NAME), conf.get(CONF_INITIAL), conf.get(CONF_ICON)) entities.append(entity) # _LOGGER.debug("entities:%s", entities) yield from hass.data[setup_domain].async_add_entities(entities) _LOGGER.debug('initialize component[%s]: entities added.', setup_domain) elif setup_domain in ['sensor' ]: #entity belongs to component.platform _LOGGER.debug( "initialize component.platform[%s]: component is ready, use EntityComponent's method to initialize entity.", setup_domain) # should set a unique namespace to ensure it's a new platform and don't affect other entities using template platform which have been initialized. yield from hass.data[setup_domain].async_setup( {setup_domain: VALIDATED_CONF.get(setup_domain, {})}) else: _LOGGER.debug( "initialize component[%s]: undefined initialize method.", setup_domain) #add config for HA to initailize else: _LOGGER.debug( 'initialize component[%s]: config hasn\'t this componet , use HA\'s setup method to initialize entity.', setup_domain) hass.async_create_task( setup.async_setup_component(hass, setup_domain, VALIDATED_CONF)) #add group through service since HA initialize group by defalut data = { ATTR_OBJECT_ID: config[DOMAIN][CONF_NAME], ATTR_NAME: config[DOMAIN][CONF_FRIENDLY_NAME], ATTR_ENTITIES: [entity_id for param, entity_id in ui.items()] } # data[ATTR_ENTITIES].append('timer.laundry') yield from hass.services.async_call('group', SERVICE_SET, data) # hass.async_add_job(hass.services.async_call('group', SERVICE_SET, data)) _LOGGER.debug('---control planel initialized---') #info panel inital info_config = config[DOMAIN].get(CONF_INFO_PANEL) if info_config: entities = [] for num in range(1, info_config[CONF_INFO_NUM_MIN]): object_id = 'ct_record_{}'.format(num) state_template = Template('-') state_template.hass = hass icon_template = Template('mdi:calendar-check') icon_template.hass = hass entity = SensorTemplate(hass=hass, device_id=object_id, friendly_name='无定时任务', friendly_name_template=None, unit_of_measurement=None, state_template=state_template, icon_template=icon_template, entity_picture_template=None, entity_ids=set(), device_class=None) entities.append(entity) info_ui.append(entity.entity_id) yield from hass.data['sensor']._platforms[ PLATFORM_KEY].async_add_entities(entities) data = { ATTR_OBJECT_ID: info_config[CONF_NAME], ATTR_NAME: info_config[CONF_FRIENDLY_NAME], ATTR_ENTITIES: [entity_id for entity_id in info_ui] } yield from hass.services.async_call('group', SERVICE_SET, data) _LOGGER.debug('---info planel initialized---') domains = config[DOMAIN].get(CONF_DOMAINS) exclude = config[DOMAIN].get(CONF_EXCLUDE) pattern = config[DOMAIN].get(CONF_PATTERN) ratio = config[DOMAIN].get(CONF_RATIO) exclude.append(ui['switch']) # ignore ui input_boolean @callback def start_common_timer(event): """ initialize common_timer. """ _LOGGER.debug('start initialize common_timer.') common_timer = CommonTimer(domains, exclude, pattern, ratio, ui, hass, info_config) @callback def common_timer_handle(event): """Listen for state changed events and refresh ui. """ if event.data[ATTR_ENTITY_ID] == ui[UI_INPUT_DOMAIN]: # _LOGGER.debug('set domain from %s to %s',event.data['old_state'].as_dict()['state'] ,event.data['new_state'].as_dict()['state']) common_timer.choose_domain( event.data['new_state'].as_dict()['state']) elif event.data[ATTR_ENTITY_ID] == ui[UI_INPUT_ENTITY]: # _LOGGER.debug('set entity') common_timer.choose_entity( event.data['new_state'].as_dict()['state']) elif event.data[ATTR_ENTITY_ID] == ui[UI_INPUT_OPERATION]: # _LOGGER.debug('set operation') common_timer.choose_operation( event.data['new_state'].as_dict()['state']) elif event.data[ATTR_ENTITY_ID] == ui[UI_INPUT_DURATION]: pass # _LOGGER.debug('set time') # common_timer.input_duration(event.data['new_state'].as_dict()['state']) elif event.data[ATTR_ENTITY_ID] == ui[UI_SWITCH]: # _LOGGER.debug('start/stop') common_timer.switch(event.data['new_state'].as_dict()['state']) else: # _LOGGER.debug('start/stop') if common_timer.stop_loop_task(event.data[ATTR_ENTITY_ID], context=event.context): hass.async_add_job(common_timer.update_info) hass.bus.async_listen(EVENT_STATE_CHANGED, common_timer_handle) @asyncio.coroutine def async_handler_service(service): """ Handle calls to the common timer services. """ entity_id = service.data[ATTR_ENTITY_ID] duration = str(service.data[ATTR_DURATION]) operation = service.data[ATTR_OPERATION] is_loop = service.data[ATTR_IS_LOOP] if service.service == SERVICE_SET: common_timer.set_task(entity_id, operation, duration, is_loop) pass elif service.service == SERVICE_CANCEL: common_timer.cancel_task(entity_id) pass hass.services.async_register(DOMAIN, SERVICE_SET, async_handler_service, schema=COMMON_TIMER_SERVICE_SCHEMA) hass.services.async_register(DOMAIN, SERVICE_CANCEL, async_handler_service, schema=COMMON_TIMER_SERVICE_SCHEMA) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_common_timer) # for test # @callback # def service_executed(event): # if event.context == CONTEXT: # _LOGGER.debug("-----common_timer调用服务完毕!-----context = %s", CONTEXT) # hass.bus.async_listen(EVENT_SERVICE_EXECUTED, service_executed) return True