def mold_indicator_startup(event):
            """Add listeners and get 1st state."""
            _LOGGER.debug("Startup for %s", self.entity_id)

            async_track_state_change(self.hass, self._entities,
                                     mold_indicator_sensors_state_listener)

            # Read initial state
            indoor_temp = self.hass.states.get(self._indoor_temp_sensor)
            outdoor_temp = self.hass.states.get(self._outdoor_temp_sensor)
            indoor_hum = self.hass.states.get(self._indoor_humidity_sensor)

            schedule_update = self._update_sensor(self._indoor_temp_sensor,
                                                  None, indoor_temp)

            schedule_update = False if not self._update_sensor(
                self._outdoor_temp_sensor, None, outdoor_temp) else\
                schedule_update

            schedule_update = False if not self._update_sensor(
                self._indoor_humidity_sensor, None, indoor_hum) else\
                schedule_update

            if schedule_update:
                self.async_schedule_update_ha_state(True)
    async def async_added_to_hass(self):
        """Call when entity about to be added."""
        @callback
        def async_threshold_sensor_state_listener(entity, old_state,
                                                  new_state):
            """Handle sensor state changes."""
            if new_state.state == STATE_UNKNOWN:
                return

            entity_obs_list = self.entity_obs[entity]

            for entity_obs in entity_obs_list:
                platform = entity_obs['platform']

                self.watchers[platform](entity_obs)

            prior = self.prior
            for obs in self.current_obs.values():
                prior = update_probability(
                    prior, obs['prob_true'], obs['prob_false'])
            self.probability = prior

            self.hass.async_add_job(self.async_update_ha_state, True)

        async_track_state_change(
            self.hass, self.entity_obs, async_threshold_sensor_state_listener)
Example #3
0
    def run(self):
        """Method called be object after driver is started."""
        state = self._hass.states.get(self._entity_id)
        self.update_temperature(new_state=state)

        async_track_state_change(
            self._hass, self._entity_id, self.update_temperature)
Example #4
0
    def async_added_to_hass(self):
        """Subscribe mqtt events.
        This method must be run in the event loop and returns a coroutine.
        """
        if (self._mqtt):
            async_track_state_change(
                self.hass, self.entity_id, self._async_state_changed_listener
            )

        @callback
        def message_received(topic, payload, qos):
            """Run when new MQTT message has been received."""
            #_LOGGER.warning("[ALARM] MQTT Topic: %s Payload: %s", topic, payload)
            if payload.split(" ")[0] == self._payload_disarm:
                #_LOGGER.warning("Disarming %s", payload)
                #TODO self._hass.states.get('binary_sensor.siren_sensor') #Use this method to relay open states
                if (self._override_code):
                    self.alarm_disarm(self._code)
                else:
                    self.alarm_disarm(payload.split(" ")[1])
            elif payload == self._payload_arm_home:
                self.alarm_arm_home('')
            elif payload == self._payload_arm_away:
                self.alarm_arm_away('')
            elif payload == self._payload_arm_night:
                self.alarm_arm_night('')
            else:
                _LOGGER.warning("[ALARM/MQTT] Received unexpected payload: %s", payload)
                return
        if (self._mqtt):
            return self.mqtt.async_subscribe(
                self.hass, self._command_topic, message_received, self._qos)
    def __init__(self, hass, name, broadlink_device, ircodes_ini, min_temp, max_temp, target_temp, temp_sensor_entity_id, operation_list, fan_list, default_operation, default_fan_mode, default_operation_from_idle):
                 
        """Initialize the Broadlink IR Climate device."""
        self.hass = hass
        self._name = name
        
        self._min_temp = min_temp
        self._max_temp = max_temp
        self._target_temperature = target_temp
        self._target_temperature_step = 1
        self._unit_of_measurement = hass.config.units.temperature_unit
        
        self._current_temperature = 0
        self._temp_sensor_entity_id = temp_sensor_entity_id

        self._current_operation = default_operation
        self._current_fan_mode = default_fan_mode
        
        self._operation_list = operation_list
        self._fan_list = fan_list
        
        self._default_operation_from_idle = default_operation_from_idle
                
        self._broadlink_device = broadlink_device
        self._commands_ini = ircodes_ini
        
        if temp_sensor_entity_id:
            async_track_state_change(
                hass, temp_sensor_entity_id, self._async_temp_sensor_changed)
                
            sensor_state = hass.states.get(temp_sensor_entity_id)    
                
            if sensor_state:
                self._async_update_current_temp(sensor_state)
Example #6
0
    def __init__(self, hass, entity_id, name, lower, upper, hysteresis,
                 device_class):
        """Initialize the Threshold sensor."""
        self._hass = hass
        self._entity_id = entity_id
        self._name = name
        self._threshold_lower = lower
        self._threshold_upper = upper
        self._hysteresis = hysteresis
        self._device_class = device_class

        self._state_position = None
        self._state = False
        self.sensor_value = None

        # pylint: disable=invalid-name
        @callback
        def async_threshold_sensor_state_listener(
                entity, old_state, new_state):
            """Handle sensor state changes."""
            try:
                self.sensor_value = None if new_state.state == STATE_UNKNOWN \
                    else float(new_state.state)
            except (ValueError, TypeError):
                self.sensor_value = None
                _LOGGER.warning("State is not numerical")

            hass.async_add_job(self.async_update_ha_state, True)

        async_track_state_change(
            hass, entity_id, async_threshold_sensor_state_listener)
    def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
                 min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
                 tolerance, keep_alive):
        """Initialize the thermostat."""
        self.hass = hass
        self._name = name
        self.heater_entity_id = heater_entity_id
        self.ac_mode = ac_mode
        self.min_cycle_duration = min_cycle_duration
        self._tolerance = tolerance
        self._keep_alive = keep_alive

        self._active = False
        self._cur_temp = None
        self._min_temp = min_temp
        self._max_temp = max_temp
        self._target_temp = target_temp
        self._unit = hass.config.units.temperature_unit

        async_track_state_change(
            hass, sensor_entity_id, self._async_sensor_changed)
        async_track_state_change(
            hass, heater_entity_id, self._async_switch_changed)

        if self._keep_alive:
            async_track_time_interval(
                hass, self._async_keep_alive, self._keep_alive)

        sensor_state = hass.states.get(sensor_entity_id)
        if sensor_state:
            self._async_update_temp(sensor_state)
Example #8
0
    def __init__(self, hass, entity_id, name, watched_entity_id,
                 state, repeat, skip_first, message_template,
                 done_message_template, notifiers, can_ack):
        """Initialize the alert."""
        self.hass = hass
        self._name = name
        self._alert_state = state
        self._skip_first = skip_first

        self._message_template = message_template
        if self._message_template is not None:
            self._message_template.hass = hass

        self._done_message_template = done_message_template
        if self._done_message_template is not None:
            self._done_message_template.hass = hass

        self._notifiers = notifiers
        self._can_ack = can_ack

        self._delay = [timedelta(minutes=val) for val in repeat]
        self._next_delay = 0

        self._firing = False
        self._ack = False
        self._cancel = None
        self._send_done_message = False
        self.entity_id = ENTITY_ID_FORMAT.format(entity_id)

        event.async_track_state_change(
            hass, watched_entity_id, self.watched_entity_change)
Example #9
0
    def __init__(self, hass, entity_id, name, threshold, limit_type,
                 device_class):
        """Initialize the Threshold sensor."""
        self._hass = hass
        self._entity_id = entity_id
        self.is_upper = limit_type == 'upper'
        self._name = name
        self._threshold = threshold
        self._device_class = device_class
        self._deviation = False
        self.sensor_value = 0

        @callback
        # pylint: disable=invalid-name
        def async_threshold_sensor_state_listener(
                entity, old_state, new_state):
            """Called when the sensor changes state."""
            if new_state.state == STATE_UNKNOWN:
                return

            try:
                self.sensor_value = float(new_state.state)
            except ValueError:
                _LOGGER.error("State is not numerical")

            hass.async_add_job(self.async_update_ha_state, True)

        async_track_state_change(
            hass, entity_id, async_threshold_sensor_state_listener)
    def async_added_to_hass(self):
        """Subscribe mqtt events.

        This method must be run in the event loop and returns a coroutine.
        """
        async_track_state_change(
            self.hass, self.entity_id, self._async_state_changed_listener
        )

        @callback
        def message_received(topic, payload, qos):
            """Run when new MQTT message has been received."""
            if payload == self._payload_disarm:
                self.async_alarm_disarm(self._code)
            elif payload == self._payload_arm_home:
                self.async_alarm_arm_home(self._code)
            elif payload == self._payload_arm_away:
                self.async_alarm_arm_away(self._code)
            elif payload == self._payload_arm_night:
                self.async_alarm_arm_night(self._code)
            else:
                _LOGGER.warning("Received unexpected payload: %s", payload)
                return

        return mqtt.async_subscribe(
            self.hass, self._command_topic, message_received, self._qos)
Example #11
0
 def template_lock_startup(event):
     """Update template on startup."""
     if self._state_entities != MATCH_ALL:
         # Track state change only for valid templates
         async_track_state_change(
             self._hass, self._state_entities,
             template_lock_state_listener)
     self.async_schedule_update_ha_state(True)
Example #12
0
        def template_bsensor_startup(event):
            """Update template on startup."""
            if self._entities != MATCH_ALL:
                # Track state change only for valid templates
                async_track_state_change(
                    self.hass, self._entities, template_bsensor_state_listener)

            self.async_check_state()
Example #13
0
        def template_light_startup(event):
            """Update template on startup."""
            if (self._template is not None or
                    self._level_template is not None):
                async_track_state_change(
                    self.hass, self._entities, template_light_state_listener)

            self.async_schedule_update_ha_state(True)
Example #14
0
 async def async_added_to_hass(self):
     """Register listeners."""
     for entity_id in self._entities:
         new_state = self.hass.states.get(entity_id)
         self.update_supported_features(entity_id, None, new_state,
                                        update_state=False)
     async_track_state_change(self.hass, self._entities,
                              self.update_supported_features)
     await self.async_update()
