def _update_from_database(self, start: datetime.datetime, end: datetime.datetime) -> list[State]: return history.state_changes_during_period( self.hass, start, end, self.entity_id, include_start_time_state=True, no_attributes=True, ).get(self.entity_id, [])
def test_state_changes_during_period(hass_recorder, attributes, no_attributes, limit): """Test state change during period.""" hass = hass_recorder() entity_id = "media_player.test" def set_state(state): """Set the state.""" hass.states.set(entity_id, state, attributes) wait_recording_done(hass) return hass.states.get(entity_id) start = dt_util.utcnow() point = start + timedelta(seconds=1) end = point + timedelta(seconds=1) with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): set_state("idle") set_state("YouTube") with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): states = [ set_state("idle"), set_state("Netflix"), set_state("Plex"), set_state("YouTube"), ] with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=end): set_state("Netflix") set_state("Plex") hist = history.state_changes_during_period(hass, start, end, entity_id, no_attributes, limit=limit) assert states[:limit] == hist[entity_id]
def _update(self, start, end, now_timestamp, start_timestamp, end_timestamp): # Get history between start and end history_list = history.state_changes_during_period( self.hass, start, end, str(self._entity_id), no_attributes=True) if self._entity_id not in history_list: return # Get the first state last_state = history.get_state(self.hass, start, self._entity_id, no_attributes=True) last_state = last_state is not None and last_state in self._entity_states last_time = start_timestamp elapsed = 0 count = 0 # Make calculations for item in history_list.get(self._entity_id): current_state = item.state in self._entity_states current_time = item.last_changed.timestamp() if last_state: elapsed += current_time - last_time if current_state and not last_state: count += 1 last_state = current_state last_time = current_time # Count time elapsed between last history state and end of measure if last_state: measure_end = min(end_timestamp, now_timestamp) elapsed += measure_end - last_time # Save value in hours self.value = elapsed / 3600 # Save counter self.count = count
def _fetch_states_from_database(self) -> list[State]: """Fetch the states from the database.""" _LOGGER.debug("%s: initializing values from the database", self.entity_id) lower_entity_id = self._source_entity_id.lower() if self._samples_max_age is not None: start_date = (dt_util.utcnow() - self._samples_max_age - timedelta(microseconds=1)) _LOGGER.debug( "%s: retrieve records not older then %s", self.entity_id, start_date, ) else: start_date = datetime.fromtimestamp(0, tz=dt_util.UTC) _LOGGER.debug("%s: retrieving all records", self.entity_id) return history.state_changes_during_period( self.hass, start_date, entity_id=lower_entity_id, descending=True, limit=self._samples_max_buffer_size, include_start_time_state=False, ).get(lower_entity_id, [])
def state_changes_during_period(hass, start_time, end_time=None, entity_id=None): """Return states changes during UTC period start_time - end_time.""" return history.state_changes_during_period( hass, start_time, end_time=None, entity_id=None )
async def test_state_changes_during_period_query_during_migration_to_schema_25( hass: ha.HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT, ): """Test we can query data prior to schema 25 and during migration to schema 25.""" instance = await async_setup_recorder_instance(hass, {}) start = dt_util.utcnow() point = start + timedelta(seconds=1) end = point + timedelta(seconds=1) entity_id = "light.test" await hass.async_add_executor_job(_add_db_entries, hass, point, [entity_id]) no_attributes = True hist = history.state_changes_during_period(hass, start, end, entity_id, no_attributes, include_start_time_state=False) state = hist[entity_id][0] assert state.attributes == {} no_attributes = False hist = history.state_changes_during_period(hass, start, end, entity_id, no_attributes, include_start_time_state=False) state = hist[entity_id][0] assert state.attributes == {"name": "the shared light"} with instance.engine.connect() as conn: conn.execute(text("update states set attributes_id=NULL;")) conn.execute(text("drop table state_attributes;")) conn.commit() with patch.object(instance, "migration_in_progress", True): no_attributes = True hist = history.state_changes_during_period( hass, start, end, entity_id, no_attributes, include_start_time_state=False) state = hist[entity_id][0] assert state.attributes == {} no_attributes = False hist = history.state_changes_during_period( hass, start, end, entity_id, no_attributes, include_start_time_state=False) state = hist[entity_id][0] assert state.attributes == {"name": "the light"}
def _update_state(self): # pylint: disable=r0914,r0912,r0915 """Update the sensor state.""" _LOGGER.debug('Updating sensor "%s"', self.name) start = end = start_ts = end_ts = None p_period = self._period # Parse templates self._update_period() if self._period is not None: now = datetime.datetime.now() start, end = self._period if p_period is None: p_start = p_end = now else: p_start, p_end = p_period # Convert times to UTC start = dt_util.as_utc(start) end = dt_util.as_utc(end) p_start = dt_util.as_utc(p_start) p_end = dt_util.as_utc(p_end) # Compute integer timestamps now_ts = math.floor(dt_util.as_timestamp(now)) start_ts = math.floor(dt_util.as_timestamp(start)) end_ts = math.floor(dt_util.as_timestamp(end)) p_start_ts = math.floor(dt_util.as_timestamp(p_start)) p_end_ts = math.floor(dt_util.as_timestamp(p_end)) # If period has not changed and current time after the period end.. if start_ts == p_start_ts and end_ts == p_end_ts and end_ts <= now_ts: # Don't compute anything as the value cannot have changed return self.available_sources = 0 values = [] self.count = 0 self.min_value = self.max_value = None # pylint: disable=too-many-nested-blocks for entity_id in self.sources: _LOGGER.debug('Processing entity "%s"', entity_id) state = self.hass.states.get(entity_id) # type: LazyState if state is None: _LOGGER.error('Unable to find an entity "%s"', entity_id) continue self._init_mode(state) value = 0 elapsed = 0 if self._period is None: # Get current state value = self._get_state_value(state) _LOGGER.debug("Current state: %s", value) else: # Get history between start and now history_list = history.state_changes_during_period( self.hass, start, end, str(entity_id) ) if entity_id not in history_list.keys(): value = self._get_state_value(state) _LOGGER.warning( 'Historical data not found for entity "%s". ' "Current state used: %s", entity_id, value, ) else: # Get the first state item = history.get_state(self.hass, start, entity_id) _LOGGER.debug("Initial historical state: %s", item) last_state = None last_time = start_ts if item is not None and self._has_state(item.state): last_state = self._get_state_value(item) # Get the other states for item in history_list.get(entity_id): _LOGGER.debug("Historical state: %s", item) current_state = self._get_state_value(item) current_time = item.last_changed.timestamp() if last_state is not None: last_elapsed = current_time - last_time value += last_state * last_elapsed elapsed += last_elapsed last_state = current_state last_time = current_time # Count time elapsed between last history state and now if last_state is not None: last_elapsed = end_ts - last_time value += last_state * last_elapsed elapsed += last_elapsed if elapsed: value /= elapsed _LOGGER.debug("Historical average state: %s", value) if isinstance(value, numbers.Number): values.append(value) self.available_sources += 1 if values: self._attr_state = round(sum(values) / len(values), self._precision) if self._precision < 1: self._attr_state = int(self._attr_state) else: self._attr_state = None _LOGGER.debug("Total average state: %s", self._attr_state)