예제 #1
0
 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
예제 #2
0
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)
예제 #3
0
    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)
예제 #4
0
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,
    )
예제 #5
0
 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
예제 #6
0
    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}"
예제 #7
0
    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()↑↑↑↑↑")
예제 #8
0
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