Example #15
0
    async def run(self):
        """Method called by accessory after driver is started.

        Run inside the HAP-python event loop.
        """
        state = self.hass.states.get(self.entity_id)
        self.hass.add_job(self.update_state_callback, None, None, state)
        async_track_state_change(
            self.hass, self.entity_id, self.update_state_callback)
        def start_refresh(*args):
            """Register state tracking."""
            @callback
            def force_refresh(*args):
                """Force the component to refresh."""
                self.async_schedule_update_ha_state(True)

            force_refresh()
            async_track_state_change(self.hass, self._entity_id, force_refresh)
    async def run(self):
        """Handle accessory driver started event.

        Run inside the HAP-python event loop.
        """
        state = self.hass.states.get(self.entity_id)
        self.hass.add_job(self.update_state_callback, None, None, state)
        async_track_state_change(
            self.hass, self.entity_id, self.update_state_callback)
Example #18
0
 def async_register(self):
     """Register listener."""
     from xknx.devices import ExposeSensor
     self.device = ExposeSensor(
         self.xknx,
         name=self.entity_id,
         group_address=self.address,
         value_type=self.type)
     self.xknx.devices.add(self.device)
     async_track_state_change(
         self.hass, self.entity_id, self._async_entity_changed)
Example #19
0
    def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
                 min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
                 cold_tolerance, hot_tolerance, keep_alive,
                 initial_operation_mode, away_temp, precision):
        """Initialize the thermostat."""
        self.hass = hass
        self._name = name
        self.heater_entity_id = heater_entity_id
        self.ac_mode = ac_mode
        self.min_cycle_duration = min_cycle_duration
        self._cold_tolerance = cold_tolerance
        self._hot_tolerance = hot_tolerance
        self._keep_alive = keep_alive
        self._initial_operation_mode = initial_operation_mode
        self._saved_target_temp = target_temp if target_temp is not None \
            else away_temp
        self._temp_precision = precision
        if self.ac_mode:
            self._current_operation = STATE_COOL
            self._operation_list = [STATE_COOL, STATE_OFF]
        else:
            self._current_operation = STATE_HEAT
            self._operation_list = [STATE_HEAT, STATE_OFF]
        if initial_operation_mode == STATE_OFF:
            self._enabled = False
            self._current_operation = STATE_OFF
        else:
            self._enabled = True
        self._active = False
        self._cur_temp = None
        self._temp_lock = asyncio.Lock()
        self._min_temp = min_temp
        self._max_temp = max_temp
        self._target_temp = target_temp
        self._unit = hass.config.units.temperature_unit
        self._support_flags = SUPPORT_FLAGS
        if away_temp is not None:
            self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
        self._away_temp = away_temp
        self._is_away = False

        async_track_state_change(
            hass, sensor_entity_id, self._async_sensor_changed)
        async_track_state_change(
            hass, heater_entity_id, self._async_switch_changed)

        if self._keep_alive:
            async_track_time_interval(
                hass, self._async_control_heating, self._keep_alive)

        sensor_state = hass.states.get(sensor_entity_id)
        if sensor_state and sensor_state.state != STATE_UNKNOWN:
            self._async_update_temp(sensor_state)
Example #20
0
        def async_stats_sensor_startup(event):
            """Add listener and get recorded state."""
            _LOGGER.debug("Startup for %s", self.entity_id)

            async_track_state_change(
                self.hass, self._entity_id, async_stats_sensor_state_listener)

            if 'recorder' in self.hass.config.components:
                # only use the database if it's configured
                self.hass.async_create_task(
                    self._async_initialize_from_database()
                )
Example #21
0
    async def async_added_to_hass(self):
        """Handle entity which will be added."""
        await super().async_added_to_hass()
        state = await self.async_get_last_state()
        if state:
            try:
                self._state = Decimal(state.state)
            except ValueError as err:
                _LOGGER.warning("Could not restore last state: %s", err)

        @callback
        def calc_integration(entity, old_state, new_state):
            """Handle the sensor state changes."""
            if old_state is None or\
                    old_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE] or\
                    new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
                return

            if self._unit_of_measurement is None:
                unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
                self._unit_of_measurement = self._unit_template.format(
                    "" if unit is None else unit)

            try:
                # integration as the Riemann integral of previous measures.
                area = 0
                elapsed_time = (new_state.last_updated
                                - old_state.last_updated).total_seconds()

                if self._method == TRAPEZOIDAL_METHOD:
                    area = (Decimal(new_state.state)
                            + Decimal(old_state.state))*Decimal(elapsed_time)/2
                elif self._method == LEFT_METHOD:
                    area = Decimal(old_state.state)*Decimal(elapsed_time)
                elif self._method == RIGHT_METHOD:
                    area = Decimal(new_state.state)*Decimal(elapsed_time)

                integral = area / (self._unit_prefix * self._unit_time)
                assert isinstance(integral, Decimal)
            except ValueError as err:
                _LOGGER.warning("While calculating integration: %s", err)
            except DecimalException as err:
                _LOGGER.warning("Invalid state (%s > %s): %s",
                                old_state.state, new_state.state, err)
            except AssertionError as err:
                _LOGGER.error("Could not calculate integral: %s", err)
            else:
                self._state += integral
                self.async_schedule_update_ha_state()

        async_track_state_change(
            self.hass, self._sensor_source_id, calc_integration)
Example #22
0
        def async_source_tracking(event):
            """Wait for source to be ready, then start meter."""
            if self._tariff_entity is not None:
                _LOGGER.debug("track %s", self._tariff_entity)
                async_track_state_change(self.hass, self._tariff_entity,
                                         self.async_tariff_change)

                tariff_entity_state = self.hass.states.get(self._tariff_entity)
                if self._tariff != tariff_entity_state.state:
                    return

            self._collecting = async_track_state_change(
                self.hass, self._sensor_source_id, self.async_reading)
Example #23
0
    async def async_added_to_hass(self):
        """After being added to hass, load from history."""
        if ENABLE_LOAD_HISTORY and 'recorder' in self.hass.config.components:
            # only use the database if it's configured
            self.hass.async_add_job(self._load_history_from_db)

        async_track_state_change(
            self.hass, list(self._sensormap), self.state_changed)

        for entity_id in self._sensormap:
            state = self.hass.states.get(entity_id)
            if state is not None:
                self.state_changed(entity_id, None, state)
def async_setup(hass, config):
    """Set up the MQTT state feed."""
    conf = config.get(DOMAIN, {})
    base_topic = conf.get(CONF_BASE_TOPIC)
    pub_include = conf.get(CONF_INCLUDE, {})
    pub_exclude = conf.get(CONF_EXCLUDE, {})
    publish_attributes = conf.get(CONF_PUBLISH_ATTRIBUTES)
    publish_timestamps = conf.get(CONF_PUBLISH_TIMESTAMPS)
    publish_filter = generate_filter(pub_include.get(CONF_DOMAINS, []),
                                     pub_include.get(CONF_ENTITIES, []),
                                     pub_exclude.get(CONF_DOMAINS, []),
                                     pub_exclude.get(CONF_ENTITIES, []))
    if not base_topic.endswith('/'):
        base_topic = base_topic + '/'

    @callback
    def _state_publisher(entity_id, old_state, new_state):
        if new_state is None:
            return

        if not publish_filter(entity_id):
            return

        payload = new_state.state

        mybase = base_topic + entity_id.replace('.', '/') + '/'
        hass.components.mqtt.async_publish(mybase + 'state', payload, 1, True)

        if publish_timestamps:
            if new_state.last_updated:
                hass.components.mqtt.async_publish(
                    mybase + 'last_updated',
                    new_state.last_updated.isoformat(),
                    1,
                    True)
            if new_state.last_changed:
                hass.components.mqtt.async_publish(
                    mybase + 'last_changed',
                    new_state.last_changed.isoformat(),
                    1,
                    True)

        if publish_attributes:
            for key, val in new_state.attributes.items():
                if val:
                    encoded_val = json.dumps(val, cls=JSONEncoder)
                    hass.components.mqtt.async_publish(mybase + key,
                                                       encoded_val, 1, True)

    async_track_state_change(hass, MATCH_ALL, _state_publisher)
    return True
Example #25
0
    def __init__(self, hass, entity_ids, name, sensor_type, round_digits):
        """Initialize the min/max sensor."""
        self._hass = hass
        self._entity_ids = entity_ids
        self._sensor_type = sensor_type
        self._round_digits = round_digits

        if name:
            self._name = name
        else:
            self._name = '{} sensor'.format(
                next(v for k, v in SENSOR_TYPES.items()
                     if self._sensor_type == v)).capitalize()
        self._unit_of_measurement = None
        self._unit_of_measurement_mismatch = False
        self.min_value = self.max_value = self.mean = self.last = None
        self.count_sensors = len(self._entity_ids)
        self.states = {}

        @callback
        def async_min_max_sensor_state_listener(entity, old_state, new_state):
            """Handle the sensor state changes."""
            if (new_state.state is None
                    or new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]):
                self.states[entity] = STATE_UNKNOWN
                hass.async_add_job(self.async_update_ha_state, True)
                return

            if self._unit_of_measurement is None:
                self._unit_of_measurement = new_state.attributes.get(
                    ATTR_UNIT_OF_MEASUREMENT)

            if self._unit_of_measurement != new_state.attributes.get(
                    ATTR_UNIT_OF_MEASUREMENT):
                _LOGGER.warning(
                    "Units of measurement do not match for entity %s",
                    self.entity_id)
                self._unit_of_measurement_mismatch = True

            try:
                self.states[entity] = float(new_state.state)
                self.last = float(new_state.state)
            except ValueError:
                _LOGGER.warning("Unable to store state. "
                                "Only numerical states are supported")

            hass.async_add_job(self.async_update_ha_state, True)

        async_track_state_change(
            hass, entity_ids, async_min_max_sensor_state_listener)
Example #26
0
def async_trigger(hass, config, action):
    """Listen for state changes based on configuration."""
    value_template = config.get(CONF_VALUE_TEMPLATE)
    value_template.hass = hass

    # Local variable to keep track of if the action has already been triggered
    already_triggered = False

    @asyncio.coroutine
    def state_changed_listener(entity_id, from_s, to_s):
        """Listen for state changes and calls action."""
        nonlocal already_triggered
        template_result = condition.async_template(hass, value_template)

        # Check to see if template returns true
        if template_result and not already_triggered:
            already_triggered = True
            hass.async_add_job(action, {
                'trigger': {
                    'platform': 'template',
                    'entity_id': entity_id,
                    'from_state': from_s,
                    'to_state': to_s,
                },
            })
        elif not template_result:
            already_triggered = False

    return async_track_state_change(hass, value_template.extract_entities(),
                                    state_changed_listener)
