def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) # type: ignore[no-untyped-call] for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables) ) except TemplateError as ex: _LOGGER.error("Error rendering data template for %s: %s", host, ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": _LOGGER.error( "Can only generate events under esphome domain! (%s)", host ) return # Call native tag scan if service_name == "tag_scanned": tag_id = service_data["tag_id"] hass.async_create_task( hass.components.tag.async_scan_tag(tag_id, device_id) ) return hass.bus.async_fire(service.service, service_data) else: hass.async_create_task( hass.services.async_call( domain, service_name, service_data, blocking=True ) )
def rewrite_common_legacy_to_modern_conf( entity_cfg: dict[str, Any], extra_legacy_fields: dict[str, str] = None) -> list[dict]: """Rewrite legacy config.""" entity_cfg = {**entity_cfg} if extra_legacy_fields is None: extra_legacy_fields = {} for from_key, to_key in itertools.chain(LEGACY_FIELDS.items(), extra_legacy_fields.items()): if from_key not in entity_cfg or to_key in entity_cfg: continue val = entity_cfg.pop(from_key) if isinstance(val, str): val = Template(val) entity_cfg[to_key] = val return entity_cfg
def test_bad_template(self): """Test Exception when the template cannot be parsed.""" bad = Template("{{ x - 12 }}", self.hass) # x is undefined duration = "01:00" sensor1 = HistoryStatsSensor("test", "on", bad, None, duration, "time", "Test") sensor1.hass = self.hass sensor2 = HistoryStatsSensor("test", "on", None, bad, duration, "time", "Test") sensor2.hass = self.hass before_update1 = sensor1._period before_update2 = sensor2._period sensor1.update_period() sensor2.update_period() assert before_update1 == sensor1._period assert before_update2 == sensor2._period
def test_template(self): """Test value template.""" test_message = email.message.Message() test_message['From'] = '*****@*****.**' test_message['Subject'] = 'Test' test_message['Date'] = datetime.datetime(2016, 1, 1, 12, 44, 57) test_message.set_payload("Test Message") sensor = imap_email_content.EmailContentSensor( self.hass, FakeEMailReader(deque([test_message])), 'test_emails_sensor', ['*****@*****.**'], Template("{{ subject }} from {{ from }} with message {{ body }}", self.hass)) sensor.entity_id = 'sensor.emailtest' sensor.schedule_update_ha_state(True) self.hass.block_till_done() self.assertEqual( "Test from [email protected] with message Test Message", sensor.state)
def test_period_parsing(self, mock): """Test the conversion from templates to period.""" now = datetime(2019, 1, 1, 23, 30, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.now", return_value=now): today = Template( "{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}", self.hass, ) duration = timedelta(hours=2, minutes=1) sensor1 = HistoryStatsSensor("test", "on", today, None, duration, "time", "test") sensor1.hass = self.hass sensor2 = HistoryStatsSensor("test", "on", None, today, duration, "time", "test") sensor2.hass = self.hass sensor1.update_period() sensor1_start, sensor1_end = sensor1._period sensor2.update_period() sensor2_start, sensor2_end = sensor2._period # Start = 00:00:00 assert sensor1_start.hour == 0 assert sensor1_start.minute == 0 assert sensor1_start.second == 0 # End = 02:01:00 assert sensor1_end.hour == 2 assert sensor1_end.minute == 1 assert sensor1_end.second == 0 # Start = 21:59:00 assert sensor2_start.hour == 21 assert sensor2_start.minute == 59 assert sensor2_start.second == 0 # End = 00:00:00 assert sensor2_end.hour == 0 assert sensor2_end.minute == 0 assert sensor2_end.second == 0
async def test_template(hass): """Test value template.""" test_message = email.message.Message() test_message["From"] = "*****@*****.**" test_message["Subject"] = "Test" test_message["Date"] = datetime.datetime(2016, 1, 1, 12, 44, 57) test_message.set_payload("Test Message") sensor = imap_email_content.EmailContentSensor( hass, FakeEMailReader(deque([test_message])), "test_emails_sensor", ["*****@*****.**"], Template("{{ subject }} from {{ from }} with message {{ body }}", hass), ) sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() assert sensor.state == "Test from [email protected] with message Test Message"
def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Setup roller shutter controlled by shell commands.""" rollershutters = config.get('rollershutters', {}) devices = [] for dev_name, properties in rollershutters.items(): value_template = properties.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template = Template(value_template, hass) devices.append( CommandRollershutter( hass, properties.get('name', dev_name), properties.get('upcmd', 'true'), properties.get('downcmd', 'true'), properties.get('stopcmd', 'true'), properties.get('statecmd', False), value_template)) add_devices_callback(devices)
def __init__(self, hass, config): """Set all the config values if they exist and get initial state.""" value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template = Template(value_template, hass) self._hass = hass self._config = { CONF_NAME: config.get(CONF_NAME), CONF_HOST: config.get(CONF_HOST), CONF_PORT: config.get(CONF_PORT), CONF_TIMEOUT: config.get(CONF_TIMEOUT), CONF_PAYLOAD: config.get(CONF_PAYLOAD), CONF_UNIT_OF_MEASUREMENT: config.get(CONF_UNIT_OF_MEASUREMENT), CONF_VALUE_TEMPLATE: value_template, CONF_VALUE_ON: config.get(CONF_VALUE_ON), CONF_BUFFER_SIZE: config.get(CONF_BUFFER_SIZE), } self._state = None self.update()
def _check_deprecated_turn_off(hass, turn_off_action): """Create an equivalent script for old turn off actions.""" if isinstance(turn_off_action, str): method = DEPRECATED_TURN_OFF_ACTIONS[turn_off_action] new_config = OrderedDict( [('service', '{}.{}'.format(DOMAIN, SERVICE_CALL_METHOD)), ('data_template', OrderedDict( [('entity_id', '{{ entity_id }}'), ('method', method)]))]) example_conf = dump(OrderedDict( [(CONF_TURN_OFF_ACTION, new_config)])) _LOGGER.warning( "The '%s' action for turn off Kodi is deprecated and " "will cease to function in a future release. You need to " "change it for a generic Home Assistant script sequence, " "which is, for this turn_off action, like this:\n%s", turn_off_action, example_conf) new_config['data_template'] = OrderedDict( [(key, Template(value, hass)) for key, value in new_config['data_template'].items()]) turn_off_action = [new_config] return turn_off_action
def test_period_parsing(self): """Test the conversion from templates to period.""" now = datetime(2019, 1, 1, 23, 30, 0, tzinfo=pytz.utc) with patch.dict(template.ENV.globals, {'now': lambda: now}): today = Template( '{{ now().replace(hour=0).replace(minute=0)' '.replace(second=0) }}', self.hass) duration = timedelta(hours=2, minutes=1) sensor1 = HistoryStatsSensor(self.hass, 'test', 'on', today, None, duration, 'time', 'test') sensor2 = HistoryStatsSensor(self.hass, 'test', 'on', None, today, duration, 'time', 'test') sensor1.update_period() sensor1_start, sensor1_end = sensor1._period sensor2.update_period() sensor2_start, sensor2_end = sensor2._period # Start = 00:00:00 assert sensor1_start.hour == 0 assert sensor1_start.minute == 0 assert sensor1_start.second == 0 # End = 02:01:00 assert sensor1_end.hour == 2 assert sensor1_end.minute == 1 assert sensor1_end.second == 0 # Start = 21:59:00 assert sensor2_start.hour == 21 assert sensor2_start.minute == 59 assert sensor2_start.second == 0 # End = 00:00:00 assert sensor2_end.hour == 0 assert sensor2_end.minute == 0 assert sensor2_end.second == 0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None: """Set up the Scrape sensor entry.""" name: str = entry.options[CONF_NAME] resource: str = entry.options[CONF_RESOURCE] select: str | None = entry.options.get(CONF_SELECT) attr: str | None = entry.options.get(CONF_ATTRIBUTE) index: int = int(entry.options[CONF_INDEX]) unit: str | None = entry.options.get(CONF_UNIT_OF_MEASUREMENT) device_class: str | None = entry.options.get(CONF_DEVICE_CLASS) state_class: str | None = entry.options.get(CONF_STATE_CLASS) value_template: str | None = entry.options.get(CONF_VALUE_TEMPLATE) entry_id: str = entry.entry_id val_template: Template | None = None if value_template is not None: val_template = Template(value_template, hass) rest = hass.data[DOMAIN][entry.entry_id] async_add_entities( [ ScrapeSensor( rest, name, select, attr, index, val_template, unit, device_class, state_class, entry_id, resource, ) ], True, )
def test_period_parsing(self): """Test the conversion from templates to period.""" today = Template( '{{ now().replace(hour=0).replace(minute=0)' '.replace(second=0) }}', self.hass) duration = timedelta(hours=2, minutes=1) sensor1 = HistoryStatsSensor(self.hass, 'test', 'on', today, None, duration, 'test') sensor2 = HistoryStatsSensor(self.hass, 'test', 'on', None, today, duration, 'test') sensor1.update_period() sensor2.update_period() self.assertEqual(sensor1.device_state_attributes['from'][-8:], '00:00:00') self.assertEqual(sensor1.device_state_attributes['to'][-8:], '02:01:00') self.assertEqual(sensor2.device_state_attributes['from'][-8:], '21:59:00') self.assertEqual(sensor2.device_state_attributes['to'][-8:], '00:00:00')
def async_on_service_call(service: 'ServiceCall') -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split('.', 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables)) except TemplateError as ex: _LOGGER.error('Error rendering data template: %s', ex) return hass.async_create_task( hass.services.async_call(domain, service_name, service_data, blocking=True))
def _render_template(self, filename, variables): try: template = None with open(filename, encoding="utf-8") as file: template = Template(file.read(), self.hass) template.ensure_valid() rendered = template.render( variables={ **variables, "_global": self.config.get("variables", {}) }, limited=True, ) stream = io.StringIO(rendered) stream.name = filename return stream except (FileNotFoundError, UnicodeDecodeError) as ex: _LOGGER.error("Unable to read file %s: %s", filename, ex) raise HomeAssistantError(ex) from ex except TemplateError as ex: _LOGGER.error("Unable to render file %s: %s", filename, ex) raise HomeAssistantError(ex) from ex
def test_period_parsing(self): """Test the conversion from templates to period.""" today = Template( '{{ now().replace(hour=0).replace(minute=0)' '.replace(second=0) }}', self.hass) duration = timedelta(hours=2, minutes=1) sensor1 = HistoryStatsSensor(self.hass, 'test', 'on', today, None, duration, 'time', 'test') sensor2 = HistoryStatsSensor(self.hass, 'test', 'on', None, today, duration, 'time', 'test') sensor1.update_period() sensor1_start, sensor1_end = sensor1._period sensor2.update_period() sensor2_start, sensor2_end = sensor2._period # Start = 00:00:00 self.assertEqual(sensor1_start.hour, 0) self.assertEqual(sensor1_start.minute, 0) self.assertEqual(sensor1_start.second, 0) # End = 02:01:00 self.assertEqual(sensor1_end.hour, 2) self.assertEqual(sensor1_end.minute, 1) self.assertEqual(sensor1_end.second, 0) # Start = 21:59:00 self.assertEqual(sensor2_start.hour, 21) self.assertEqual(sensor2_start.minute, 59) self.assertEqual(sensor2_start.second, 0) # End = 00:00:00 self.assertEqual(sensor2_end.hour, 0) self.assertEqual(sensor2_end.minute, 0) self.assertEqual(sensor2_end.second, 0)
async def async_setup_platform( hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Web scrape sensor.""" _LOGGER.warning( # Config flow added in Home Assistant Core 2022.7, remove import flow in 2022.9 "Loading Scrape via platform setup has been deprecated in Home Assistant 2022.7 " "Your configuration has been automatically imported and you can " "remove it from your configuration.yaml") if config.get(CONF_VALUE_TEMPLATE): template: Template = Template(config[CONF_VALUE_TEMPLATE]) template.ensure_valid() config[CONF_VALUE_TEMPLATE] = template.template hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config, ))
else: entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, DEFAULT_OBJECT_ID, hass=hass) notification_id = entity_id.split(".")[1] warn = False attr: dict[str, str] = {} if title is not None: if is_template_string(title): warn = True try: title = cast( str, Template(title, hass).async_render( parse_result=False) # type: ignore[no-untyped-call] ) except TemplateError as ex: _LOGGER.error("Error rendering title %s: %s", title, ex) attr[ATTR_TITLE] = title attr[ATTR_FRIENDLY_NAME] = title if is_template_string(message): warn = True try: message = Template(message, hass).async_render( parse_result=False) # type: ignore[no-untyped-call] except TemplateError as ex: _LOGGER.error("Error rendering message %s: %s", message, ex)
async def addBJFsensor(hostname, add_devices, hass): _LOGGER.info("addBJFsensor querying %s", hostname) #config = doQuery(hostname, "/json/config", True) config = await async_doQuery(hostname, "/json/config", True) if config != None: sensorsToAdd = [] mac = config["mac"] # built early, in case it's shared url = "http://" + config["ip"] + "/json/state" rest = BJFRestData(hass,"GET", url, None, None, None) friendlyName = ( config["friendlyName"] if "friendlyName" in config else config["name"] ) # and add a datacoordinator coord = DataUpdateCoordinator(hass,_LOGGER,name=friendlyName+"_DUC", update_method=rest.async_update,update_interval=timedelta(seconds=30)) await coord.async_config_entry_first_refresh() #await hass.async_add_executor_job(coord.async_config_entry_first_refresh) # asyncio.run_coroutine_threadsafe( # coordinator.async_config_entry_first_refresh(), hass.loop # ).result() # add a bunch of rest sensors if "sensorConfig" in config: for eachSensor in config["sensorConfig"]: for element in eachSensor["elements"]: deviceClass = element["type"] potential = None # special case - if there's an instant sensor - PIR generally if "impl" in element: sensorValue = Template( '{{ value_json["sensorState"][' + str(eachSensor["sensor"]) + "].state" + " }}", hass, ) _LOGGER.debug(sensorValue) _LOGGER.info("Potential BJFBinarySensor") potential = BJFBinarySensor( hass, coord, element["impl"], mac, hostname, rest, # entity name friendlyName+" "+eachSensor["name"] + " " + deviceClass, deviceClass, None, sensorValue, eachSensor["sensor"], deviceClass, config, ) else: _LOGGER.info("Potential BJFRestSensor") # uom, round uom = element["uom"] if "uom" in element else None numDP = element["round"] if "round" in element else "0" # build the template string sensorValue = Template( '{{ value_json["sensorState"][' + str(eachSensor["sensor"]) + "]." + deviceClass + " | round(" + numDP + ") }}", hass, ) _LOGGER.debug(sensorValue) potential = BJFRestSensor( hass, coord, mac, hostname, rest, # entity name friendlyName+" "+eachSensor["name"] + " " + deviceClass, deviceClass, uom, sensorValue, eachSensor["sensor"], deviceClass, config, ) if potential is not None: _LOGGER.info("Adding sensor %s", potential._unique_id) sensorsToAdd.append(potential) add_devices(sensorsToAdd) return True else: _LOGGER.error("Failed to query %s", hostname) return False
}, 'input_boolean': { 'ct_switch': { 'name': '启用/暂停', 'initial': False, 'icon': 'mdi:switch', 'use_for': 'switch' } }, 'sensor': [{ 'platform': 'template', 'entity_namespace': "common_timer", 'sensors': { 'ct_record_0': { 'friendly_name': "无定时任务", 'value_template': Template("-"), 'icon_template': Template("mdi:calendar-check") } } }] }, # 'domains': ['light', 'switch', 'automation', 'script', 'input_boolean'], # 'exclude': [], # 'pattern': '[\u4e00-\u9fa5]+', # 'name': 'ct_control_panel', # 'friendly_name': '通用定时器', # 'info_panel': { # 'name': 'ct_info_panel', # 'friendly_name': '定时任务列表', # 'info_num_min': 1, # 'info_num_max': 10,
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
from tests.common import (get_test_home_assistant, assert_setup_component) from homeassistant.bootstrap import setup_component from homeassistant.components.sensor import tcp from homeassistant.helpers.entity import Entity from homeassistant.helpers.template import Template TEST_CONFIG = { 'sensor': { 'platform': 'tcp', tcp.CONF_NAME: 'test_name', tcp.CONF_HOST: 'test_host', tcp.CONF_PORT: 12345, tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, tcp.CONF_PAYLOAD: 'test_payload', tcp.CONF_UNIT_OF_MEASUREMENT: 'test_unit', tcp.CONF_VALUE_TEMPLATE: Template('test_template'), tcp.CONF_VALUE_ON: 'test_on', tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1 }, } KEYS_AND_DEFAULTS = { tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT, tcp.CONF_UNIT_OF_MEASUREMENT: None, tcp.CONF_VALUE_TEMPLATE: None, tcp.CONF_VALUE_ON: None, tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE } class TestTCPSensor(unittest.TestCase):
async def async_play_media(self, media_type: str, media_id: str, extra: dict = None, **kwargs): # backward support Hass lower than v2022.3 if '/api/tts_proxy/' in media_id: session = async_get_clientsession(self.hass) media_id = await utils.get_tts_message(session, media_id) media_type = 'tts' if media_id.startswith("media-source://tts/"): query = utils.decode_media_source(media_id) if query.get("template"): template = Template(query.pop("template"), self.hass) media_id = template.async_render(query) else: media_id = query["message"] if query.get("volume_level"): extra.setdefault("volume_level", float(query["volume_level"])) # provider, music - from 3rd party TTS (ex google) if media_type in ("provider", "music"): media_type = "text" if not media_id: _LOGGER.warning(f"Получено пустое media_id") return # tts for backward compatibility if media_type == "tts": media_type = "text" elif media_type == 'brightness': await self._set_brightness(media_id) return elif media_type == 'beta': await self._set_beta(media_id) return elif media_type == 'settings': await self._set_settings(media_id) return if self.local_state: if 'https://' in media_id or 'http://' in media_id: session = async_get_clientsession(self.hass) payload = await utils.get_media_payload(media_id, session) if not payload: _LOGGER.warning(f"Unsupported url: {media_id}") return elif media_type.startswith(("text:", "dialog:")): payload = { 'command': 'sendText', 'text': self.yandex_dialog(media_type, media_id) } elif media_type == 'text': # даже в локальном режиме делам TTS через облако, чтоб колонка # не продолжала слушать force_local: bool = extra and extra.get("force_local") if self.quasar.session.x_token and not force_local: media_id = utils.fix_cloud_text(media_id) if extra and extra.get("volume_level") is not None: self._check_set_alice_volume(extra["volume_level"]) await self.quasar.send(self.device, media_id, is_tts=True) return else: payload = { 'command': 'sendText', 'text': f"Повтори за мной '{media_id}'" } elif media_type == 'command': payload = {'command': 'sendText', 'text': media_id} elif media_type == 'dialog': if extra and extra.get("volume_level") is not None: self._check_set_alice_volume(extra["volume_level"]) payload = utils.update_form( 'personal_assistant.scenarios.repeat_after_me', request=media_id) elif media_type == 'json': payload = json.loads(media_id) elif RE_MUSIC_ID.match(media_id): payload = { 'command': 'playMusic', 'id': media_id, 'type': media_type } elif media_type == 'shopping_list': await self._shopping_list() return elif media_type.startswith('question'): request_id = (media_type.split(':', 1)[1] if ':' in media_type else None) card = await self.glagol.send({ 'command': 'sendText', 'text': media_id }) await self.response(card, request_id) return else: _LOGGER.warning(f"Unsupported local media: {media_id}") return await self.glagol.send(payload) else: if media_type.startswith(("text:", "dialog:")): media_id = self.yandex_dialog(media_type, media_id) await self.quasar.send(self.device, media_id) elif media_type == 'text': media_id = utils.fix_cloud_text(media_id) await self.quasar.send(self.device, media_id, is_tts=True) elif media_type == 'command': media_id = utils.fix_cloud_text(media_id) await self.quasar.send(self.device, media_id) elif media_type == 'brightness': await self._set_brightness(media_id) return else: _LOGGER.warning(f"Unsupported cloud media: {media_type}") return
from homeassistant.helpers.template import Template from homeassistant.setup import setup_component from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant TEST_CONFIG = { "sensor": { "platform": "tcp", tcp.CONF_NAME: "test_name", tcp.CONF_HOST: "test_host", tcp.CONF_PORT: 12345, tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, tcp.CONF_PAYLOAD: "test_payload", tcp.CONF_UNIT_OF_MEASUREMENT: "test_unit", tcp.CONF_VALUE_TEMPLATE: Template("test_template"), tcp.CONF_VALUE_ON: "test_on", tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1, } } KEYS_AND_DEFAULTS = { tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT, tcp.CONF_UNIT_OF_MEASUREMENT: None, tcp.CONF_VALUE_TEMPLATE: None, tcp.CONF_VALUE_ON: None, tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE, } class TestTCPSensor(unittest.TestCase):
def test_measure_multiple(self): """Test the history statistics sensor measure for multiple states.""" t0 = dt_util.utcnow() - timedelta(minutes=40) t1 = t0 + timedelta(minutes=20) t2 = dt_util.utcnow() - timedelta(minutes=10) # Start t0 t1 t2 End # |--20min--|--20min--|--10min--|--10min--| # |---------|--orange-|-default-|---blue--| fake_states = { "input_select.test_id": [ ha.State("input_select.test_id", "orange", last_changed=t0), ha.State("input_select.test_id", "default", last_changed=t1), ha.State("input_select.test_id", "blue", last_changed=t2), ] } start = Template("{{ as_timestamp(now()) - 3600 }}", self.hass) end = Template("{{ now() }}", self.hass) sensor1 = HistoryStatsSensor( self.hass, "input_select.test_id", ["orange", "blue"], start, end, None, "time", "Test", ) sensor2 = HistoryStatsSensor( self.hass, "unknown.id", ["orange", "blue"], start, end, None, "time", "Test", ) sensor3 = HistoryStatsSensor( self.hass, "input_select.test_id", ["orange", "blue"], start, end, None, "count", "test", ) sensor4 = HistoryStatsSensor( self.hass, "input_select.test_id", ["orange", "blue"], start, end, None, "ratio", "test", ) assert sensor1._type == "time" assert sensor3._type == "count" assert sensor4._type == "ratio" with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, ), patch("homeassistant.components.history.get_state", return_value=None): sensor1.update() sensor2.update() sensor3.update() sensor4.update() assert sensor1.state == 0.5 assert sensor2.state is None assert sensor3.state == 2 assert sensor4.state == 50
"""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(): 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):
import aiohttp import async_timeout import voluptuous as vol from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_NAME, CONF_RESOURCE, CONF_TIMEOUT) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.template import Template CONF_BODY_OFF = 'body_off' CONF_BODY_ON = 'body_on' CONF_IS_ON_TEMPLATE = 'is_on_template' DEFAULT_BODY_OFF = Template('OFF') DEFAULT_BODY_ON = Template('ON') DEFAULT_NAME = 'REST Switch' DEFAULT_TIMEOUT = 10 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_RESOURCE): cv.url, vol.Optional(CONF_BODY_OFF, default=DEFAULT_BODY_OFF): cv.template, vol.Optional(CONF_BODY_ON, default=DEFAULT_BODY_ON): cv.template, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_IS_ON_TEMPLATE): cv.template, vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, }) _LOGGER = logging.getLogger(__name__)
async def test_track_template(hass): """Test tracking template.""" specific_runs = [] wildcard_runs = [] wildercard_runs = [] template_condition = Template("{{states.switch.test.state == 'on'}}", hass) template_condition_var = Template( "{{states.switch.test.state == 'on' and test == 5}}", hass) hass.states.async_set("switch.test", "off") def specific_run_callback(entity_id, old_state, new_state): specific_runs.append(1) async_track_template(hass, template_condition, specific_run_callback) @ha.callback def wildcard_run_callback(entity_id, old_state, new_state): wildcard_runs.append((old_state, new_state)) async_track_template(hass, template_condition, wildcard_run_callback) @asyncio.coroutine def wildercard_run_callback(entity_id, old_state, new_state): wildercard_runs.append((old_state, new_state)) async_track_template(hass, template_condition_var, wildercard_run_callback, {"test": 5}) hass.states.async_set("switch.test", "on") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "on") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "off") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "off") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 hass.states.async_set("switch.test", "on") await hass.async_block_till_done() assert len(specific_runs) == 2 assert len(wildcard_runs) == 2 assert len(wildercard_runs) == 2
async def test_extract_entities(): """Test extracting entities.""" assert condition.async_extract_entities( { "condition": "and", "conditions": [ { "condition": "state", "entity_id": "sensor.temperature", "state": "100", }, { "condition": "numeric_state", "entity_id": "sensor.temperature_2", "below": 110, }, { "condition": "not", "conditions": [ { "condition": "state", "entity_id": "sensor.temperature_3", "state": "100", }, { "condition": "numeric_state", "entity_id": "sensor.temperature_4", "below": 110, }, ], }, { "condition": "or", "conditions": [ { "condition": "state", "entity_id": "sensor.temperature_5", "state": "100", }, { "condition": "numeric_state", "entity_id": "sensor.temperature_6", "below": 110, }, ], }, { "condition": "state", "entity_id": ["sensor.temperature_7", "sensor.temperature_8"], "state": "100", }, { "condition": "numeric_state", "entity_id": ["sensor.temperature_9", "sensor.temperature_10"], "below": 110, }, Template("{{ is_state('light.example', 'on') }}"), ], } ) == { "sensor.temperature", "sensor.temperature_2", "sensor.temperature_3", "sensor.temperature_4", "sensor.temperature_5", "sensor.temperature_6", "sensor.temperature_7", "sensor.temperature_8", "sensor.temperature_9", "sensor.temperature_10", }
def test_track_template(self): """Test tracking template.""" specific_runs = [] wildcard_runs = [] wildercard_runs = [] template_condition = Template( "{{states.switch.test.state == 'on'}}", self.hass ) template_condition_var = Template( "{{states.switch.test.state == 'on' and test == 5}}", self.hass ) self.hass.states.set('switch.test', 'off') def specific_run_callback(entity_id, old_state, new_state): specific_runs.append(1) track_template(self.hass, template_condition, specific_run_callback) @ha.callback def wildcard_run_callback(entity_id, old_state, new_state): wildcard_runs.append((old_state, new_state)) track_template(self.hass, template_condition, wildcard_run_callback) @asyncio.coroutine def wildercard_run_callback(entity_id, old_state, new_state): wildercard_runs.append((old_state, new_state)) track_template( self.hass, template_condition_var, wildercard_run_callback, {'test': 5}) self.hass.states.set('switch.test', 'on') self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) self.hass.states.set('switch.test', 'on') self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) self.hass.states.set('switch.test', 'off') self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) self.hass.states.set('switch.test', 'off') self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) self.hass.states.set('switch.test', 'on') self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) self.assertEqual(2, len(wildcard_runs)) self.assertEqual(2, len(wildercard_runs))