Example #27
0
async def async_setup(hass, config):
    """Set up the Plant component."""
    component = EntityComponent(_LOGGER, DOMAIN, hass,
                                group_name=GROUP_NAME_ALL_PLANTS)

    entities = []
    for plant_name, plant_config in config[DOMAIN].items():
        _LOGGER.info("Added plant %s", plant_name)
        entity = Plant(plant_name, plant_config)
        sensor_entity_ids = list(plant_config[CONF_SENSORS].values())
        _LOGGER.debug("Subscribing to entity_ids %s", sensor_entity_ids)
        async_track_state_change(hass, sensor_entity_ids, entity.state_changed)
        entities.append(entity)

    await component.async_add_entities(entities)
    return True
Example #28
0
 def async_on_state_subscription(entity_id: str) -> None:
     """Subscribe and forward states for requested entities."""
     unsub = async_track_state_change(
         hass, entity_id, send_home_assistant_state)
     entry_data.disconnect_callbacks.append(unsub)
     # Send initial state
     hass.async_create_task(send_home_assistant_state(
         entity_id, None, hass.states.get(entity_id)))
Example #29
0
    def async_start(self):
        """Start tracking members.

        This method must be run in the event loop.
        """
        self._async_unsub_state_changed = async_track_state_change(
            self.hass, self.tracking, self._state_changed_listener
        )
Example #30
0
    def __init__(self, hass, device, friendly_name, sensor_class,
                 value_template, entity_ids):
        """Initialize the Template binary sensor."""
        self.hass = hass
        self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
                                                  hass=hass)
        self._name = friendly_name
        self._sensor_class = sensor_class
        self._template = value_template
        self._state = None

        @callback
        def template_bsensor_state_listener(entity, old_state, new_state):
            """Called when the target device changes state."""
            hass.async_add_job(self.async_update_ha_state, True)

        async_track_state_change(
            hass, entity_ids, template_bsensor_state_listener)
def async_trigger(hass, config, action):
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    below = config.get(CONF_BELOW)
    above = config.get(CONF_ABOVE)
    time_delta = config.get(CONF_FOR)
    value_template = config.get(CONF_VALUE_TEMPLATE)
    async_remove_track_same = None

    if value_template is not None:
        value_template.hass = hass

    @callback
    def check_numeric_state(entity, from_s, to_s):
        """Return True if they should trigger."""
        if to_s is None:
            return False

        variables = {
            'trigger': {
                'platform': 'numeric_state',
                'entity_id': entity,
                'below': below,
                'above': above,
            }
        }

        # If new one doesn't match, nothing to do
        if not condition.async_numeric_state(
                hass, to_s, below, above, value_template, variables):
            return False

        return True

    @callback
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        nonlocal async_remove_track_same

        if not check_numeric_state(entity, from_s, to_s):
            return

        variables = {
            'trigger': {
                'platform': 'numeric_state',
                'entity_id': entity,
                'below': below,
                'above': above,
                'from_state': from_s,
                'to_state': to_s,
            }
        }

        # Only match if old didn't exist or existed but didn't match
        # Written as: skip if old one did exist and matched
        if from_s is not None and condition.async_numeric_state(
                hass, from_s, below, above, value_template, variables):
            return

        @callback
        def call_action():
            """Call action with right context."""
            hass.async_run_job(action, variables)

        if not time_delta:
            call_action()
            return

        async_remove_track_same = async_track_same_state(
            hass, True, time_delta, call_action, entity_ids=entity_id,
            async_check_func=check_numeric_state)

    unsub = async_track_state_change(
        hass, entity_id, state_automation_listener)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        if async_remove_track_same:
            async_remove_track_same()  # pylint: disable=not-callable

    return async_remove
Example #32
0
async def test_track_state_change_from_to_state_match(hass):
    """Test track_state_change with from and to state matchers."""
    from_and_to_state_runs = []
    only_from_runs = []
    only_to_runs = []
    match_all_runs = []
    no_to_from_specified_runs = []

    def from_and_to_state_callback(entity_id, old_state, new_state):
        from_and_to_state_runs.append(1)

    def only_from_state_callback(entity_id, old_state, new_state):
        only_from_runs.append(1)

    def only_to_state_callback(entity_id, old_state, new_state):
        only_to_runs.append(1)

    def match_all_callback(entity_id, old_state, new_state):
        match_all_runs.append(1)

    def no_to_from_specified_callback(entity_id, old_state, new_state):
        no_to_from_specified_runs.append(1)

    async_track_state_change(hass, "light.Bowl", from_and_to_state_callback,
                             "on", "off")
    async_track_state_change(hass, "light.Bowl", only_from_state_callback,
                             "on", None)
    async_track_state_change(hass, "light.Bowl", only_to_state_callback, None,
                             ["off", "standby"])
    async_track_state_change(hass, "light.Bowl", match_all_callback, MATCH_ALL,
                             MATCH_ALL)
    async_track_state_change(hass, "light.Bowl", no_to_from_specified_callback)

    hass.states.async_set("light.Bowl", "on")
    await hass.async_block_till_done()
    assert len(from_and_to_state_runs) == 0
    assert len(only_from_runs) == 0
    assert len(only_to_runs) == 0
    assert len(match_all_runs) == 1
    assert len(no_to_from_specified_runs) == 1

    hass.states.async_set("light.Bowl", "off")
    await hass.async_block_till_done()
    assert len(from_and_to_state_runs) == 1
    assert len(only_from_runs) == 1
    assert len(only_to_runs) == 1
    assert len(match_all_runs) == 2
    assert len(no_to_from_specified_runs) == 2

    hass.states.async_set("light.Bowl", "on")
    await hass.async_block_till_done()
    assert len(from_and_to_state_runs) == 1
    assert len(only_from_runs) == 1
    assert len(only_to_runs) == 1
    assert len(match_all_runs) == 3
    assert len(no_to_from_specified_runs) == 3

    hass.states.async_set("light.Bowl", "on")
    await hass.async_block_till_done()
    assert len(from_and_to_state_runs) == 1
    assert len(only_from_runs) == 1
    assert len(only_to_runs) == 1
    assert len(match_all_runs) == 3
    assert len(no_to_from_specified_runs) == 3

    hass.states.async_set("light.Bowl", "off")
    await hass.async_block_till_done()
    assert len(from_and_to_state_runs) == 2
    assert len(only_from_runs) == 2
    assert len(only_to_runs) == 2
    assert len(match_all_runs) == 4
    assert len(no_to_from_specified_runs) == 4

    hass.states.async_set("light.Bowl", "off")
    await hass.async_block_till_done()
    assert len(from_and_to_state_runs) == 2
    assert len(only_from_runs) == 2
    assert len(only_to_runs) == 2
    assert len(match_all_runs) == 4
    assert len(no_to_from_specified_runs) == 4
Example #33
0
async def async_trigger(hass, config, action, automation_info):
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    below = config.get(CONF_BELOW)
    above = config.get(CONF_ABOVE)
    time_delta = config.get(CONF_FOR)
    value_template = config.get(CONF_VALUE_TEMPLATE)
    unsub_track_same = {}
    entities_triggered = set()

    if value_template is not None:
        value_template.hass = hass

    @callback
    def check_numeric_state(entity, from_s, to_s):
        """Return True if criteria are now met."""
        if to_s is None:
            return False

        variables = {
            'trigger': {
                'platform': 'numeric_state',
                'entity_id': entity,
                'below': below,
                'above': above,
            }
        }
        return condition.async_numeric_state(hass, to_s, below, above,
                                             value_template, variables)

    @callback
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        @callback
        def call_action():
            """Call action with right context."""
            hass.async_run_job(
                action(
                    {
                        'trigger': {
                            'platform': 'numeric_state',
                            'entity_id': entity,
                            'below': below,
                            'above': above,
                            'from_state': from_s,
                            'to_state': to_s,
                        }
                    },
                    context=to_s.context))

        matching = check_numeric_state(entity, from_s, to_s)

        if not matching:
            entities_triggered.discard(entity)
        elif entity not in entities_triggered:
            entities_triggered.add(entity)

            if time_delta:
                unsub_track_same[entity] = async_track_same_state(
                    hass,
                    time_delta,
                    call_action,
                    entity_ids=entity,
                    async_check_same_func=check_numeric_state)
            else:
                call_action()

    unsub = async_track_state_change(hass, entity_id,
                                     state_automation_listener)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        for async_remove in unsub_track_same.values():
            async_remove()
        unsub_track_same.clear()

    return async_remove
def setup(hass, config):
    """Set up the MQTT state feed."""
    conf = config.get(DOMAIN, {})
    base_topic = conf.get(CONF_BASE_TOPIC)
    entity_include = conf.get(CONF_INCLUDE, {})
    entity_exclude = conf.get(CONF_EXCLUDE, {})
    entity_filter = generate_filter(entity_include.get(CONF_DOMAINS, []),
                                    entity_include.get(CONF_ENTITIES, []),
                                    entity_exclude.get(CONF_DOMAINS, []),
                                    entity_exclude.get(CONF_ENTITIES, []))

    if not base_topic.endswith('/'):
        base_topic = base_topic + '/'
    
    def _publish_all_states(*_):
        states = hass.states.all()
        for state in states:
            _state_publisher(state.entity_id, None, state)

    def _handle_hass_status(topic, payload, qos):
        if payload == 'online':
            async_call_later(hass, 20, _publish_all_states)

    def _state_publisher(entity_id, old_state, new_state):
        if new_state is None:
            return

        if not entity_filter(entity_id):
            return

        entity_id_parts = entity_id.split('.')
        domain = entity_id_parts[0]
        entity_state = hass.states.get(entity_id)

        mybase = base_topic + entity_id.replace('.', '/') + '/state'

        if domain == 'switch' or domain == 'binary_sensor':
            data = {}
            if new_state.state == 'on':
                data['state'] = 'ON'
            elif new_state.state == 'off':
                data['state'] = 'OFF'

            payload = json.dumps(data, cls=JSONEncoder)
            hass.components.mqtt.async_publish(mybase, payload, 1, False)
        elif domain == 'light':
            data = {}
            if new_state.state == 'on':
                data['state'] = 'ON'
            elif new_state.state == 'off':
                data['state'] = 'OFF'
            try:
                data['brightness'] = new_state.attributes['brightness']
            except KeyError:
                pass

            payload = json.dumps(data, cls=JSONEncoder)
            hass.components.mqtt.async_publish(mybase, payload, 1, False)
        elif domain == 'lock':
            data = {}
            if new_state.state == 'locked':
                data['state'] = 'LOCK'
            elif new_state.state == 'unlocked':
                data['state'] = 'UNLOCK'
            
            try:
                data['notification'] = entity_state.attributes['notification']
            except KeyError:
                pass
            try:
                data['lock_status'] = entity_state.attributes['lock_status']
            except KeyError:
                pass

            payload = json.dumps(data, cls=JSONEncoder)
            hass.components.mqtt.async_publish(mybase, payload, 1, False)
        elif domain == 'sensor':
            data = {}
            data['state'] = new_state.state

            payload = json.dumps(data, cls=JSONEncoder)
            hass.components.mqtt.async_publish(mybase, payload, 1, False)

    def _state_message_received(topic, payload, qos):
        """Handle new MQTT state messages."""
        # Parse entity from topic
        topic_parts = topic.split('/')
        domain = topic_parts[-3]
        entity = topic_parts[-2]

        entity_id = "{0}.{1}".format(domain, entity)
        if not entity_filter(entity_id):
            return

        
        data = {ATTR_ENTITY_ID: entity_id}
        if domain == 'switch':
            if payload == mqtt_switch.DEFAULT_PAYLOAD_ON:
                hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_ON, data))
            if payload == mqtt_switch.DEFAULT_PAYLOAD_OFF:
                hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_OFF, data))
        elif domain == 'light':
            payload = json.loads(payload)
            if payload['state'] == 'ON':
                try:
                    data['brightness'] = payload['brightness']
                except KeyError:
                    pass
                hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_ON, data))
            if payload['state'] == 'OFF':
                hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_OFF, data))
        elif domain == 'lock':
            if payload == mqtt_lock.DEFAULT_PAYLOAD_LOCK:
                hass.async_add_job(hass.services.async_call(domain, SERVICE_LOCK, data))
            if payload == mqtt_lock.DEFAULT_PAYLOAD_UNLOCK:
                hass.async_add_job(hass.services.async_call(domain, SERVICE_UNLOCK, data))
    
    async_track_state_change(hass, MATCH_ALL, _state_publisher)
    mqtt.subscribe(hass, base_topic+'+/+/set', _state_message_received)
    mqtt.subscribe(hass, 'hass/status', _handle_hass_status)
    return True
Example #35
0
    async def async_added_to_hass(self):
        """Run when entity about to be added."""
        await super().async_added_to_hass()

        # Add listener
        async_track_state_change(self.hass, self.sensor_entity_id,
                                 self._async_sensor_changed)
        if self.heater_entity_id is not None:
            async_track_state_change(self.hass, self.heater_entity_id,
                                     self._async_switch_changed)
        if self.cooler_entity_id is not None:
            async_track_state_change(self.hass, self.cooler_entity_id,
                                     self._async_switch_changed)
        if self.fan_entity_id is not None:
            async_track_state_change(self.hass, self.fan_entity_id,
                                     self._async_switch_changed)
        if self.dryer_entity_id is not None:
            async_track_state_change(self.hass, self.dryer_entity_id,
                                     self._async_switch_changed)

        if self._keep_alive:
            async_track_time_interval(self.hass, self._async_control_heating,
                                      self._keep_alive)

        @callback
        def _async_startup(event):
            """Init on startup."""
            sensor_state = self.hass.states.get(self.sensor_entity_id)
            if sensor_state and sensor_state.state not in (
                    STATE_UNAVAILABLE,
                    STATE_UNKNOWN,
            ):
                self._async_update_temp(sensor_state)

        self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START,
                                        _async_startup)

        # Check If we have an old state
        old_state = await self.async_get_last_state()
        if old_state is not None:
            # If we have no initial temperature, restore
            if self._target_temp is None:
                # If we have a previously saved temperature
                if old_state.attributes.get(ATTR_TEMPERATURE) is None:
                    if self._hvac_mode == HVAC_MODE_COOL:
                        self._target_temp = self.max_temp
                    if self._hvac_mode == HVAC_MODE_FAN_ONLY:
                        self._target_temp = self.max_temp
                    if self._hvac_mode == HVAC_MODE_HEAT:
                        self._target_temp = self.min_temp
                    if self._hvac_mode == HVAC_MODE_DRY:
                        self._target_temp = self._min_temp
                    _LOGGER.warning(
                        "Undefined target temperature,"
                        "falling back to %s",
                        self._target_temp,
                    )
                else:
                    self._target_temp = float(
                        old_state.attributes[ATTR_TEMPERATURE])
            if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY:
                self._is_away = True
            if not self._hvac_mode and old_state.state:
                self._hvac_mode = old_state.state

        else:
            # No previous state, try and restore defaults
            if self._target_temp is None:
                if self._hvac_mode == HVAC_MODE_COOL:
                    self._target_temp = self.max_temp
                if self._hvac_mode == HVAC_MODE_FAN_ONLY:
                    self._target_temp = self.max_temp
                if self._hvac_mode == HVAC_MODE_HEAT:
                    self._target_temp = self.min_temp
                if self._hvac_mode == HVAC_MODE_DRY:
                    self._target_temp = self.min_temp
            _LOGGER.warning("No previously saved temperature, setting to %s",
                            self._target_temp)

        # Set default state to off
        if not self._hvac_mode:
            self._hvac_mode = HVAC_MODE_OFF
Example #36
0
    def __init__(self, hass, area):
        """Initialize the area presence binary sensor."""

        self.area = area
        self.hass = hass
        self._name = f"Area ({self.area.name})"
        self._state = None
        self.last_off_time = datetime.utcnow()

        self._passive = self.area.passive_start  # Prevent acting until all is loaded

        _LOGGER.info(f"Area {self.area.slug} presence sensor initializing.")

        # Fetch presence sensors
        self.presence_sensors = []
        for component, entities in self.area.entities.items():

            if component not in PRESENCE_DEVICE_COMPONENTS:
                continue

            for entity in entities:

                if (component == BINARY_SENSOR_DOMAIN and entity.device_class
                        not in self.area.presence_device_class):
                    continue

                self.presence_sensors.append(entity.entity_id)

        # Append presence_hold switch as a presence_sensor
        presence_hold_switch_id = f"{SWITCH_DOMAIN}.area_presence_hold_{self.area.slug}"
        self.presence_sensors.append(presence_hold_switch_id)

        area_lights = [
            entity.entity_id for entity in self.area.entities[LIGHT_DOMAIN]
        ] if LIGHT_DOMAIN in self.area.entities.keys() else []
        area_climate = [
            entity.entity_id for entity in self.area.entities[CLIMATE_DOMAIN]
        ] if CLIMATE_DOMAIN in self.area.entities.keys() else []

        # Set attributes
        self._attributes = {
            'presence_sensors': self.presence_sensors,
            'active_sensors': [],
            'lights': area_lights,
            'climate': area_climate,
            'clear_timeout': self.area.clear_timeout,
            'update_interval': self.area.update_interval,
            'on_states': self.area.on_states,
            'exterior': self.area.exterior
        }

        # Track presence sensors
        async_track_state_change(hass, self.presence_sensors,
                                 self.sensor_state_change)

        # Track autolight_disable sensor if available
        if self.area.automatic_lights[CONF_AL_DISABLE_ENTITY]:
            async_track_state_change(
                hass, self.area.automatic_lights[CONF_AL_DISABLE_ENTITY],
                self.autolight_disable_state_change)

        # Timed self update
        delta = timedelta(seconds=self.area.update_interval)
        async_track_time_interval(self.hass, self.update_area, delta)

        _LOGGER.info(f"Area {self.area.slug} presence sensor initialized.")
Example #37
0
 async def async_added_to_hass(self):
     async_track_state_change(self.hass, self._sensor, self.state_changed)
        def sensor_startup(event):  # pylint: disable=w0613
            """Update template on startup."""
            async_track_state_change(self._hass, [self._weather_entity],
                                     sensor_state_listener)

            self.async_schedule_update_ha_state(True)
Example #39
0
    async def async_added_to_hass(self):
        """Run when entity about to be added."""
        await super().async_added_to_hass()

        # Add listener
        await self._subscribe_topics()

        # Check If we have an old state
        old_state = await self.async_get_last_state()
        if old_state is not None:
            # If we have no initial temperature, restore
            if old_state.attributes.get(ATTR_TEMPERATURE) is not None:
                self._target_temp = float(
                    old_state.attributes[ATTR_TEMPERATURE])
            if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY:
                self._is_away = True
            if old_state.attributes.get(ATTR_FAN_MODE) is not None:
                self._fan_mode = old_state.attributes.get(ATTR_FAN_MODE)
            if old_state.attributes.get(ATTR_SWING_MODE) is not None:
                self._swing_mode = old_state.attributes.get(ATTR_SWING_MODE)
            for attr in ATTRIBUTES_IRHVAC:
                val = old_state.attributes.get(attr)
                if val is not None:
                    setattr(self, "_" + attr, val)
            if old_state.state:
                self._hvac_mode = old_state.state
                self._enabled = self._hvac_mode != STATE_OFF
                if self._enabled:
                    self._last_on_mode = self._hvac_mode
        else:
            # No previous state, try and restore defaults
            if self._target_temp is None:
                self._target_temp = self._def_target_temp
            _LOGGER.warning(
                "No previously saved temperature, setting to %s", self._target_temp
            )
        # Set initial state
        if self._hvac_mode is STATE_UNKNOWN:
            self._hvac_mode = self._init_hvac_mode

        if self._hvac_mode is HVAC_MODE_OFF:
            self.power_mode = STATE_OFF
            self._enabled = False
        else:
            self.power_mode = STATE_ON
            self._enabled = True

        if self._temp_sensor:
            async_track_state_change(self.hass, self._temp_sensor, self._async_sensor_changed)

            temp_sensor_state = self.hass.states.get(self._temp_sensor)
            if temp_sensor_state and temp_sensor_state.state != STATE_UNKNOWN and temp_sensor_state.state != STATE_UNAVAILABLE:
                self._async_update_temp(temp_sensor_state)


        if self._humidity_sensor:
            async_track_state_change(self.hass, self._humidity_sensor, self._async_humidity_sensor_changed)

            humidity_sensor_state = self.hass.states.get(self._humidity_sensor)
            if humidity_sensor_state and humidity_sensor_state.state != STATE_UNKNOWN and humidity_sensor_state.state != STATE_UNAVAILABLE:
                self._async_update_humidity(humidity_sensor_state)

        if self._power_sensor:
            async_track_state_change(self.hass, self._power_sensor, self._async_power_sensor_changed)
Example #40
0
async def update_listener(hass: HomeAssistant,
                          config_entry: ConfigEntry) -> None:
    """Update listener."""
    # Get current code slots and new code slots, and remove entities for current code
    # slots that are being removed
    curr_slots = range(config_entry.data[CONF_START],
                       config_entry.data[CONF_SLOTS] + 1)
    new_slots = range(config_entry.options[CONF_START],
                      config_entry.options[CONF_SLOTS] + 1)

    await remove_generated_entities(hass, config_entry,
                                    list(set(curr_slots) - set(new_slots)),
                                    False)

    # If the path has changed delete the old base folder, otherwise if the lock name
    # has changed only delete the old lock folder
    if config_entry.options[CONF_PATH] != config_entry.data[CONF_PATH]:
        await hass.async_add_executor_job(delete_folder, hass.config.path(),
                                          config_entry.data[CONF_PATH])
    elif config_entry.options[CONF_LOCK_NAME] != config_entry.data[
            CONF_LOCK_NAME]:
        await hass.async_add_executor_job(
            delete_folder,
            hass.config.path(),
            config_entry.data[CONF_PATH],
            config_entry.data[CONF_LOCK_NAME],
        )

    new_data = config_entry.options.copy()
    new_data.pop(CONF_GENERATE, None)

    hass.config_entries.async_update_entry(
        entry=config_entry,
        unique_id=config_entry.options[CONF_LOCK_NAME],
        data=new_data,
    )

    primary_lock = KeymasterLock(
        config_entry.data[CONF_LOCK_NAME],
        config_entry.data[CONF_LOCK_ENTITY_ID],
        config_entry.data[CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID],
        config_entry.data[CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID],
        config_entry.data[CONF_SENSOR_NAME],
    )
    hass.data[DOMAIN][config_entry.entry_id].update({
        PRIMARY_LOCK:
        primary_lock,
        CHILD_LOCKS: [
            KeymasterLock(
                lock_name,
                lock[CONF_LOCK_ENTITY_ID],
                lock[CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID],
                lock[CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID],
            ) for lock_name, lock in config_entry.data.get(
                CONF_CHILD_LOCKS, {}).items()
        ],
    })
    servicedata = {"lockname": primary_lock.lock_name}
    await hass.services.async_call(DOMAIN, SERVICE_GENERATE_PACKAGE,
                                   servicedata)

    async def async_entity_state_listener(changed_entity: str,
                                          old_state: State,
                                          new_state: State) -> None:
        """Listener to track state changes to lock entities."""
        handle_state_change(hass, config_entry, changed_entity, old_state,
                            new_state)

    # Unsubscribe to any listeners so we can create new ones
    for unsub_listener in hass.data[DOMAIN][config_entry.entry_id].get(
            UNSUB_LISTENERS, []):
        unsub_listener()
    hass.data[DOMAIN][config_entry.entry_id].get(UNSUB_LISTENERS, []).clear()

    # Create new listeners
    hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append(
        async_track_state_change(hass, primary_lock.lock_entity_id,
                                 async_entity_state_listener))
Example #41
0
 async def async_added_to_hass(self):
     """Register update dispatcher."""
     event.async_track_state_change(self.hass, self.outdoorSensor,
                                    self.change)
     self._may_update = True
Example #42
0
 async def async_homeassistant_started_listener(evt: Event):
     """Start tracking state changes after HomeAssistant has started."""
     # Listen to lock state changes so we can fire an event
     hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append(
         async_track_state_change(hass, primary_lock.lock_entity_id,
                                  async_entity_state_listener))
Example #43
0
async def test_track_state_change(hass):
    """Test track_state_change."""
    # 2 lists to track how often our callbacks get called
    specific_runs = []
    wildcard_runs = []
    wildercard_runs = []

    def specific_run_callback(entity_id, old_state, new_state):
        specific_runs.append(1)

    async_track_state_change(hass, "light.Bowl", specific_run_callback, "on",
                             "off")

    @ha.callback
    def wildcard_run_callback(entity_id, old_state, new_state):
        wildcard_runs.append((old_state, new_state))

    async_track_state_change(hass, "light.Bowl", wildcard_run_callback)

    @asyncio.coroutine
    def wildercard_run_callback(entity_id, old_state, new_state):
        wildercard_runs.append((old_state, new_state))

    async_track_state_change(hass, MATCH_ALL, wildercard_run_callback)

    # Adding state to state machine
    hass.states.async_set("light.Bowl", "on")
    await hass.async_block_till_done()
    assert len(specific_runs) == 0
    assert len(wildcard_runs) == 1
    assert len(wildercard_runs) == 1
    assert wildcard_runs[-1][0] is None
    assert wildcard_runs[-1][1] is not None

    # Set same state should not trigger a state change/listener
    hass.states.async_set("light.Bowl", "on")
    await hass.async_block_till_done()
    assert len(specific_runs) == 0
    assert len(wildcard_runs) == 1
    assert len(wildercard_runs) == 1

    # State change off -> on
    hass.states.async_set("light.Bowl", "off")
    await hass.async_block_till_done()
    assert len(specific_runs) == 1
    assert len(wildcard_runs) == 2
    assert len(wildercard_runs) == 2

    # State change off -> off
    hass.states.async_set("light.Bowl", "off", {"some_attr": 1})
    await hass.async_block_till_done()
    assert len(specific_runs) == 1
    assert len(wildcard_runs) == 3
    assert len(wildercard_runs) == 3

    # State change off -> on
    hass.states.async_set("light.Bowl", "on")
    await hass.async_block_till_done()
    assert len(specific_runs) == 1
    assert len(wildcard_runs) == 4
    assert len(wildercard_runs) == 4

    hass.states.async_remove("light.bowl")
    await hass.async_block_till_done()
    assert len(specific_runs) == 1
    assert len(wildcard_runs) == 5
    assert len(wildercard_runs) == 5
    assert wildcard_runs[-1][0] is not None
    assert wildcard_runs[-1][1] is None
    assert wildercard_runs[-1][0] is not None
    assert wildercard_runs[-1][1] is None

    # Set state for different entity id
    hass.states.async_set("switch.kitchen", "on")
    await hass.async_block_till_done()
    assert len(specific_runs) == 1
    assert len(wildcard_runs) == 5
    assert len(wildercard_runs) == 6
    def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
                 min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
                 cold_tolerance, hot_tolerance, keep_alive,
                 initial_operation_mode, difference, away_temp, kp, ki,
                 kd, pwm, autotune, noiseband):
        """Initialize the thermostat."""
        self.hass = hass
        self._name = name
        self.heater_entity_id = heater_entity_id
        self.ac_mode = ac_mode
        self.min_cycle_duration = min_cycle_duration
        self._cold_tolerance = cold_tolerance
        self._hot_tolerance = hot_tolerance
        self._keep_alive = keep_alive
        self._initial_operation_mode = initial_operation_mode
        self._saved_target_temp = target_temp if target_temp is not None \
            else away_temp
        if self.ac_mode:
            self._current_operation = STATE_COOL
            self._operation_list = [STATE_COOL, STATE_OFF]
            self.minOut = -difference
            self.maxOut = 0
        else:
            self._current_operation = STATE_HEAT
            self._operation_list = [STATE_HEAT, STATE_OFF]
            self.minOut = 0
            self.maxOut = difference
        if initial_operation_mode == STATE_OFF:
            self._enabled = False
            self._current_operation = STATE_OFF
        else:
            self._enabled = True
        self._active = False
        self._cur_temp = None
        self._min_temp = min_temp
        self._max_temp = max_temp
        self._target_temp = target_temp
        self._unit = hass.config.units.temperature_unit
        self._support_flags = SUPPORT_FLAGS
        if away_temp is not None:
            self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
        self.difference = difference
        self._away_temp = away_temp
        self._is_away = False
        self.kp = kp
        self.ki = ki
        self.kd = kd
        self.pwm = pwm
        self.autotune = autotune
        self.sensor_entity_id = sensor_entity_id
        self.time_changed = time.time()
        if self.autotune != "none":
            self.pidAutotune = pid_controller.PIDAutotune(self._target_temp, self.difference,
            self._keep_alive.seconds, self._keep_alive.seconds, self.minOut, self.maxOut,
            noiseband, time.time)
            _LOGGER.warning("Autotune will run with the next Setpoint Value you set."
            "changes, submited after doesn't have any effekt until it's finished")
        else:
            self.pidController = pid_controller.PIDArduino(self._keep_alive.seconds,
            self.kp, self.ki, self.kd, self.minOut, self.maxOut, time.time)

        async_track_state_change(
            hass, sensor_entity_id, self._async_sensor_changed)
        async_track_state_change(
            hass, heater_entity_id, self._async_switch_changed)

        if self._keep_alive:
            async_track_time_interval(
                hass, self._async_keep_alive, self._keep_alive)

        sensor_state = hass.states.get(sensor_entity_id)
        if sensor_state and sensor_state.state != STATE_UNKNOWN:
            self._async_update_temp(sensor_state)
Example #45
0
    async def async_added_to_hass(self):
        """Run when entity about to be added."""
        await super().async_added_to_hass()

        # Add listener
        async_track_state_change(
            self.hass, self.sensor_entity_id, self._async_sensor_changed)
        if self._hvac_mode == HVAC_MODE_HEAT:
            async_track_state_change(
                self.hass, self.heaters_entity_ids, self._async_switch_changed)
        elif self._hvac_mode == HVAC_MODE_COOL:
            async_track_state_change(
                self.hass, self.coolers_entity_ids, self._async_switch_changed)
        async_track_state_change(
            self.hass, self.target_entity_id, self._async_target_changed)
        if self._related_climate is not None:
            async_track_state_change(
                self.hass, self._related_climate, self._async_switch_changed)

        @callback
        def _async_startup(event):
            """Init on startup."""
            sensor_state = self._getStateSafe(self.sensor_entity_id)
            if sensor_state and sensor_state != STATE_UNKNOWN:
                self._async_update_temp(sensor_state)
            target_state = self._getStateSafe(self.target_entity_id)
            if target_state and \
               target_state != STATE_UNKNOWN and \
               self._hvac_mode != HVAC_MODE_HEAT_COOL:
                self._async_update_program_temp(target_state)

        self.hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_START, _async_startup)

        # Check If we have an old state
        old_state = await self.async_get_last_state()
        _LOGGER.info("old state: %s", old_state)
        if old_state is not None:
            # If we have no initial temperature, restore
            if self._target_temp is None:
                # If we have a previously saved temperature
                if old_state.attributes.get(ATTR_TEMPERATURE) is None:
                    target_entity_state = self._getStateSafe(target_entity_id)
                    if target_entity_state is not None:
                        self._target_temp = float(target_entity_state)
                    else:
                        self._target_temp = float((self._min_temp + self._max_temp)/2)
                    _LOGGER.warning("Undefined target temperature,"
                                    "falling back to %s", self._target_temp)
                else:
                    self._target_temp = float(
                        old_state.attributes[ATTR_TEMPERATURE])
            if (self._initial_hvac_mode is None and
                    old_state.state is not None):
                self._hvac_mode = \
                    old_state.state
                self._enabled = self._hvac_mode != HVAC_MODE_OFF

        else:
            # No previous state, try and restore defaults
            if self._target_temp is None:
                self._target_temp = float((self._min_temp + self._max_temp)/2)
            _LOGGER.warning("No previously saved temperature, setting to %s",
                            self._target_temp)

        # Set default state to off
        if not self._hvac_mode:
            self._hvac_mode = HVAC_MODE_OFF
Example #46
0
async def async_attach_trigger(
    hass: HomeAssistant,
    config,
    action,
    automation_info,
    *,
    platform_type: str = "state",
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    from_state = config.get(CONF_FROM, MATCH_ALL)
    to_state = config.get(CONF_TO, MATCH_ALL)
    time_delta = config.get(CONF_FOR)
    template.attach(hass, time_delta)
    match_all = from_state == MATCH_ALL and to_state == MATCH_ALL
    unsub_track_same = {}
    period: Dict[str, timedelta] = {}

    @callback
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        @callback
        def call_action():
            """Call action with right context."""
            hass.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform":
                            platform_type,
                            "entity_id":
                            entity,
                            "from_state":
                            from_s,
                            "to_state":
                            to_s,
                            "for":
                            time_delta if not time_delta else period[entity],
                        }
                    },
                    context=to_s.context,
                ))

        # Ignore changes to state attributes if from/to is in use
        if (not match_all and from_s is not None and to_s is not None
                and from_s.state == to_s.state):
            return

        if not time_delta:
            call_action()
            return

        variables = {
            "trigger": {
                "platform": "state",
                "entity_id": entity,
                "from_state": from_s,
                "to_state": to_s,
            }
        }

        try:
            if isinstance(time_delta, template.Template):
                period[entity] = vol.All(
                    cv.time_period,
                    cv.positive_timedelta)(time_delta.async_render(variables))
            elif isinstance(time_delta, dict):
                time_delta_data = {}
                time_delta_data.update(
                    template.render_complex(time_delta, variables))
                period[entity] = vol.All(
                    cv.time_period, cv.positive_timedelta)(time_delta_data)
            else:
                period[entity] = time_delta
        except (exceptions.TemplateError, vol.Invalid) as ex:
            _LOGGER.error("Error rendering '%s' for template: %s",
                          automation_info["name"], ex)
            return

        unsub_track_same[entity] = async_track_same_state(
            hass,
            period[entity],
            call_action,
            lambda _, _2, to_state: to_state.state == to_s.state,
            entity_ids=entity,
        )

    unsub = async_track_state_change(hass, entity_id,
                                     state_automation_listener, from_state,
                                     to_state)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        for async_remove in unsub_track_same.values():
            async_remove()
        unsub_track_same.clear()

    return async_remove
def async_trigger(hass, config, action):
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    below = config.get(CONF_BELOW)
    above = config.get(CONF_ABOVE)
    time_delta = config.get(CONF_FOR)
    value_template = config.get(CONF_VALUE_TEMPLATE)
    async_remove_track_same = None
    already_triggered = False

    if value_template is not None:
        value_template.hass = hass

    @callback
    def check_numeric_state(entity, from_s, to_s):
        """Return True if criteria are now met."""
        if to_s is None:
            return False

        variables = {
            'trigger': {
                'platform': 'numeric_state',
                'entity_id': entity,
                'below': below,
                'above': above,
            }
        }
        return condition.async_numeric_state(hass, to_s, below, above,
                                             value_template, variables)

    @callback
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        nonlocal already_triggered, async_remove_track_same

        @callback
        def call_action():
            """Call action with right context."""
            hass.async_run_job(
                action, {
                    'trigger': {
                        'platform': 'numeric_state',
                        'entity_id': entity,
                        'below': below,
                        'above': above,
                        'from_state': from_s,
                        'to_state': to_s,
                    }
                })

        matching = check_numeric_state(entity, from_s, to_s)

        if matching and not already_triggered:
            if time_delta:
                async_remove_track_same = async_track_same_state(
                    hass,
                    time_delta,
                    call_action,
                    entity_ids=entity_id,
                    async_check_same_func=check_numeric_state)
            else:
                call_action()

        already_triggered = matching

    unsub = async_track_state_change(hass, entity_id,
                                     state_automation_listener)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        if async_remove_track_same:
            async_remove_track_same()  # pylint: disable=not-callable

    return async_remove
Example #48
0
    async def async_added_to_hass(self):
        """Register callbacks."""
        @callback
        def filter_sensor_state_listener(entity,
                                         old_state,
                                         new_state,
                                         update_ha=True):
            """Handle device state changes."""
            if new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
                return

            temp_state = new_state

            try:
                for filt in self._filters:
                    filtered_state = filt.filter_state(copy(temp_state))
                    _LOGGER.debug(
                        "%s(%s=%s) -> %s", filt.name, self._entity,
                        temp_state.state, "skip"
                        if filt.skip_processing else filtered_state.state)
                    if filt.skip_processing:
                        return
                    temp_state = filtered_state
            except ValueError:
                _LOGGER.error("Could not convert state: %s to number",
                              self._state)
                return

            self._state = temp_state.state

            if self._icon is None:
                self._icon = new_state.attributes.get(ATTR_ICON, ICON)

            if self._unit_of_measurement is None:
                self._unit_of_measurement = new_state.attributes.get(
                    ATTR_UNIT_OF_MEASUREMENT)

            if update_ha:
                self.async_schedule_update_ha_state()

        if 'recorder' in self.hass.config.components:
            history_list = []
            largest_window_items = 0
            largest_window_time = timedelta(0)

            # Determine the largest window_size by type
            for filt in self._filters:
                if filt.window_unit == WINDOW_SIZE_UNIT_NUMBER_EVENTS\
                        and largest_window_items < filt.window_size:
                    largest_window_items = filt.window_size
                elif filt.window_unit == WINDOW_SIZE_UNIT_TIME\
                        and largest_window_time < filt.window_size:
                    largest_window_time = filt.window_size

            # Retrieve the largest window_size of each type
            if largest_window_items > 0:
                filter_history = await self.hass.async_add_job(
                    partial(history.get_last_state_changes,
                            self.hass,
                            largest_window_items,
                            entity_id=self._entity))
                history_list.extend(
                    [state for state in filter_history[self._entity]])
            if largest_window_time > timedelta(seconds=0):
                start = dt_util.utcnow() - largest_window_time
                filter_history = await self.hass.async_add_job(
                    partial(history.state_changes_during_period,
                            self.hass,
                            start,
                            entity_id=self._entity))
                history_list.extend([
                    state for state in filter_history[self._entity]
                    if state not in history_list
                ])

            # Sort the window states
            history_list = sorted(history_list, key=lambda s: s.last_updated)
            _LOGGER.debug("Loading from history: %s",
                          [(s.state, s.last_updated) for s in history_list])

            # Replay history through the filter chain
            prev_state = None
            for state in history_list:
                filter_sensor_state_listener(self._entity, prev_state, state,
                                             False)
                prev_state = state

        async_track_state_change(self.hass, self._entity,
                                 filter_sensor_state_listener)
Example #49
0
 def qr_sensor_startup(event):
     """Update template on startup."""
     async_track_state_change(self.hass, self._entities,
                              qr_state_listener)
Example #50
0
    def __init__(self,
                 hass,
                 name,
                 ip_addr,
                 port,
                 mac_addr,
                 timeout,
                 target_temp_step,
                 temp_sensor_entity_id,
                 lights_entity_id,
                 xfan_entity_id,
                 health_entity_id,
                 powersave_entity_id,
                 sleep_entity_id,
                 eightdegheat_entity_id,
                 hvac_modes,
                 fan_modes,
                 swing_modes,
                 encryption_key=None,
                 uid=None):
        _LOGGER.info('Initialize the GREE climate device')
        self.hass = hass
        self._name = name
        self._ip_addr = ip_addr
        self._port = port
        self._mac_addr = mac_addr.decode('utf-8').lower()
        self._timeout = timeout

        self._target_temperature = None
        self._target_temperature_step = target_temp_step
        self._unit_of_measurement = hass.config.units.temperature_unit

        self._current_temperature = None
        self._temp_sensor_entity_id = temp_sensor_entity_id
        self._lights_entity_id = lights_entity_id
        self._xfan_entity_id = xfan_entity_id
        self._health_entity_id = health_entity_id
        self._powersave_entity_id = powersave_entity_id
        self._sleep_entity_id = sleep_entity_id
        self._eightdegheat_entity_id = eightdegheat_entity_id

        self._hvac_mode = None
        self._fan_mode = None
        self._swing_mode = None
        self._current_lights = None
        self._current_xfan = None
        self._current_health = None
        self._current_powersave = None
        self._current_sleep = None
        self._current_eightdegheat = None

        self._hvac_modes = hvac_modes
        self._fan_modes = fan_modes
        self._swing_modes = swing_modes

        if encryption_key:
            _LOGGER.info(
                'Using configured encryption key: {}'.format(encryption_key))
            self._encryption_key = encryption_key.encode("utf8")
        else:
            self._encryption_key = self.GetDeviceKey().encode("utf8")
            _LOGGER.info('Fetched device encrytion key: %s' %
                         str(self._encryption_key))

        if uid:
            self._uid = uid
        else:
            self._uid = 0

        self._acOptions = {
            'Pow': None,
            'Mod': None,
            'SetTem': None,
            'WdSpd': None,
            'Air': None,
            'Blo': None,
            'Health': None,
            'SwhSlp': None,
            'Lig': None,
            'SwingLfRig': None,
            'SwUpDn': None,
            'Quiet': None,
            'Tur': None,
            'StHt': None,
            'TemUn': None,
            'HeatCoolType': None,
            'TemRec': None,
            'SvSt': None,
            'SlpMod': None
        }

        self._firstTimeRun = True

        # Cipher to use to encrypt/decrypt
        self.CIPHER = AES.new(self._encryption_key, AES.MODE_ECB)

        if temp_sensor_entity_id:
            _LOGGER.info('Setting up temperature sensor: ' +
                         str(temp_sensor_entity_id))
            async_track_state_change(hass, temp_sensor_entity_id,
                                     self._async_temp_sensor_changed)

        if lights_entity_id:
            _LOGGER.info('Setting up lights entity: ' + str(lights_entity_id))
            async_track_state_change(hass, lights_entity_id,
                                     self._async_lights_entity_state_changed)

        if xfan_entity_id:
            _LOGGER.info('Setting up xfan entity: ' + str(xfan_entity_id))
            async_track_state_change(hass, xfan_entity_id,
                                     self._async_xfan_entity_state_changed)

        if health_entity_id:
            _LOGGER.info('Setting up health entity: ' + str(health_entity_id))
            async_track_state_change(hass, health_entity_id,
                                     self._async_health_entity_state_changed)

        if powersave_entity_id:
            _LOGGER.info('Setting up powersave entity: ' +
                         str(powersave_entity_id))
            async_track_state_change(
                hass, powersave_entity_id,
                self._async_powersave_entity_state_changed)

        if sleep_entity_id:
            _LOGGER.info('Setting up sleep entity: ' + str(sleep_entity_id))
            async_track_state_change(hass, sleep_entity_id,
                                     self._async_sleep_entity_state_changed)

        if eightdegheat_entity_id:
            _LOGGER.info('Setting up 8℃ heat entity: ' +
                         str(eightdegheat_entity_id))
            async_track_state_change(
                hass, eightdegheat_entity_id,
                self._async_eightdegheat_entity_state_changed)
Example #51
0
def async_trigger(hass, config, action):
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    from_state = config.get(CONF_FROM, MATCH_ALL)
    to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
    time_delta = config.get(CONF_FOR)
    async_remove_state_for_cancel = None
    async_remove_state_for_listener = None

    @callback
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        nonlocal async_remove_state_for_cancel, async_remove_state_for_listener

        def call_action():
            """Call action with right context."""
            hass.async_run_job(
                action, {
                    'trigger': {
                        'platform': 'state',
                        'entity_id': entity,
                        'from_state': from_s,
                        'to_state': to_s,
                        'for': time_delta,
                    }
                })

        if time_delta is None:
            call_action()
            return

        @callback
        def state_for_listener(now):
            """Fire on state changes after a delay and calls action."""
            async_remove_state_for_cancel()
            call_action()

        @callback
        def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
            """Fire on changes and cancel for listener if changed."""
            if inner_to_s.state == to_s.state:
                return
            async_remove_state_for_listener()
            async_remove_state_for_cancel()

        async_remove_state_for_listener = async_track_point_in_utc_time(
            hass, state_for_listener,
            dt_util.utcnow() + time_delta)

        async_remove_state_for_cancel = async_track_state_change(
            hass, entity, state_for_cancel_listener)

    unsub = async_track_state_change(hass, entity_id,
                                     state_automation_listener, from_state,
                                     to_state)

    def async_remove():
        """Remove state listeners async."""
        unsub()
        # pylint: disable=not-callable
        if async_remove_state_for_cancel is not None:
            async_remove_state_for_cancel()

        if async_remove_state_for_listener is not None:
            async_remove_state_for_listener()

    return async_remove
Example #52
0
async def async_enable_proactive_mode(hass, smart_home_config):
    """Enable the proactive mode.

    Proactive mode makes this component report state changes to Alexa.
    """
    # Validate we can get access token.
    await smart_home_config.async_get_access_token()

    @callback
    def extra_significant_check(
        hass: HomeAssistant,
        old_state: str,
        old_attrs: dict,
        old_extra_arg: dict,
        new_state: str,
        new_attrs: dict,
        new_extra_arg: dict,
    ):
        """Check if the serialized data has changed."""
        return old_extra_arg is not None and old_extra_arg != new_extra_arg

    checker = await create_checker(hass, DOMAIN, extra_significant_check)

    async def async_entity_state_listener(
        changed_entity: str,
        old_state: State | None,
        new_state: State | None,
    ):
        if not hass.is_running:
            return

        if not new_state:
            return

        if new_state.domain not in ENTITY_ADAPTERS:
            return

        if not smart_home_config.should_expose(changed_entity):
            _LOGGER.debug("Not exposing %s because filtered by config",
                          changed_entity)
            return

        alexa_changed_entity: AlexaEntity = ENTITY_ADAPTERS[new_state.domain](
            hass, smart_home_config, new_state)

        # Determine how entity should be reported on
        should_report = False
        should_doorbell = False

        for interface in alexa_changed_entity.interfaces():
            if not should_report and interface.properties_proactively_reported(
            ):
                should_report = True

            if interface.name() == "Alexa.DoorbellEventSource":
                should_doorbell = True
                break

        if not should_report and not should_doorbell:
            return

        if should_doorbell:
            if new_state.state == STATE_ON:
                await async_send_doorbell_event_message(
                    hass, smart_home_config, alexa_changed_entity)
            return

        alexa_properties = list(alexa_changed_entity.serialize_properties())

        if not checker.async_is_significant_change(new_state,
                                                   extra_arg=alexa_properties):
            return

        await async_send_changereport_message(hass, smart_home_config,
                                              alexa_changed_entity,
                                              alexa_properties)

    return async_track_state_change(hass, MATCH_ALL,
                                    async_entity_state_listener)
Example #53
0
        def template_bsensor_startup(event):
            """Update template on startup."""
            async_track_state_change(self.hass, self._entities,
                                     template_bsensor_state_listener)

            self.hass.async_add_job(self.async_check_state)
Example #54
0
    def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
                 min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
                 cold_tolerance, hot_tolerance, keep_alive,
                 initial_operation_mode, away_temp, precision,
                 heat_entity_id, regulation_entity_id, state_entity_id,
                 regulation_duration, regulation_nb_duration, regulation_delta
                 ):
        """Initialize the thermostat."""
        self.hass = hass
        self._name = name
        self.heater_entity_id = heater_entity_id
        self.ac_mode = ac_mode
        self.min_cycle_duration = min_cycle_duration
        self._cold_tolerance = cold_tolerance
        self._hot_tolerance = hot_tolerance
        self._keep_alive = keep_alive
        self._initial_operation_mode = initial_operation_mode
        self._saved_target_temp = target_temp if target_temp is not None \
            else away_temp
        self._temp_precision = precision
        if self.ac_mode:
            self._current_operation = STATE_COOL
            self._operation_list = [STATE_COOL, STATE_OFF]
        else:
            self._current_operation = STATE_HEAT
            self._operation_list = [STATE_HEAT, STATE_OFF]
        if initial_operation_mode == STATE_OFF:
            self._enabled = False
            self._current_operation = STATE_OFF
        else:
            self._enabled = True
        self._active = False
        self._cur_temp = None
        self._temp_lock = asyncio.Lock()
        self._min_temp = min_temp
        self._max_temp = max_temp
        self._target_temp = target_temp
        self._unit = hass.config.units.temperature_unit
        self._support_flags = SUPPORT_FLAGS
        if away_temp is not None:
            self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
        self._away_temp = away_temp
        self._is_away = False

        async_track_state_change(
            hass, sensor_entity_id, self._async_sensor_changed)
        async_track_state_change(
            hass, heater_entity_id, self._async_switch_changed)

        if self._keep_alive:
            async_track_time_interval(
                hass, self._async_control_heating, self._keep_alive)
        
        sensor_state = hass.states.get(sensor_entity_id)
        if sensor_state and sensor_state.state != STATE_UNKNOWN:
            self._async_update_temp(sensor_state)

        self._heat_entity_id = heat_entity_id
        self._regulation_entity_id = regulation_entity_id
        self._state_entity_id = state_entity_id
        self._regulation_duration = regulation_duration
        self._regulation_nb_duration = regulation_nb_duration
        self._regulation_delta = regulation_delta
        self._nb_tick_regulation = 0

        if self._regulation_duration:
            async_track_time_interval(
                hass, self._async_regulation, self._regulation_duration)
Example #55
0
        def template_sensor_startup(event):
            """Update template on startup."""
            async_track_state_change(
                self.hass, self._entities, template_sensor_state_listener)

            self.async_schedule_update_ha_state(True)
Example #56
0
        def template_sensor_startup(event):
            """Update on startup."""
            async_track_state_change(self.hass, self._entity,
                                     template_sensor_state_listener)

            self.hass.async_add_job(self.async_update_ha_state(True))
Example #57
0
async def update_listener(hass: HomeAssistant,
                          config_entry: ConfigEntry) -> None:
    """Update listener."""
    # No need to update if the options match the data
    if not config_entry.options:
        return

    # If the path has changed delete the old base folder, otherwise if the lock name
    # has changed only delete the old lock folder
    if config_entry.options[CONF_PATH] != config_entry.data[CONF_PATH]:
        await hass.async_add_executor_job(delete_folder, hass.config.path(),
                                          config_entry.data[CONF_PATH])
    elif config_entry.options[CONF_LOCK_NAME] != config_entry.data[
            CONF_LOCK_NAME]:
        await hass.async_add_executor_job(
            delete_folder,
            hass.config.path(),
            config_entry.data[CONF_PATH],
            config_entry.data[CONF_LOCK_NAME],
        )

    start_from = config_entry.data[CONF_START]
    code_slots = config_entry.data[CONF_SLOTS]
    old_slots = list(range(start_from, start_from + code_slots))

    start_from = config_entry.options[CONF_START]
    code_slots = config_entry.options[CONF_SLOTS]
    new_slots = list(range(start_from, start_from + code_slots))

    new_data = config_entry.options.copy()
    new_data.pop(CONF_GENERATE, None)

    hass.config_entries.async_update_entry(
        entry=config_entry,
        unique_id=config_entry.options[CONF_LOCK_NAME],
        data=new_data,
        options={},
    )

    primary_lock, child_locks = await generate_keymaster_locks(
        hass, config_entry)

    hass.data[DOMAIN][config_entry.entry_id].update({
        PRIMARY_LOCK: primary_lock,
        CHILD_LOCKS: child_locks,
    })
    servicedata = {"lockname": primary_lock.lock_name}
    await hass.services.async_call(DOMAIN,
                                   SERVICE_GENERATE_PACKAGE,
                                   servicedata,
                                   blocking=True)

    if old_slots != new_slots:
        async_dispatcher_send(
            hass,
            f"{DOMAIN}_{config_entry.entry_id}_code_slots_changed",
            old_slots,
            new_slots,
        )

    # Unsubscribe to any listeners so we can create new ones
    for unsub_listener in hass.data[DOMAIN][config_entry.entry_id].get(
            UNSUB_LISTENERS, []):
        unsub_listener()
    hass.data[DOMAIN][config_entry.entry_id].get(UNSUB_LISTENERS, []).clear()

    if using_zwave_js(hass):
        hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append(
            hass.bus.async_listen(
                ZWAVE_JS_EVENT,
                functools.partial(handle_zwave_js_event, hass, config_entry),
            ))

    # Check if alarm type/alarm level sensors are specified, in which case
    # we need to listen for lock state changes and derive the action from those
    # sensors
    locks_to_watch = []
    for lock in [primary_lock, *child_locks]:
        if (lock.alarm_level_or_user_code_entity_id not in (
                None,
                "sensor.fake",
        ) and lock.alarm_type_or_access_control_entity_id
                not in (None, "sensor.fake")):
            locks_to_watch.append(lock)

    if locks_to_watch:
        # Create new listeners for lock state changes
        hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append(
            async_track_state_change(
                hass,
                [lock.lock_entity_id for lock in locks_to_watch],
                functools.partial(handle_state_change, hass, config_entry),
                from_state=[STATE_LOCKED, STATE_UNLOCKED],
                to_state=[STATE_LOCKED, STATE_UNLOCKED],
            ))
Example #58
0
 def run(self):
     """Method called by accessory after driver is started."""
     state = self.hass.states.get(self.entity_id)
     self.update_state_callback(new_state=state)
     async_track_state_change(self.hass, self.entity_id,
                              self.update_state_callback)
Example #59
0
async def async_setup(hass, config):
    """Set up the triggers to control lights based on device presence."""
    logger = logging.getLogger(__name__)
    device_tracker = hass.components.device_tracker
    group = hass.components.group
    light = hass.components.light
    person = hass.components.person
    conf = config[DOMAIN]
    disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF)
    light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS)
    light_profile = conf.get(CONF_LIGHT_PROFILE)
    device_group = conf.get(CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES)
    device_entity_ids = group.get_entity_ids(device_group, device_tracker.DOMAIN)
    device_entity_ids.extend(group.get_entity_ids(device_group, person.DOMAIN))

    if not device_entity_ids:
        logger.error("No devices found to track")
        return False

    # Get the light IDs from the specified group
    light_ids = group.get_entity_ids(light_group, light.DOMAIN)

    if not light_ids:
        logger.error("No lights found to turn on")
        return False

    def calc_time_for_light_when_sunset():
        """Calculate the time when to start fading lights in when sun sets.

        Returns None if no next_setting data available.

        Async friendly.
        """
        next_setting = get_astral_event_next(hass, SUN_EVENT_SUNSET)
        if not next_setting:
            return None
        return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)

    def async_turn_on_before_sunset(light_id):
        """Turn on lights."""
        if not device_tracker.is_on() or light.is_on(light_id):
            return
        hass.async_create_task(
            hass.services.async_call(
                DOMAIN_LIGHT,
                SERVICE_TURN_ON,
                {
                    ATTR_ENTITY_ID: light_id,
                    ATTR_TRANSITION: LIGHT_TRANSITION_TIME.seconds,
                    ATTR_PROFILE: light_profile,
                },
            )
        )

    def async_turn_on_factory(light_id):
        """Generate turn on callbacks as factory."""

        @callback
        def async_turn_on_light(now):
            """Turn on specific light."""
            async_turn_on_before_sunset(light_id)

        return async_turn_on_light

    # Track every time sun rises so we can schedule a time-based
    # pre-sun set event
    @callback
    def schedule_light_turn_on(now):
        """Turn on all the lights at the moment sun sets.

        We will schedule to have each light start after one another
        and slowly transition in.
        """
        start_point = calc_time_for_light_when_sunset()
        if not start_point:
            return

        for index, light_id in enumerate(light_ids):
            async_track_point_in_utc_time(
                hass,
                async_turn_on_factory(light_id),
                start_point + index * LIGHT_TRANSITION_TIME,
            )

    async_track_point_in_utc_time(
        hass, schedule_light_turn_on, get_astral_event_next(hass, SUN_EVENT_SUNRISE)
    )

    # If the sun is already above horizon schedule the time-based pre-sun set
    # event.
    if is_up(hass):
        schedule_light_turn_on(None)

    @callback
    def check_light_on_dev_state_change(entity, old_state, new_state):
        """Handle tracked device state changes."""
        lights_are_on = group.is_on(light_group)
        light_needed = not (lights_are_on or is_up(hass))

        # These variables are needed for the elif check
        now = dt_util.utcnow()
        start_point = calc_time_for_light_when_sunset()

        # Do we need lights?
        if light_needed:
            logger.info("Home coming event for %s. Turning lights on", entity)
            hass.async_create_task(
                hass.services.async_call(
                    DOMAIN_LIGHT,
                    SERVICE_TURN_ON,
                    {ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile},
                )
            )

        # Are we in the time span were we would turn on the lights
        # if someone would be home?
        # Check this by seeing if current time is later then the point
        # in time when we would start putting the lights on.
        elif start_point and start_point < now < get_astral_event_next(
            hass, SUN_EVENT_SUNSET
        ):

            # Check for every light if it would be on if someone was home
            # when the fading in started and turn it on if so
            for index, light_id in enumerate(light_ids):
                if now > start_point + index * LIGHT_TRANSITION_TIME:
                    hass.async_create_task(
                        hass.services.async_call(
                            DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_id}
                        )
                    )

                else:
                    # If this light didn't happen to be turned on yet so
                    # will all the following then, break.
                    break

    async_track_state_change(
        hass,
        device_entity_ids,
        check_light_on_dev_state_change,
        STATE_NOT_HOME,
        STATE_HOME,
    )

    if disable_turn_off:
        return True

    @callback
    def turn_off_lights_when_all_leave(entity, old_state, new_state):
        """Handle device group state change."""
        if not group.is_on(light_group):
            return

        logger.info("Everyone has left but there are lights on. Turning them off")
        hass.async_create_task(
            hass.services.async_call(
                DOMAIN_LIGHT, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: light_ids}
            )
        )

    async_track_state_change(
        hass, device_group, turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME
    )

    return True
Example #60
0
def async_trigger(hass, config, action):
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    from_state = config.get(CONF_FROM, MATCH_ALL)
    to_state = get_deprecated(config, CONF_TO, CONF_STATE, MATCH_ALL)
    time_delta = config.get(CONF_FOR)
    async_remove_state_for_cancel = None
    async_remove_state_for_listener = None
    match_all = (from_state == MATCH_ALL and to_state == MATCH_ALL)

    @callback
    def clear_listener():
        """Clear all unsub listener."""
        nonlocal async_remove_state_for_cancel, async_remove_state_for_listener

        # pylint: disable=not-callable
        if async_remove_state_for_listener is not None:
            async_remove_state_for_listener()
            async_remove_state_for_listener = None
        if async_remove_state_for_cancel is not None:
            async_remove_state_for_cancel()
            async_remove_state_for_cancel = None

    @callback
    def state_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        nonlocal async_remove_state_for_cancel, async_remove_state_for_listener

        def call_action():
            """Call action with right context."""
            hass.async_run_job(
                action, {
                    'trigger': {
                        'platform': 'state',
                        'entity_id': entity,
                        'from_state': from_s,
                        'to_state': to_s,
                        'for': time_delta,
                    }
                })

        # Ignore changes to state attributes if from/to is in use
        if (not match_all and from_s is not None and to_s is not None
                and from_s.last_changed == to_s.last_changed):
            return

        if time_delta is None:
            call_action()
            return

        @callback
        def state_for_listener(now):
            """Fire on state changes after a delay and calls action."""
            nonlocal async_remove_state_for_listener
            async_remove_state_for_listener = None
            clear_listener()
            call_action()

        @callback
        def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
            """Fire on changes and cancel for listener if changed."""
            if inner_to_s.state == to_s.state:
                return
            clear_listener()

        # cleanup previous listener
        clear_listener()

        async_remove_state_for_listener = async_track_point_in_utc_time(
            hass, state_for_listener,
            dt_util.utcnow() + time_delta)

        async_remove_state_for_cancel = async_track_state_change(
            hass, entity, state_for_cancel_listener)

    unsub = async_track_state_change(hass, entity_id,
                                     state_automation_listener, from_state,
                                     to_state)

    @callback
    def async_remove():
        """Remove state listeners async."""
        unsub()
        clear_listener()

    return async_remove