mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Merge pull request #113 from balloob/refactor-utc
Refactor to use UTC internally
This commit is contained in:
commit
a752bc012c
@ -12,7 +12,6 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import enum
|
import enum
|
||||||
import re
|
import re
|
||||||
import datetime as dt
|
|
||||||
import functools as ft
|
import functools as ft
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -22,6 +21,7 @@ from homeassistant.const import (
|
|||||||
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
|
|
||||||
DOMAIN = "homeassistant"
|
DOMAIN = "homeassistant"
|
||||||
|
|
||||||
@ -107,7 +107,20 @@ class HomeAssistant(object):
|
|||||||
|
|
||||||
def track_point_in_time(self, action, point_in_time):
|
def track_point_in_time(self, action, point_in_time):
|
||||||
"""
|
"""
|
||||||
Adds a listener that fires once at or after a spefic point in time.
|
Adds a listener that fires once after a spefic point in time.
|
||||||
|
"""
|
||||||
|
utc_point_in_time = date_util.as_utc(point_in_time)
|
||||||
|
|
||||||
|
@ft.wraps(action)
|
||||||
|
def utc_converter(utc_now):
|
||||||
|
""" Converts passed in UTC now to local now. """
|
||||||
|
action(date_util.as_local(utc_now))
|
||||||
|
|
||||||
|
self.track_point_in_utc_time(utc_converter, utc_point_in_time)
|
||||||
|
|
||||||
|
def track_point_in_utc_time(self, action, point_in_time):
|
||||||
|
"""
|
||||||
|
Adds a listener that fires once after a specific point in UTC time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ft.wraps(action)
|
@ft.wraps(action)
|
||||||
@ -133,11 +146,19 @@ class HomeAssistant(object):
|
|||||||
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
self.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener)
|
||||||
return point_in_time_listener
|
return point_in_time_listener
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def track_utc_time_change(self, action,
|
||||||
|
year=None, month=None, day=None,
|
||||||
|
hour=None, minute=None, second=None):
|
||||||
|
""" Adds a listener that will fire if time matches a pattern. """
|
||||||
|
self.track_time_change(
|
||||||
|
action, year, month, day, hour, minute, second, utc=True)
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def track_time_change(self, action,
|
def track_time_change(self, action,
|
||||||
year=None, month=None, day=None,
|
year=None, month=None, day=None,
|
||||||
hour=None, minute=None, second=None):
|
hour=None, minute=None, second=None, utc=False):
|
||||||
""" Adds a listener that will fire if time matches a pattern. """
|
""" Adds a listener that will fire if UTC time matches a pattern. """
|
||||||
|
|
||||||
# We do not have to wrap the function with time pattern matching logic
|
# We do not have to wrap the function with time pattern matching logic
|
||||||
# if no pattern given
|
# if no pattern given
|
||||||
@ -153,6 +174,9 @@ class HomeAssistant(object):
|
|||||||
""" Listens for matching time_changed events. """
|
""" Listens for matching time_changed events. """
|
||||||
now = event.data[ATTR_NOW]
|
now = event.data[ATTR_NOW]
|
||||||
|
|
||||||
|
if not utc:
|
||||||
|
now = date_util.as_local(now)
|
||||||
|
|
||||||
mat = _matcher
|
mat = _matcher
|
||||||
|
|
||||||
if mat(now.year, year) and \
|
if mat(now.year, year) and \
|
||||||
@ -303,7 +327,7 @@ def create_worker_pool():
|
|||||||
|
|
||||||
for start, job in current_jobs:
|
for start, job in current_jobs:
|
||||||
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
||||||
util.datetime_to_str(start), job)
|
date_util.datetime_to_local_str(start), job)
|
||||||
|
|
||||||
return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback)
|
return util.ThreadPool(job_handler, MIN_WORKER_THREAD, busy_callback)
|
||||||
|
|
||||||
@ -331,7 +355,7 @@ class Event(object):
|
|||||||
self.data = data or {}
|
self.data = data or {}
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.time_fired = util.strip_microseconds(
|
self.time_fired = util.strip_microseconds(
|
||||||
time_fired or dt.datetime.now())
|
time_fired or date_util.utcnow())
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Returns a dict representation of this Event. """
|
""" Returns a dict representation of this Event. """
|
||||||
@ -339,7 +363,7 @@ class Event(object):
|
|||||||
'event_type': self.event_type,
|
'event_type': self.event_type,
|
||||||
'data': dict(self.data),
|
'data': dict(self.data),
|
||||||
'origin': str(self.origin),
|
'origin': str(self.origin),
|
||||||
'time_fired': util.datetime_to_str(self.time_fired),
|
'time_fired': date_util.datetime_to_str(self.time_fired),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -352,6 +376,13 @@ class Event(object):
|
|||||||
return "<Event {}[{}]>".format(self.event_type,
|
return "<Event {}[{}]>".format(self.event_type,
|
||||||
str(self.origin)[0])
|
str(self.origin)[0])
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (self.__class__ == other.__class__ and
|
||||||
|
self.event_type == other.event_type and
|
||||||
|
self.data == other.data and
|
||||||
|
self.origin == other.origin and
|
||||||
|
self.time_fired == other.time_fired)
|
||||||
|
|
||||||
|
|
||||||
class EventBus(object):
|
class EventBus(object):
|
||||||
""" Class that allows different components to communicate via services
|
""" Class that allows different components to communicate via services
|
||||||
@ -374,6 +405,9 @@ class EventBus(object):
|
|||||||
|
|
||||||
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
def fire(self, event_type, event_data=None, origin=EventOrigin.local):
|
||||||
""" Fire an event. """
|
""" Fire an event. """
|
||||||
|
if not self._pool.running:
|
||||||
|
raise HomeAssistantError('Home Assistant has shut down.')
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
# Copy the list of the current listeners because some listeners
|
# Copy the list of the current listeners because some listeners
|
||||||
# remove themselves as a listener while being executed which
|
# remove themselves as a listener while being executed which
|
||||||
@ -472,13 +506,14 @@ class State(object):
|
|||||||
self.entity_id = entity_id.lower()
|
self.entity_id = entity_id.lower()
|
||||||
self.state = state
|
self.state = state
|
||||||
self.attributes = attributes or {}
|
self.attributes = attributes or {}
|
||||||
self.last_updated = last_updated or dt.datetime.now()
|
self.last_updated = date_util.strip_microseconds(
|
||||||
|
last_updated or date_util.utcnow())
|
||||||
|
|
||||||
# Strip microsecond from last_changed else we cannot guarantee
|
# Strip microsecond from last_changed else we cannot guarantee
|
||||||
# state == State.from_dict(state.as_dict())
|
# state == State.from_dict(state.as_dict())
|
||||||
# This behavior occurs because to_dict uses datetime_to_str
|
# This behavior occurs because to_dict uses datetime_to_str
|
||||||
# which does not preserve microseconds
|
# which does not preserve microseconds
|
||||||
self.last_changed = util.strip_microseconds(
|
self.last_changed = date_util.strip_microseconds(
|
||||||
last_changed or self.last_updated)
|
last_changed or self.last_updated)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -510,8 +545,8 @@ class State(object):
|
|||||||
return {'entity_id': self.entity_id,
|
return {'entity_id': self.entity_id,
|
||||||
'state': self.state,
|
'state': self.state,
|
||||||
'attributes': self.attributes,
|
'attributes': self.attributes,
|
||||||
'last_changed': util.datetime_to_str(self.last_changed),
|
'last_changed': date_util.datetime_to_str(self.last_changed),
|
||||||
'last_updated': util.datetime_to_str(self.last_updated)}
|
'last_updated': date_util.datetime_to_str(self.last_updated)}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, json_dict):
|
def from_dict(cls, json_dict):
|
||||||
@ -526,12 +561,12 @@ class State(object):
|
|||||||
last_changed = json_dict.get('last_changed')
|
last_changed = json_dict.get('last_changed')
|
||||||
|
|
||||||
if last_changed:
|
if last_changed:
|
||||||
last_changed = util.str_to_datetime(last_changed)
|
last_changed = date_util.str_to_datetime(last_changed)
|
||||||
|
|
||||||
last_updated = json_dict.get('last_updated')
|
last_updated = json_dict.get('last_updated')
|
||||||
|
|
||||||
if last_updated:
|
if last_updated:
|
||||||
last_updated = util.str_to_datetime(last_updated)
|
last_updated = date_util.str_to_datetime(last_updated)
|
||||||
|
|
||||||
return cls(json_dict['entity_id'], json_dict['state'],
|
return cls(json_dict['entity_id'], json_dict['state'],
|
||||||
json_dict.get('attributes'), last_changed, last_updated)
|
json_dict.get('attributes'), last_changed, last_updated)
|
||||||
@ -548,7 +583,7 @@ class State(object):
|
|||||||
|
|
||||||
return "<state {}={}{} @ {}>".format(
|
return "<state {}={}{} @ {}>".format(
|
||||||
self.entity_id, self.state, attr,
|
self.entity_id, self.state, attr,
|
||||||
util.datetime_to_str(self.last_changed))
|
date_util.datetime_to_local_str(self.last_changed))
|
||||||
|
|
||||||
|
|
||||||
class StateMachine(object):
|
class StateMachine(object):
|
||||||
@ -585,7 +620,7 @@ class StateMachine(object):
|
|||||||
"""
|
"""
|
||||||
Returns all states that have been changed since point_in_time.
|
Returns all states that have been changed since point_in_time.
|
||||||
"""
|
"""
|
||||||
point_in_time = util.strip_microseconds(point_in_time)
|
point_in_time = date_util.strip_microseconds(point_in_time)
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return [state for state in self._states.values()
|
return [state for state in self._states.values()
|
||||||
@ -847,7 +882,7 @@ class Timer(threading.Thread):
|
|||||||
|
|
||||||
last_fired_on_second = -1
|
last_fired_on_second = -1
|
||||||
|
|
||||||
calc_now = dt.datetime.now
|
calc_now = date_util.utcnow
|
||||||
interval = self.interval
|
interval = self.interval
|
||||||
|
|
||||||
while not self._stop_event.isSet():
|
while not self._stop_event.isSet():
|
||||||
@ -873,7 +908,13 @@ class Timer(threading.Thread):
|
|||||||
|
|
||||||
last_fired_on_second = now.second
|
last_fired_on_second = now.second
|
||||||
|
|
||||||
self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
|
# Event might have been set while sleeping
|
||||||
|
if not self._stop_event.isSet():
|
||||||
|
try:
|
||||||
|
self.hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
|
||||||
|
except HomeAssistantError:
|
||||||
|
# HA raises error if firing event after it has shut down
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
|
@ -15,6 +15,7 @@ from collections import defaultdict
|
|||||||
|
|
||||||
import homeassistant
|
import homeassistant
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
import homeassistant.config as config_util
|
import homeassistant.config as config_util
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
import homeassistant.components as core_components
|
import homeassistant.components as core_components
|
||||||
@ -183,13 +184,27 @@ def process_ha_core_config(hass, config):
|
|||||||
""" Processes the [homeassistant] section from the config. """
|
""" Processes the [homeassistant] section from the config. """
|
||||||
hac = hass.config
|
hac = hass.config
|
||||||
|
|
||||||
|
def set_time_zone(time_zone_str):
|
||||||
|
""" Helper method to set time zone in HA. """
|
||||||
|
if time_zone_str is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
time_zone = date_util.get_time_zone(time_zone_str)
|
||||||
|
|
||||||
|
if time_zone:
|
||||||
|
hac.time_zone = time_zone
|
||||||
|
date_util.set_default_time_zone(time_zone)
|
||||||
|
else:
|
||||||
|
_LOGGER.error("Received invalid time zone %s", time_zone_str)
|
||||||
|
|
||||||
for key, attr in ((CONF_LATITUDE, 'latitude'),
|
for key, attr in ((CONF_LATITUDE, 'latitude'),
|
||||||
(CONF_LONGITUDE, 'longitude'),
|
(CONF_LONGITUDE, 'longitude'),
|
||||||
(CONF_NAME, 'location_name'),
|
(CONF_NAME, 'location_name')):
|
||||||
(CONF_TIME_ZONE, 'time_zone')):
|
|
||||||
if key in config:
|
if key in config:
|
||||||
setattr(hac, attr, config[key])
|
setattr(hac, attr, config[key])
|
||||||
|
|
||||||
|
set_time_zone(config.get(CONF_TIME_ZONE))
|
||||||
|
|
||||||
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items():
|
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items():
|
||||||
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
|
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
|
||||||
|
|
||||||
@ -202,7 +217,8 @@ def process_ha_core_config(hass, config):
|
|||||||
hac.temperature_unit = TEMP_FAHRENHEIT
|
hac.temperature_unit = TEMP_FAHRENHEIT
|
||||||
|
|
||||||
# If we miss some of the needed values, auto detect them
|
# If we miss some of the needed values, auto detect them
|
||||||
if None not in (hac.latitude, hac.longitude, hac.temperature_unit):
|
if None not in (
|
||||||
|
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.info('Auto detecting location and temperature unit')
|
_LOGGER.info('Auto detecting location and temperature unit')
|
||||||
@ -227,7 +243,7 @@ def process_ha_core_config(hass, config):
|
|||||||
hac.location_name = info.city
|
hac.location_name = info.city
|
||||||
|
|
||||||
if hac.time_zone is None:
|
if hac.time_zone is None:
|
||||||
hac.time_zone = info.time_zone
|
set_time_zone(info.time_zone)
|
||||||
|
|
||||||
|
|
||||||
def _ensure_loader_prepared(hass):
|
def _ensure_loader_prepared(hass):
|
||||||
|
@ -8,11 +8,12 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
|
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
|
||||||
@ -113,7 +114,7 @@ class DeviceTracker(object):
|
|||||||
""" Reload known devices file. """
|
""" Reload known devices file. """
|
||||||
self._read_known_devices_file()
|
self._read_known_devices_file()
|
||||||
|
|
||||||
self.update_devices(datetime.now())
|
self.update_devices(dt_util.utcnow())
|
||||||
|
|
||||||
dev_group.update_tracked_entity_ids(self.device_entity_ids)
|
dev_group.update_tracked_entity_ids(self.device_entity_ids)
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ class DeviceTracker(object):
|
|||||||
seconds = range(0, 60, seconds)
|
seconds = range(0, 60, seconds)
|
||||||
|
|
||||||
_LOGGER.info("Device tracker interval second=%s", seconds)
|
_LOGGER.info("Device tracker interval second=%s", seconds)
|
||||||
hass.track_time_change(update_device_state, second=seconds)
|
hass.track_utc_time_change(update_device_state, second=seconds)
|
||||||
|
|
||||||
hass.services.register(DOMAIN,
|
hass.services.register(DOMAIN,
|
||||||
SERVICE_DEVICE_TRACKER_RELOAD,
|
SERVICE_DEVICE_TRACKER_RELOAD,
|
||||||
@ -226,7 +227,7 @@ class DeviceTracker(object):
|
|||||||
self.untracked_devices.clear()
|
self.untracked_devices.clear()
|
||||||
|
|
||||||
with open(known_dev_path) as inp:
|
with open(known_dev_path) as inp:
|
||||||
default_last_seen = datetime(1990, 1, 1)
|
default_last_seen = dt_util.utcnow().replace(year=1990)
|
||||||
|
|
||||||
# To track which devices need an entity_id assigned
|
# To track which devices need an entity_id assigned
|
||||||
need_entity_id = []
|
need_entity_id = []
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "93774c7a1643c7e3f9cbbb1554b36683"
|
VERSION = "fdfcc1c10ff8713976c482931769a8e6"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -8,16 +8,17 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var timeFormatOptions = {hour: 'numeric', minute: '2-digit'};
|
var uiUtil = window.hass.uiUtil;
|
||||||
|
|
||||||
Polymer({
|
Polymer({
|
||||||
time: "",
|
time: "",
|
||||||
|
|
||||||
dateObjChanged: function(oldVal, newVal) {
|
dateObjChanged: function(oldVal, newVal) {
|
||||||
if (!newVal) {
|
if (newVal) {
|
||||||
|
this.time = uiUtil.formatTime(newVal);
|
||||||
|
} else {
|
||||||
this.time = "";
|
this.time = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.time = newVal.toLocaleTimeString([], timeFormatOptions);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<link rel="import" href="../resources/moment-js.html">
|
<link rel="import" href="../resources/moment-js.html">
|
||||||
|
|
||||||
<polymer-element name="relative-ha-datetime" attributes="datetime">
|
<polymer-element name="relative-ha-datetime" attributes="datetime datetimeObj">
|
||||||
<template>
|
<template>
|
||||||
{{ relativeTime }}
|
{{ relativeTime }}
|
||||||
</template>
|
</template>
|
||||||
@ -34,8 +34,15 @@
|
|||||||
this.updateRelative();
|
this.updateRelative();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
datetimeObjChanged: function(oldVal, newVal) {
|
||||||
|
this.parsedDateTime = newVal;
|
||||||
|
|
||||||
|
this.updateRelative();
|
||||||
|
},
|
||||||
|
|
||||||
updateRelative: function() {
|
updateRelative: function() {
|
||||||
this.relativeTime = this.parsedDateTime ? moment(this.parsedDateTime).fromNow() : "";
|
this.relativeTime = this.parsedDateTime ?
|
||||||
|
moment(this.parsedDateTime).fromNow() : "";
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
@ -37,8 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="time-ago">
|
<div class="time-ago">
|
||||||
<core-tooltip label="{{stateObj.lastChanged}}" position="bottom">
|
<core-tooltip label="{{stateObj.lastChangedAsDate | formatDateTime}}" position="bottom">
|
||||||
<relative-ha-datetime datetime="{{stateObj.lastChanged}}"></relative-ha-datetime>
|
<relative-ha-datetime datetimeObj="{{stateObj.lastChangedAsDate}}"></relative-ha-datetime>
|
||||||
</core-tooltip>
|
</core-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 56f896efa573aaa9554812a3c41b78278bce2064
|
Subproject commit 124b5df8ffb08bd7db22912865dba80845815e5d
|
@ -11,38 +11,43 @@
|
|||||||
|
|
||||||
<div layout justified horizontal class='data-entry' id='rising'>
|
<div layout justified horizontal class='data-entry' id='rising'>
|
||||||
<div class='key'>
|
<div class='key'>
|
||||||
Rising <relative-ha-datetime datetime="{{stateObj.attributes.next_rising}}"></relative-ha-datetime>
|
Rising <relative-ha-datetime datetimeObj="{{rising}}"></relative-ha-datetime>
|
||||||
</div>
|
</div>
|
||||||
<div class='value'>
|
<div class='value'>
|
||||||
{{stateObj.attributes.next_rising | HATimeStripDate}}
|
{{rising | formatTime}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div layout justified horizontal class='data-entry' id='setting'>
|
<div layout justified horizontal class='data-entry' id='setting'>
|
||||||
<div class='key'>
|
<div class='key'>
|
||||||
Setting <relative-ha-datetime datetime="{{stateObj.attributes.next_setting}}"></relative-ha-datetime>
|
Setting <relative-ha-datetime datetimeObj="{{setting}}"></relative-ha-datetime>
|
||||||
</div>
|
</div>
|
||||||
<div class='value'>
|
<div class='value'>
|
||||||
{{stateObj.attributes.next_setting | HATimeStripDate}}
|
{{setting | formatTime}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
(function() {
|
||||||
var parseDateTime = window.hass.util.parseDateTime;
|
var parseDateTime = window.hass.util.parseDateTime;
|
||||||
|
|
||||||
Polymer({
|
Polymer({
|
||||||
stateObjChanged: function() {
|
rising: null,
|
||||||
var rising = parseDateTime(this.stateObj.attributes.next_rising);
|
setting: null,
|
||||||
var setting = parseDateTime(this.stateObj.attributes.next_setting);
|
|
||||||
|
|
||||||
if(rising > setting) {
|
stateObjChanged: function() {
|
||||||
|
this.rising = parseDateTime(this.stateObj.attributes.next_rising);
|
||||||
|
this.setting = parseDateTime(this.stateObj.attributes.next_setting);
|
||||||
|
|
||||||
|
if(self.rising > self.setting) {
|
||||||
this.$.sunData.appendChild(this.$.rising);
|
this.$.sunData.appendChild(this.$.rising);
|
||||||
} else {
|
} else {
|
||||||
this.$.sunData.appendChild(this.$.setting);
|
this.$.sunData.appendChild(this.$.setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</polymer-element>
|
</polymer-element>
|
||||||
|
@ -7,18 +7,6 @@
|
|||||||
'light', 'group', 'sun', 'configurator', 'thermostat', 'script'
|
'light', 'group', 'sun', 'configurator', 'thermostat', 'script'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Register some polymer filters
|
|
||||||
|
|
||||||
PolymerExpressions.prototype.HATimeToDate = function(timeString) {
|
|
||||||
if (!timeString) return;
|
|
||||||
|
|
||||||
return window.hass.util.parseDateTime(timeString);
|
|
||||||
};
|
|
||||||
|
|
||||||
PolymerExpressions.prototype.HATimeStripDate = function(timeString) {
|
|
||||||
return (timeString || "").split(' ')[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add some frontend specific helpers to the models
|
// Add some frontend specific helpers to the models
|
||||||
Object.defineProperties(window.hass.stateModel.prototype, {
|
Object.defineProperties(window.hass.stateModel.prototype, {
|
||||||
// how to render the card for this state
|
// how to render the card for this state
|
||||||
@ -81,3 +69,5 @@
|
|||||||
window.hass.uiUtil = {};
|
window.hass.uiUtil = {};
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<link rel="import" href="./moment-js.html">
|
||||||
|
@ -3,3 +3,22 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script src="../bower_components/moment/moment.js"></script>
|
<script src="../bower_components/moment/moment.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.hass.uiUtil.formatTime = function(dateObj) {
|
||||||
|
return moment(dateObj).format('LT');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.hass.uiUtil.formatDateTime = function(dateObj) {
|
||||||
|
return moment(dateObj).format('lll');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.hass.uiUtil.formatDate = function(dateObj) {
|
||||||
|
return moment(dateObj).format('ll');
|
||||||
|
};
|
||||||
|
|
||||||
|
PolymerExpressions.prototype.formatTime = window.hass.uiUtil.formatTime;
|
||||||
|
PolymerExpressions.prototype.formatDateTime = window.hass.uiUtil.formatDateTime;
|
||||||
|
PolymerExpressions.prototype.formatDate = window.hass.uiUtil.formatDate;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@ -5,10 +5,11 @@ homeassistant.components.history
|
|||||||
Provide pre-made queries on top of the recorder component.
|
Provide pre-made queries on top of the recorder component.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
import homeassistant.components.recorder as recorder
|
import homeassistant.components.recorder as recorder
|
||||||
|
|
||||||
DOMAIN = 'history'
|
DOMAIN = 'history'
|
||||||
@ -22,7 +23,7 @@ def last_5_states(entity_id):
|
|||||||
query = """
|
query = """
|
||||||
SELECT * FROM states WHERE entity_id=? AND
|
SELECT * FROM states WHERE entity_id=? AND
|
||||||
last_changed=last_updated
|
last_changed=last_updated
|
||||||
ORDER BY last_changed DESC LIMIT 0, 5
|
ORDER BY state_id DESC LIMIT 0, 5
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return recorder.query_states(query, (entity_id, ))
|
return recorder.query_states(query, (entity_id, ))
|
||||||
@ -30,7 +31,7 @@ def last_5_states(entity_id):
|
|||||||
|
|
||||||
def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
||||||
"""
|
"""
|
||||||
Return states changes during period start_time - end_time.
|
Return states changes during UTC period start_time - end_time.
|
||||||
"""
|
"""
|
||||||
where = "last_changed=last_updated AND last_changed > ? "
|
where = "last_changed=last_updated AND last_changed > ? "
|
||||||
data = [start_time]
|
data = [start_time]
|
||||||
@ -64,17 +65,17 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_states(point_in_time, entity_ids=None, run=None):
|
def get_states(utc_point_in_time, entity_ids=None, run=None):
|
||||||
""" Returns the states at a specific point in time. """
|
""" Returns the states at a specific point in time. """
|
||||||
if run is None:
|
if run is None:
|
||||||
run = recorder.run_information(point_in_time)
|
run = recorder.run_information(utc_point_in_time)
|
||||||
|
|
||||||
# History did not run before point_in_time
|
# History did not run before utc_point_in_time
|
||||||
if run is None:
|
if run is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
where = run.where_after_start_run + "AND created < ? "
|
where = run.where_after_start_run + "AND created < ? "
|
||||||
where_data = [point_in_time]
|
where_data = [utc_point_in_time]
|
||||||
|
|
||||||
if entity_ids is not None:
|
if entity_ids is not None:
|
||||||
where += "AND entity_id IN ({}) ".format(
|
where += "AND entity_id IN ({}) ".format(
|
||||||
@ -93,9 +94,9 @@ def get_states(point_in_time, entity_ids=None, run=None):
|
|||||||
return recorder.query_states(query, where_data)
|
return recorder.query_states(query, where_data)
|
||||||
|
|
||||||
|
|
||||||
def get_state(point_in_time, entity_id, run=None):
|
def get_state(utc_point_in_time, entity_id, run=None):
|
||||||
""" Return a state at a specific point in time. """
|
""" Return a state at a specific point in time. """
|
||||||
states = get_states(point_in_time, (entity_id,), run)
|
states = get_states(utc_point_in_time, (entity_id,), run)
|
||||||
|
|
||||||
return states[0] if states else None
|
return states[0] if states else None
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ def _api_last_5_states(handler, path_match, data):
|
|||||||
def _api_history_period(handler, path_match, data):
|
def _api_history_period(handler, path_match, data):
|
||||||
""" Return history over a period of time. """
|
""" Return history over a period of time. """
|
||||||
# 1 day for now..
|
# 1 day for now..
|
||||||
start_time = datetime.now() - timedelta(seconds=86400)
|
start_time = date_util.utcnow() - timedelta(seconds=86400)
|
||||||
|
|
||||||
entity_id = data.get('filter_entity_id')
|
entity_id = data.get('filter_entity_id')
|
||||||
|
|
||||||
|
@ -4,14 +4,13 @@ homeassistant.components.logbook
|
|||||||
|
|
||||||
Parses events and generates a human log
|
Parses events and generates a human log
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from homeassistant import State, DOMAIN as HA_DOMAIN
|
from homeassistant import State, DOMAIN as HA_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
|
EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF,
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||||
import homeassistant.util as util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.components.recorder as recorder
|
import homeassistant.components.recorder as recorder
|
||||||
import homeassistant.components.sun as sun
|
import homeassistant.components.sun as sun
|
||||||
|
|
||||||
@ -38,10 +37,11 @@ def setup(hass, config):
|
|||||||
|
|
||||||
def _handle_get_logbook(handler, path_match, data):
|
def _handle_get_logbook(handler, path_match, data):
|
||||||
""" Return logbook entries. """
|
""" Return logbook entries. """
|
||||||
start_today = datetime.now().date()
|
start_today = dt_util.now().replace(hour=0, minute=0, second=0)
|
||||||
|
|
||||||
handler.write_json(humanify(
|
handler.write_json(humanify(
|
||||||
recorder.query_events(QUERY_EVENTS_AFTER, (start_today,))))
|
recorder.query_events(
|
||||||
|
QUERY_EVENTS_AFTER, (dt_util.as_utc(start_today),))))
|
||||||
|
|
||||||
|
|
||||||
class Entry(object):
|
class Entry(object):
|
||||||
@ -60,7 +60,7 @@ class Entry(object):
|
|||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Convert Entry to a dict to be used within JSON. """
|
""" Convert Entry to a dict to be used within JSON. """
|
||||||
return {
|
return {
|
||||||
'when': util.datetime_to_str(self.when),
|
'when': dt_util.datetime_to_str(self.when),
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'message': self.message,
|
'message': self.message,
|
||||||
'domain': self.domain,
|
'domain': self.domain,
|
||||||
|
@ -10,11 +10,11 @@ import threading
|
|||||||
import queue
|
import queue
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
import time
|
|
||||||
import json
|
import json
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
from homeassistant import Event, EventOrigin, State
|
from homeassistant import Event, EventOrigin, State
|
||||||
|
import homeassistant.util.dt as date_util
|
||||||
from homeassistant.remote import JSONEncoder
|
from homeassistant.remote import JSONEncoder
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||||
@ -60,8 +60,9 @@ def row_to_state(row):
|
|||||||
""" Convert a databsae row to a state. """
|
""" Convert a databsae row to a state. """
|
||||||
try:
|
try:
|
||||||
return State(
|
return State(
|
||||||
row[1], row[2], json.loads(row[3]), datetime.fromtimestamp(row[4]),
|
row[1], row[2], json.loads(row[3]),
|
||||||
datetime.fromtimestamp(row[5]))
|
date_util.utc_from_timestamp(row[4]),
|
||||||
|
date_util.utc_from_timestamp(row[5]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# When json.loads fails
|
# When json.loads fails
|
||||||
_LOGGER.exception("Error converting row to state: %s", row)
|
_LOGGER.exception("Error converting row to state: %s", row)
|
||||||
@ -72,7 +73,7 @@ def row_to_event(row):
|
|||||||
""" Convert a databse row to an event. """
|
""" Convert a databse row to an event. """
|
||||||
try:
|
try:
|
||||||
return Event(row[1], json.loads(row[2]), EventOrigin[row[3].lower()],
|
return Event(row[1], json.loads(row[2]), EventOrigin[row[3].lower()],
|
||||||
datetime.fromtimestamp(row[5]))
|
date_util.utc_from_timestamp(row[5]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# When json.loads fails
|
# When json.loads fails
|
||||||
_LOGGER.exception("Error converting row to event: %s", row)
|
_LOGGER.exception("Error converting row to event: %s", row)
|
||||||
@ -113,10 +114,10 @@ class RecorderRun(object):
|
|||||||
self.start = _INSTANCE.recording_start
|
self.start = _INSTANCE.recording_start
|
||||||
self.closed_incorrect = False
|
self.closed_incorrect = False
|
||||||
else:
|
else:
|
||||||
self.start = datetime.fromtimestamp(row[1])
|
self.start = date_util.utc_from_timestamp(row[1])
|
||||||
|
|
||||||
if row[2] is not None:
|
if row[2] is not None:
|
||||||
self.end = datetime.fromtimestamp(row[2])
|
self.end = date_util.utc_from_timestamp(row[2])
|
||||||
|
|
||||||
self.closed_incorrect = bool(row[3])
|
self.closed_incorrect = bool(row[3])
|
||||||
|
|
||||||
@ -166,7 +167,8 @@ class Recorder(threading.Thread):
|
|||||||
self.queue = queue.Queue()
|
self.queue = queue.Queue()
|
||||||
self.quit_object = object()
|
self.quit_object = object()
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.recording_start = datetime.now()
|
self.recording_start = date_util.utcnow()
|
||||||
|
self.utc_offset = date_util.now().utcoffset().total_seconds()
|
||||||
|
|
||||||
def start_recording(event):
|
def start_recording(event):
|
||||||
""" Start recording. """
|
""" Start recording. """
|
||||||
@ -187,9 +189,11 @@ class Recorder(threading.Thread):
|
|||||||
if event == self.quit_object:
|
if event == self.quit_object:
|
||||||
self._close_run()
|
self._close_run()
|
||||||
self._close_connection()
|
self._close_connection()
|
||||||
|
self.queue.task_done()
|
||||||
return
|
return
|
||||||
|
|
||||||
elif event.event_type == EVENT_TIME_CHANGED:
|
elif event.event_type == EVENT_TIME_CHANGED:
|
||||||
|
self.queue.task_done()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif event.event_type == EVENT_STATE_CHANGED:
|
elif event.event_type == EVENT_STATE_CHANGED:
|
||||||
@ -197,6 +201,7 @@ class Recorder(threading.Thread):
|
|||||||
event.data['entity_id'], event.data.get('new_state'))
|
event.data['entity_id'], event.data.get('new_state'))
|
||||||
|
|
||||||
self.record_event(event)
|
self.record_event(event)
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
def event_listener(self, event):
|
def event_listener(self, event):
|
||||||
""" Listens for new events on the EventBus and puts them
|
""" Listens for new events on the EventBus and puts them
|
||||||
@ -209,31 +214,33 @@ class Recorder(threading.Thread):
|
|||||||
|
|
||||||
def record_state(self, entity_id, state):
|
def record_state(self, entity_id, state):
|
||||||
""" Save a state to the database. """
|
""" Save a state to the database. """
|
||||||
now = datetime.now()
|
now = date_util.utcnow()
|
||||||
|
|
||||||
|
# State got deleted
|
||||||
if state is None:
|
if state is None:
|
||||||
info = (entity_id, '', "{}", now, now, now)
|
info = (entity_id, '', "{}", now, now, now)
|
||||||
else:
|
else:
|
||||||
info = (
|
info = (
|
||||||
entity_id.lower(), state.state, json.dumps(state.attributes),
|
entity_id.lower(), state.state, json.dumps(state.attributes),
|
||||||
state.last_changed, state.last_updated, now)
|
state.last_changed, state.last_updated, now, self.utc_offset)
|
||||||
|
|
||||||
self.query(
|
self.query(
|
||||||
"INSERT INTO states ("
|
"INSERT INTO states ("
|
||||||
"entity_id, state, attributes, last_changed, last_updated,"
|
"entity_id, state, attributes, last_changed, last_updated,"
|
||||||
"created) VALUES (?, ?, ?, ?, ?, ?)", info)
|
"created, utc_offset) VALUES (?, ?, ?, ?, ?, ?, ?)", info)
|
||||||
|
|
||||||
def record_event(self, event):
|
def record_event(self, event):
|
||||||
""" Save an event to the database. """
|
""" Save an event to the database. """
|
||||||
info = (
|
info = (
|
||||||
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
event.event_type, json.dumps(event.data, cls=JSONEncoder),
|
||||||
str(event.origin), datetime.now(), event.time_fired,
|
str(event.origin), date_util.utcnow(), event.time_fired,
|
||||||
|
self.utc_offset
|
||||||
)
|
)
|
||||||
|
|
||||||
self.query(
|
self.query(
|
||||||
"INSERT INTO events ("
|
"INSERT INTO events ("
|
||||||
"event_type, event_data, origin, created, time_fired"
|
"event_type, event_data, origin, created, time_fired, utc_offset"
|
||||||
") VALUES (?, ?, ?, ?, ?)", info)
|
") VALUES (?, ?, ?, ?, ?, ?)", info)
|
||||||
|
|
||||||
def query(self, sql_query, data=None, return_value=None):
|
def query(self, sql_query, data=None, return_value=None):
|
||||||
""" Query the database. """
|
""" Query the database. """
|
||||||
@ -262,6 +269,10 @@ class Recorder(threading.Thread):
|
|||||||
"Error querying the database using: %s", sql_query)
|
"Error querying the database using: %s", sql_query)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def block_till_done(self):
|
||||||
|
""" Blocks till all events processed. """
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
def _setup_connection(self):
|
def _setup_connection(self):
|
||||||
""" Ensure database is ready to fly. """
|
""" Ensure database is ready to fly. """
|
||||||
db_path = self.hass.config.path(DB_FILE)
|
db_path = self.hass.config.path(DB_FILE)
|
||||||
@ -282,7 +293,7 @@ class Recorder(threading.Thread):
|
|||||||
def save_migration(migration_id):
|
def save_migration(migration_id):
|
||||||
""" Save and commit a migration to the database. """
|
""" Save and commit a migration to the database. """
|
||||||
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
cur.execute('INSERT INTO schema_version VALUES (?, ?)',
|
||||||
(migration_id, datetime.now()))
|
(migration_id, date_util.utcnow()))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
_LOGGER.info("Database migrated to version %d", migration_id)
|
_LOGGER.info("Database migrated to version %d", migration_id)
|
||||||
|
|
||||||
@ -297,7 +308,7 @@ class Recorder(threading.Thread):
|
|||||||
migration_id = 0
|
migration_id = 0
|
||||||
|
|
||||||
if migration_id < 1:
|
if migration_id < 1:
|
||||||
cur.execute("""
|
self.query("""
|
||||||
CREATE TABLE recorder_runs (
|
CREATE TABLE recorder_runs (
|
||||||
run_id integer primary key,
|
run_id integer primary key,
|
||||||
start integer,
|
start integer,
|
||||||
@ -306,7 +317,7 @@ class Recorder(threading.Thread):
|
|||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
cur.execute("""
|
self.query("""
|
||||||
CREATE TABLE events (
|
CREATE TABLE events (
|
||||||
event_id integer primary key,
|
event_id integer primary key,
|
||||||
event_type text,
|
event_type text,
|
||||||
@ -314,10 +325,10 @@ class Recorder(threading.Thread):
|
|||||||
origin text,
|
origin text,
|
||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
cur.execute(
|
self.query(
|
||||||
'CREATE INDEX events__event_type ON events(event_type)')
|
'CREATE INDEX events__event_type ON events(event_type)')
|
||||||
|
|
||||||
cur.execute("""
|
self.query("""
|
||||||
CREATE TABLE states (
|
CREATE TABLE states (
|
||||||
state_id integer primary key,
|
state_id integer primary key,
|
||||||
entity_id text,
|
entity_id text,
|
||||||
@ -327,20 +338,44 @@ class Recorder(threading.Thread):
|
|||||||
last_updated integer,
|
last_updated integer,
|
||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
cur.execute('CREATE INDEX states__entity_id ON states(entity_id)')
|
self.query('CREATE INDEX states__entity_id ON states(entity_id)')
|
||||||
|
|
||||||
save_migration(1)
|
save_migration(1)
|
||||||
|
|
||||||
if migration_id < 2:
|
if migration_id < 2:
|
||||||
cur.execute("""
|
self.query("""
|
||||||
ALTER TABLE events
|
ALTER TABLE events
|
||||||
ADD COLUMN time_fired integer
|
ADD COLUMN time_fired integer
|
||||||
""")
|
""")
|
||||||
|
|
||||||
cur.execute('UPDATE events SET time_fired=created')
|
self.query('UPDATE events SET time_fired=created')
|
||||||
|
|
||||||
save_migration(2)
|
save_migration(2)
|
||||||
|
|
||||||
|
if migration_id < 3:
|
||||||
|
utc_offset = self.utc_offset
|
||||||
|
|
||||||
|
self.query("""
|
||||||
|
ALTER TABLE recorder_runs
|
||||||
|
ADD COLUMN utc_offset integer
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.query("""
|
||||||
|
ALTER TABLE events
|
||||||
|
ADD COLUMN utc_offset integer
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.query("""
|
||||||
|
ALTER TABLE states
|
||||||
|
ADD COLUMN utc_offset integer
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.query("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
|
||||||
|
self.query("UPDATE events SET utc_offset=?", [utc_offset])
|
||||||
|
self.query("UPDATE states SET utc_offset=?", [utc_offset])
|
||||||
|
|
||||||
|
save_migration(3)
|
||||||
|
|
||||||
def _close_connection(self):
|
def _close_connection(self):
|
||||||
""" Close connection to the database. """
|
""" Close connection to the database. """
|
||||||
_LOGGER.info("Closing database")
|
_LOGGER.info("Closing database")
|
||||||
@ -357,18 +392,18 @@ class Recorder(threading.Thread):
|
|||||||
|
|
||||||
self.query(
|
self.query(
|
||||||
"INSERT INTO recorder_runs (start, created) VALUES (?, ?)",
|
"INSERT INTO recorder_runs (start, created) VALUES (?, ?)",
|
||||||
(self.recording_start, datetime.now()))
|
(self.recording_start, date_util.utcnow()))
|
||||||
|
|
||||||
def _close_run(self):
|
def _close_run(self):
|
||||||
""" Save end time for current run. """
|
""" Save end time for current run. """
|
||||||
self.query(
|
self.query(
|
||||||
"UPDATE recorder_runs SET end=? WHERE start=?",
|
"UPDATE recorder_runs SET end=? WHERE start=?",
|
||||||
(datetime.now(), self.recording_start))
|
(date_util.utcnow(), self.recording_start))
|
||||||
|
|
||||||
|
|
||||||
def _adapt_datetime(datetimestamp):
|
def _adapt_datetime(datetimestamp):
|
||||||
""" Turn a datetime into an integer for in the DB. """
|
""" Turn a datetime into an integer for in the DB. """
|
||||||
return time.mktime(datetimestamp.timetuple())
|
return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
|
||||||
|
|
||||||
|
|
||||||
def _verify_instance():
|
def _verify_instance():
|
||||||
|
@ -30,7 +30,7 @@ except ImportError:
|
|||||||
# Error will be raised during setup
|
# Error will be raised during setup
|
||||||
ephem = None
|
ephem = None
|
||||||
|
|
||||||
from homeassistant.util import str_to_datetime, datetime_to_str
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.components.scheduler import ServiceEventListener
|
from homeassistant.components.scheduler import ServiceEventListener
|
||||||
|
|
||||||
@ -55,13 +55,21 @@ def is_on(hass, entity_id=None):
|
|||||||
|
|
||||||
|
|
||||||
def next_setting(hass, entity_id=None):
|
def next_setting(hass, entity_id=None):
|
||||||
""" Returns the datetime object representing the next sun setting. """
|
""" Returns the local datetime object of the next sun setting. """
|
||||||
|
utc_next = next_setting_utc(hass, entity_id)
|
||||||
|
|
||||||
|
return dt_util.as_local(utc_next) if utc_next else None
|
||||||
|
|
||||||
|
|
||||||
|
def next_setting_utc(hass, entity_id=None):
|
||||||
|
""" Returns the UTC datetime object of the next sun setting. """
|
||||||
entity_id = entity_id or ENTITY_ID
|
entity_id = entity_id or ENTITY_ID
|
||||||
|
|
||||||
state = hass.states.get(ENTITY_ID)
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return str_to_datetime(state.attributes[STATE_ATTR_NEXT_SETTING])
|
return dt_util.str_to_datetime(
|
||||||
|
state.attributes[STATE_ATTR_NEXT_SETTING])
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
# AttributeError if state is None
|
# AttributeError if state is None
|
||||||
# KeyError if STATE_ATTR_NEXT_SETTING does not exist
|
# KeyError if STATE_ATTR_NEXT_SETTING does not exist
|
||||||
@ -69,13 +77,21 @@ def next_setting(hass, entity_id=None):
|
|||||||
|
|
||||||
|
|
||||||
def next_rising(hass, entity_id=None):
|
def next_rising(hass, entity_id=None):
|
||||||
""" Returns the datetime object representing the next sun rising. """
|
""" Returns the local datetime object of the next sun rising. """
|
||||||
|
utc_next = next_rising_utc(hass, entity_id)
|
||||||
|
|
||||||
|
return dt_util.as_local(utc_next) if utc_next else None
|
||||||
|
|
||||||
|
|
||||||
|
def next_rising_utc(hass, entity_id=None):
|
||||||
|
""" Returns the UTC datetime object of the next sun rising. """
|
||||||
entity_id = entity_id or ENTITY_ID
|
entity_id = entity_id or ENTITY_ID
|
||||||
|
|
||||||
state = hass.states.get(ENTITY_ID)
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return str_to_datetime(state.attributes[STATE_ATTR_NEXT_RISING])
|
return dt_util.str_to_datetime(
|
||||||
|
state.attributes[STATE_ATTR_NEXT_RISING])
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
# AttributeError if state is None
|
# AttributeError if state is None
|
||||||
# KeyError if STATE_ATTR_NEXT_RISING does not exist
|
# KeyError if STATE_ATTR_NEXT_RISING does not exist
|
||||||
@ -94,15 +110,15 @@ def setup(hass, config):
|
|||||||
logger.error("Latitude or longitude not set in Home Assistant config")
|
logger.error("Latitude or longitude not set in Home Assistant config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sun.point_in_time_listener(datetime.now())
|
sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Raised when invalid latitude or longitude is given to Observer
|
# Raised when invalid latitude or longitude is given to Observer
|
||||||
logger.exception("Invalid value for latitude or longitude")
|
logger.exception("Invalid value for latitude or longitude")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
sun.point_in_time_listener(dt_util.utcnow())
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -113,8 +129,11 @@ class Sun(Entity):
|
|||||||
|
|
||||||
def __init__(self, hass, latitude, longitude):
|
def __init__(self, hass, latitude, longitude):
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.latitude = latitude
|
self.observer = ephem.Observer()
|
||||||
self.longitude = longitude
|
# pylint: disable=assigning-non-slot
|
||||||
|
self.observer.lat = latitude
|
||||||
|
# pylint: disable=assigning-non-slot
|
||||||
|
self.observer.long = longitude
|
||||||
|
|
||||||
self._state = self.next_rising = self.next_setting = None
|
self._state = self.next_rising = self.next_setting = None
|
||||||
|
|
||||||
@ -137,8 +156,8 @@ class Sun(Entity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
return {
|
return {
|
||||||
STATE_ATTR_NEXT_RISING: datetime_to_str(self.next_rising),
|
STATE_ATTR_NEXT_RISING: dt_util.datetime_to_str(self.next_rising),
|
||||||
STATE_ATTR_NEXT_SETTING: datetime_to_str(self.next_setting)
|
STATE_ATTR_NEXT_SETTING: dt_util.datetime_to_str(self.next_setting)
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -146,22 +165,19 @@ class Sun(Entity):
|
|||||||
""" Returns the datetime when the next change to the state is. """
|
""" Returns the datetime when the next change to the state is. """
|
||||||
return min(self.next_rising, self.next_setting)
|
return min(self.next_rising, self.next_setting)
|
||||||
|
|
||||||
def update_as_of(self, point_in_time):
|
def update_as_of(self, utc_point_in_time):
|
||||||
""" Calculate sun state at a point in time. """
|
""" Calculate sun state at a point in UTC time. """
|
||||||
utc_offset = datetime.utcnow() - datetime.now()
|
|
||||||
utc_now = point_in_time + utc_offset
|
|
||||||
|
|
||||||
sun = ephem.Sun() # pylint: disable=no-member
|
sun = ephem.Sun() # pylint: disable=no-member
|
||||||
|
|
||||||
# Setting invalid latitude and longitude to observer raises ValueError
|
# pylint: disable=assigning-non-slot
|
||||||
observer = ephem.Observer()
|
self.observer.date = ephem.date(utc_point_in_time)
|
||||||
observer.lat = self.latitude # pylint: disable=assigning-non-slot
|
|
||||||
observer.long = self.longitude # pylint: disable=assigning-non-slot
|
|
||||||
|
|
||||||
self.next_rising = ephem.localtime(
|
self.next_rising = self.observer.next_rising(
|
||||||
observer.next_rising(sun, start=utc_now))
|
sun,
|
||||||
self.next_setting = ephem.localtime(
|
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
observer.next_setting(sun, start=utc_now))
|
self.next_setting = self.observer.next_setting(
|
||||||
|
sun,
|
||||||
|
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
def point_in_time_listener(self, now):
|
def point_in_time_listener(self, now):
|
||||||
""" Called when the state of the sun has changed. """
|
""" Called when the state of the sun has changed. """
|
||||||
@ -169,8 +185,9 @@ class Sun(Entity):
|
|||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
# Schedule next update at next_change+1 second so sun state has changed
|
# Schedule next update at next_change+1 second so sun state has changed
|
||||||
self.hass.track_point_in_time(self.point_in_time_listener,
|
self.hass.track_point_in_utc_time(
|
||||||
self.next_change + timedelta(seconds=1))
|
self.point_in_time_listener,
|
||||||
|
self.next_change + timedelta(seconds=1))
|
||||||
|
|
||||||
|
|
||||||
def create_event_listener(schedule, event_listener_data):
|
def create_event_listener(schedule, event_listener_data):
|
||||||
|
@ -5,9 +5,9 @@ homeassistant.helpers.state
|
|||||||
Helpers that help with state related things.
|
Helpers that help with state related things.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from homeassistant import State
|
from homeassistant import State
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class TrackStates(object):
|
|||||||
self.states = []
|
self.states = []
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.now = datetime.now()
|
self.now = dt_util.utcnow()
|
||||||
return self.states
|
return self.states
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
@ -8,7 +8,7 @@ import collections
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
import threading
|
import threading
|
||||||
import queue
|
import queue
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
import enum
|
import enum
|
||||||
import socket
|
import socket
|
||||||
@ -18,12 +18,17 @@ from functools import wraps
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package
|
||||||
|
# pylint: disable=unused-import
|
||||||
|
from .dt import ( # noqa
|
||||||
|
datetime_to_str, str_to_datetime, strip_microseconds,
|
||||||
|
datetime_to_local_str, utcnow)
|
||||||
|
|
||||||
|
|
||||||
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
||||||
RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)')
|
RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)')
|
||||||
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
||||||
|
|
||||||
DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y"
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize_filename(filename):
|
def sanitize_filename(filename):
|
||||||
""" Sanitizes a filename by removing .. / and \\. """
|
""" Sanitizes a filename by removing .. / and \\. """
|
||||||
@ -42,33 +47,6 @@ def slugify(text):
|
|||||||
return RE_SLUGIFY.sub("", text)
|
return RE_SLUGIFY.sub("", text)
|
||||||
|
|
||||||
|
|
||||||
def datetime_to_str(dattim):
|
|
||||||
""" Converts datetime to a string format.
|
|
||||||
|
|
||||||
@rtype : str
|
|
||||||
"""
|
|
||||||
return dattim.strftime(DATE_STR_FORMAT)
|
|
||||||
|
|
||||||
|
|
||||||
def str_to_datetime(dt_str):
|
|
||||||
""" Converts a string to a datetime object.
|
|
||||||
|
|
||||||
@rtype: datetime
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return datetime.strptime(dt_str, DATE_STR_FORMAT)
|
|
||||||
except ValueError: # If dt_str did not match our format
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def strip_microseconds(dattim):
|
|
||||||
""" Returns a copy of dattime object but with microsecond set to 0. """
|
|
||||||
if dattim.microsecond:
|
|
||||||
return dattim - timedelta(microseconds=dattim.microsecond)
|
|
||||||
else:
|
|
||||||
return dattim
|
|
||||||
|
|
||||||
|
|
||||||
def split_entity_id(entity_id):
|
def split_entity_id(entity_id):
|
||||||
""" Splits a state entity_id into domain, object_id. """
|
""" Splits a state entity_id into domain, object_id. """
|
||||||
return entity_id.split(".", 1)
|
return entity_id.split(".", 1)
|
||||||
@ -81,7 +59,7 @@ def repr_helper(inp):
|
|||||||
repr_helper(key)+"="+repr_helper(item) for key, item
|
repr_helper(key)+"="+repr_helper(item) for key, item
|
||||||
in inp.items())
|
in inp.items())
|
||||||
elif isinstance(inp, datetime):
|
elif isinstance(inp, datetime):
|
||||||
return datetime_to_str(inp)
|
return datetime_to_local_str(inp)
|
||||||
else:
|
else:
|
||||||
return str(inp)
|
return str(inp)
|
||||||
|
|
||||||
@ -464,7 +442,7 @@ class ThreadPool(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Add to current running jobs
|
# Add to current running jobs
|
||||||
job_log = (datetime.now(), job)
|
job_log = (utcnow(), job)
|
||||||
self.current_jobs.append(job_log)
|
self.current_jobs.append(job_log)
|
||||||
|
|
||||||
# Do the job
|
# Do the job
|
96
homeassistant/util/dt.py
Normal file
96
homeassistant/util/dt.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.util.dt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Provides helper methods to handle the time in HA.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
DATE_STR_FORMAT = "%H:%M:%S %d-%m-%Y"
|
||||||
|
UTC = DEFAULT_TIME_ZONE = pytz.utc
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_time_zone(time_zone):
|
||||||
|
""" Sets a default time zone to be used when none is specified. """
|
||||||
|
global DEFAULT_TIME_ZONE # pylint: disable=global-statement
|
||||||
|
|
||||||
|
assert isinstance(time_zone, dt.tzinfo)
|
||||||
|
|
||||||
|
DEFAULT_TIME_ZONE = time_zone
|
||||||
|
|
||||||
|
|
||||||
|
def get_time_zone(time_zone_str):
|
||||||
|
""" Get time zone from string. Return None if unable to determine. """
|
||||||
|
try:
|
||||||
|
return pytz.timezone(time_zone_str)
|
||||||
|
except pytz.exceptions.UnknownTimeZoneError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def utcnow():
|
||||||
|
""" Get now in UTC time. """
|
||||||
|
return dt.datetime.now(pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def now(time_zone=None):
|
||||||
|
""" Get now in specified time zone. """
|
||||||
|
return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
|
def as_utc(dattim):
|
||||||
|
""" Return a datetime as UTC time.
|
||||||
|
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE. """
|
||||||
|
if dattim.tzinfo == pytz.utc:
|
||||||
|
return dattim
|
||||||
|
elif dattim.tzinfo is None:
|
||||||
|
dattim = dattim.replace(tzinfo=DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
return dattim.astimezone(pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def as_local(dattim):
|
||||||
|
""" Converts a UTC datetime object to local time_zone. """
|
||||||
|
if dattim.tzinfo == DEFAULT_TIME_ZONE:
|
||||||
|
return dattim
|
||||||
|
elif dattim.tzinfo is None:
|
||||||
|
dattim = dattim.replace(tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
return dattim.astimezone(DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
|
def utc_from_timestamp(timestamp):
|
||||||
|
""" Returns a UTC time from a timestamp. """
|
||||||
|
return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_to_local_str(dattim, time_zone=None):
|
||||||
|
""" Converts datetime to specified time_zone and returns a string. """
|
||||||
|
return datetime_to_str(as_local(dattim))
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_to_str(dattim):
|
||||||
|
""" Converts datetime to a string format.
|
||||||
|
|
||||||
|
@rtype : str
|
||||||
|
"""
|
||||||
|
return dattim.strftime(DATE_STR_FORMAT)
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_datetime(dt_str):
|
||||||
|
""" Converts a string to a UTC datetime object.
|
||||||
|
|
||||||
|
@rtype: datetime
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return dt.datetime.strptime(
|
||||||
|
dt_str, DATE_STR_FORMAT).replace(tzinfo=pytz.utc)
|
||||||
|
except ValueError: # If dt_str did not match our format
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def strip_microseconds(dattim):
|
||||||
|
""" Returns a copy of dattime object but with microsecond set to 0. """
|
||||||
|
return dattim.replace(microsecond=0)
|
@ -1,6 +1,7 @@
|
|||||||
# required for Home Assistant core
|
# required for Home Assistant core
|
||||||
requests>=2.0
|
requests>=2.0
|
||||||
pyyaml>=3.11
|
pyyaml>=3.11
|
||||||
|
pytz>=2015.2
|
||||||
|
|
||||||
# optional, needed for specific components
|
# optional, needed for specific components
|
||||||
|
|
||||||
|
@ -3,4 +3,8 @@ if [ ${PWD##*/} == "scripts" ]; then
|
|||||||
cd ..
|
cd ..
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -m unittest discover tests
|
if [ "$1" = "coverage" ]; then
|
||||||
|
coverage run -m unittest discover tests
|
||||||
|
else
|
||||||
|
python3 -m unittest discover tests
|
||||||
|
fi
|
||||||
|
@ -26,6 +26,10 @@ class MockScanner(object):
|
|||||||
""" Make a device leave the house. """
|
""" Make a device leave the house. """
|
||||||
self.devices_home.remove(device)
|
self.devices_home.remove(device)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
""" Resets which devices are home. """
|
||||||
|
self.devices_home = []
|
||||||
|
|
||||||
def scan_devices(self):
|
def scan_devices(self):
|
||||||
""" Returns a list of fake devices. """
|
""" Returns a list of fake devices. """
|
||||||
|
|
||||||
|
@ -5,10 +5,15 @@ tests.helper
|
|||||||
Helper method for writing tests.
|
Helper method for writing tests.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
|
from homeassistant.const import (
|
||||||
|
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED,
|
||||||
|
EVENT_STATE_CHANGED)
|
||||||
|
from homeassistant.components import sun
|
||||||
|
|
||||||
|
|
||||||
def get_test_config_dir():
|
def get_test_config_dir():
|
||||||
@ -16,10 +21,20 @@ def get_test_config_dir():
|
|||||||
return os.path.join(os.path.dirname(__file__), "config")
|
return os.path.join(os.path.dirname(__file__), "config")
|
||||||
|
|
||||||
|
|
||||||
def get_test_home_assistant():
|
def get_test_home_assistant(num_threads=None):
|
||||||
""" Returns a Home Assistant object pointing at test config dir. """
|
""" Returns a Home Assistant object pointing at test config dir. """
|
||||||
|
if num_threads:
|
||||||
|
orig_num_threads = ha.MIN_WORKER_THREAD
|
||||||
|
ha.MIN_WORKER_THREAD = num_threads
|
||||||
|
|
||||||
hass = ha.HomeAssistant()
|
hass = ha.HomeAssistant()
|
||||||
|
|
||||||
|
if num_threads:
|
||||||
|
ha.MIN_WORKER_THREAD = orig_num_threads
|
||||||
|
|
||||||
hass.config.config_dir = get_test_config_dir()
|
hass.config.config_dir = get_test_config_dir()
|
||||||
|
hass.config.latitude = 32.87336
|
||||||
|
hass.config.longitude = -117.22743
|
||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
@ -37,6 +52,44 @@ def mock_service(hass, domain, service):
|
|||||||
return calls
|
return calls
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_device_tracker_scan(hass):
|
||||||
|
""" Triggers the device tracker to scan. """
|
||||||
|
hass.bus.fire(
|
||||||
|
EVENT_TIME_CHANGED,
|
||||||
|
{'now':
|
||||||
|
dt_util.utcnow().replace(second=0) + timedelta(hours=1)})
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_sun_risen(hass):
|
||||||
|
""" Trigger sun to rise if below horizon. """
|
||||||
|
if not sun.is_on(hass):
|
||||||
|
hass.bus.fire(
|
||||||
|
EVENT_TIME_CHANGED,
|
||||||
|
{'now':
|
||||||
|
sun.next_rising_utc(hass) + timedelta(seconds=10)})
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_sun_set(hass):
|
||||||
|
""" Trigger sun to set if above horizon. """
|
||||||
|
if sun.is_on(hass):
|
||||||
|
hass.bus.fire(
|
||||||
|
EVENT_TIME_CHANGED,
|
||||||
|
{'now':
|
||||||
|
sun.next_setting_utc(hass) + timedelta(seconds=10)})
|
||||||
|
|
||||||
|
|
||||||
|
def mock_state_change_event(hass, new_state, old_state=None):
|
||||||
|
event_data = {
|
||||||
|
'entity_id': new_state.entity_id,
|
||||||
|
'new_state': new_state,
|
||||||
|
}
|
||||||
|
|
||||||
|
if old_state:
|
||||||
|
event_data['old_state'] = old_state
|
||||||
|
|
||||||
|
hass.bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||||
|
|
||||||
|
|
||||||
class MockModule(object):
|
class MockModule(object):
|
||||||
""" Provides a fake module. """
|
""" Provides a fake module. """
|
||||||
|
|
||||||
|
127
tests/test_component_device_sun_light_trigger.py
Normal file
127
tests/test_component_device_sun_light_trigger.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_device_sun_light_trigger
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests device sun light trigger component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import homeassistant.loader as loader
|
||||||
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
from homeassistant.components import (
|
||||||
|
device_tracker, light, sun, device_sun_light_trigger)
|
||||||
|
|
||||||
|
|
||||||
|
from helpers import (
|
||||||
|
get_test_home_assistant, ensure_sun_risen, ensure_sun_set,
|
||||||
|
trigger_device_tracker_scan)
|
||||||
|
|
||||||
|
|
||||||
|
KNOWN_DEV_PATH = None
|
||||||
|
|
||||||
|
|
||||||
|
def setUpModule(): # pylint: disable=invalid-name
|
||||||
|
""" Initalizes a Home Assistant server. """
|
||||||
|
global KNOWN_DEV_PATH
|
||||||
|
|
||||||
|
hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
loader.prepare(hass)
|
||||||
|
KNOWN_DEV_PATH = hass.config.path(
|
||||||
|
device_tracker.KNOWN_DEVICES_FILE)
|
||||||
|
|
||||||
|
hass.stop()
|
||||||
|
|
||||||
|
with open(KNOWN_DEV_PATH, 'w') as fil:
|
||||||
|
fil.write('device,name,track,picture\n')
|
||||||
|
fil.write('DEV1,device 1,1,http://example.com/dev1.jpg\n')
|
||||||
|
fil.write('DEV2,device 2,1,http://example.com/dev2.jpg\n')
|
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
|
""" Stops the Home Assistant server. """
|
||||||
|
os.remove(KNOWN_DEV_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeviceSunLightTrigger(unittest.TestCase):
|
||||||
|
""" Test the device sun light trigger module. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
self.scanner = loader.get_component(
|
||||||
|
'device_tracker.test').get_scanner(None, None)
|
||||||
|
|
||||||
|
self.scanner.reset()
|
||||||
|
self.scanner.come_home('DEV1')
|
||||||
|
|
||||||
|
loader.get_component('light.test').init()
|
||||||
|
|
||||||
|
device_tracker.setup(self.hass, {
|
||||||
|
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}
|
||||||
|
})
|
||||||
|
|
||||||
|
light.setup(self.hass, {
|
||||||
|
light.DOMAIN: {CONF_PLATFORM: 'test'}
|
||||||
|
})
|
||||||
|
|
||||||
|
sun.setup(self.hass, {})
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_lights_on_when_sun_sets(self):
|
||||||
|
""" Test lights go on when there is someone home and the sun sets. """
|
||||||
|
|
||||||
|
device_sun_light_trigger.setup(
|
||||||
|
self.hass, {device_sun_light_trigger.DOMAIN: {}})
|
||||||
|
|
||||||
|
ensure_sun_risen(self.hass)
|
||||||
|
|
||||||
|
light.turn_off(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
ensure_sun_set(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertTrue(light.is_on(self.hass))
|
||||||
|
|
||||||
|
def test_lights_turn_off_when_everyone_leaves(self):
|
||||||
|
""" Test lights turn off when everyone leaves the house. """
|
||||||
|
light.turn_on(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
device_sun_light_trigger.setup(
|
||||||
|
self.hass, {device_sun_light_trigger.DOMAIN: {}})
|
||||||
|
|
||||||
|
self.scanner.leave_home('DEV1')
|
||||||
|
|
||||||
|
trigger_device_tracker_scan(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertFalse(light.is_on(self.hass))
|
||||||
|
|
||||||
|
def test_lights_turn_on_when_coming_home_after_sun_set(self):
|
||||||
|
""" Test lights turn on when coming home after sun set. """
|
||||||
|
light.turn_off(self.hass)
|
||||||
|
|
||||||
|
ensure_sun_set(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
device_sun_light_trigger.setup(
|
||||||
|
self.hass, {device_sun_light_trigger.DOMAIN: {}})
|
||||||
|
|
||||||
|
self.scanner.come_home('DEV2')
|
||||||
|
trigger_device_tracker_scan(self.hass)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertTrue(light.is_on(self.hass))
|
@ -1,17 +1,18 @@
|
|||||||
"""
|
"""
|
||||||
tests.test_component_group
|
tests.test_component_device_tracker
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Tests the group compoments.
|
Tests the device tracker compoments.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM)
|
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM)
|
||||||
import homeassistant.components.device_tracker as device_tracker
|
import homeassistant.components.device_tracker as device_tracker
|
||||||
@ -80,6 +81,8 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
scanner = loader.get_component(
|
scanner = loader.get_component(
|
||||||
'device_tracker.test').get_scanner(None, None)
|
'device_tracker.test').get_scanner(None, None)
|
||||||
|
|
||||||
|
scanner.reset()
|
||||||
|
|
||||||
scanner.come_home('DEV1')
|
scanner.come_home('DEV1')
|
||||||
scanner.come_home('DEV2')
|
scanner.come_home('DEV2')
|
||||||
|
|
||||||
@ -116,7 +119,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
dev2 = device_tracker.ENTITY_ID_FORMAT.format('device_2')
|
dev2 = device_tracker.ENTITY_ID_FORMAT.format('device_2')
|
||||||
dev3 = device_tracker.ENTITY_ID_FORMAT.format('DEV3')
|
dev3 = device_tracker.ENTITY_ID_FORMAT.format('DEV3')
|
||||||
|
|
||||||
now = datetime.now()
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
# Device scanner scans every 12 seconds. We need to sync our times to
|
# Device scanner scans every 12 seconds. We need to sync our times to
|
||||||
# be every 12 seconds or else the time_changed event will be ignored.
|
# be every 12 seconds or else the time_changed event will be ignored.
|
137
tests/test_component_history.py
Normal file
137
tests/test_component_history.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_history
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the history component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.components import history, recorder, http
|
||||||
|
|
||||||
|
from helpers import get_test_home_assistant, mock_state_change_event
|
||||||
|
|
||||||
|
SERVER_PORT = 8126
|
||||||
|
|
||||||
|
|
||||||
|
class TestComponentHistory(unittest.TestCase):
|
||||||
|
""" Tests homeassistant.components.history module. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
""" Init needed objects. """
|
||||||
|
self.hass = get_test_home_assistant(1)
|
||||||
|
self.init_rec = False
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
if self.init_rec:
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
os.remove(self.hass.config.path(recorder.DB_FILE))
|
||||||
|
|
||||||
|
def init_recorder(self):
|
||||||
|
recorder.setup(self.hass, {})
|
||||||
|
self.hass.start()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
self.init_rec = True
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
""" Test setup method of history. """
|
||||||
|
http.setup(self.hass, {
|
||||||
|
http.DOMAIN: {http.CONF_SERVER_PORT: SERVER_PORT}})
|
||||||
|
self.assertTrue(history.setup(self.hass, {}))
|
||||||
|
|
||||||
|
def test_last_5_states(self):
|
||||||
|
""" Test retrieving the last 5 states. """
|
||||||
|
self.init_recorder()
|
||||||
|
states = []
|
||||||
|
|
||||||
|
entity_id = 'test.last_5_states'
|
||||||
|
|
||||||
|
for i in range(7):
|
||||||
|
self.hass.states.set(entity_id, "State {}".format(i))
|
||||||
|
|
||||||
|
if i > 1:
|
||||||
|
states.append(self.hass.states.get(entity_id))
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
list(reversed(states)), history.last_5_states(entity_id))
|
||||||
|
|
||||||
|
def test_get_states(self):
|
||||||
|
""" Test getting states at a specific point in time. """
|
||||||
|
self.init_recorder()
|
||||||
|
states = []
|
||||||
|
|
||||||
|
# Create 10 states for 5 different entities
|
||||||
|
# After the first 5, sleep a second and save the time
|
||||||
|
# history.get_states takes the latest states BEFORE point X
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
state = ha.State(
|
||||||
|
'test.point_in_time_{}'.format(i % 5),
|
||||||
|
"State {}".format(i),
|
||||||
|
{'attribute_test': i})
|
||||||
|
|
||||||
|
mock_state_change_event(self.hass, state)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
if i < 5:
|
||||||
|
states.append(state)
|
||||||
|
|
||||||
|
if i == 4:
|
||||||
|
time.sleep(1)
|
||||||
|
point = dt_util.utcnow()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
states,
|
||||||
|
sorted(
|
||||||
|
history.get_states(point), key=lambda state: state.entity_id))
|
||||||
|
|
||||||
|
# Test get_state here because we have a DB setup
|
||||||
|
self.assertEqual(
|
||||||
|
states[0], history.get_state(point, states[0].entity_id))
|
||||||
|
|
||||||
|
def test_state_changes_during_period(self):
|
||||||
|
self.init_recorder()
|
||||||
|
entity_id = 'media_player.test'
|
||||||
|
|
||||||
|
def set_state(state):
|
||||||
|
self.hass.states.set(entity_id, state)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
return self.hass.states.get(entity_id)
|
||||||
|
|
||||||
|
set_state('idle')
|
||||||
|
set_state('YouTube')
|
||||||
|
|
||||||
|
start = dt_util.utcnow()
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
states = [
|
||||||
|
set_state('idle'),
|
||||||
|
set_state('Netflix'),
|
||||||
|
set_state('Plex'),
|
||||||
|
set_state('YouTube'),
|
||||||
|
]
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
end = dt_util.utcnow()
|
||||||
|
|
||||||
|
set_state('Netflix')
|
||||||
|
set_state('Plex')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
{entity_id: states},
|
||||||
|
history.state_changes_during_period(start, end, entity_id))
|
98
tests/test_component_logbook.py
Normal file
98
tests/test_component_logbook.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_logbook
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the logbook component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
|
import unittest
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
|
from homeassistant.const import (
|
||||||
|
EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.components import logbook, http
|
||||||
|
|
||||||
|
from helpers import get_test_home_assistant
|
||||||
|
|
||||||
|
SERVER_PORT = 8127
|
||||||
|
|
||||||
|
|
||||||
|
class TestComponentHistory(unittest.TestCase):
|
||||||
|
""" Tests homeassistant.components.history module. """
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
""" Test setup method. """
|
||||||
|
try:
|
||||||
|
hass = get_test_home_assistant()
|
||||||
|
http.setup(hass, {
|
||||||
|
http.DOMAIN: {http.CONF_SERVER_PORT: SERVER_PORT}})
|
||||||
|
self.assertTrue(logbook.setup(hass, {}))
|
||||||
|
finally:
|
||||||
|
hass.stop()
|
||||||
|
|
||||||
|
def test_humanify_filter_sensor(self):
|
||||||
|
""" Test humanify filter too frequent sensor values. """
|
||||||
|
entity_id = 'sensor.bla'
|
||||||
|
|
||||||
|
pointA = dt_util.strip_microseconds(dt_util.utcnow().replace(minute=2))
|
||||||
|
pointB = pointA.replace(minute=5)
|
||||||
|
pointC = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
|
||||||
|
|
||||||
|
eventA = self.create_state_changed_event(pointA, entity_id, 10)
|
||||||
|
eventB = self.create_state_changed_event(pointB, entity_id, 20)
|
||||||
|
eventC = self.create_state_changed_event(pointC, entity_id, 30)
|
||||||
|
|
||||||
|
entries = list(logbook.humanify((eventA, eventB, eventC)))
|
||||||
|
|
||||||
|
self.assertEqual(2, len(entries))
|
||||||
|
self.assert_entry(
|
||||||
|
entries[0], pointB, 'bla', domain='sensor', entity_id=entity_id)
|
||||||
|
|
||||||
|
self.assert_entry(
|
||||||
|
entries[1], pointC, 'bla', domain='sensor', entity_id=entity_id)
|
||||||
|
|
||||||
|
def test_home_assistant_start_stop_grouped(self):
|
||||||
|
""" Tests if home assistant start and stop events are grouped if
|
||||||
|
occuring in the same minute. """
|
||||||
|
entries = list(logbook.humanify((
|
||||||
|
ha.Event(EVENT_HOMEASSISTANT_STOP),
|
||||||
|
ha.Event(EVENT_HOMEASSISTANT_START),
|
||||||
|
)))
|
||||||
|
|
||||||
|
self.assertEqual(1, len(entries))
|
||||||
|
self.assert_entry(
|
||||||
|
entries[0], name='Home Assistant', message='restarted',
|
||||||
|
domain=ha.DOMAIN)
|
||||||
|
|
||||||
|
def assert_entry(self, entry, when=None, name=None, message=None,
|
||||||
|
domain=None, entity_id=None):
|
||||||
|
""" Asserts an entry is what is expected """
|
||||||
|
if when:
|
||||||
|
self.assertEqual(when, entry.when)
|
||||||
|
|
||||||
|
if name:
|
||||||
|
self.assertEqual(name, entry.name)
|
||||||
|
|
||||||
|
if message:
|
||||||
|
self.assertEqual(message, entry.message)
|
||||||
|
|
||||||
|
if domain:
|
||||||
|
self.assertEqual(domain, entry.domain)
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
self.assertEqual(entity_id, entry.entity_id)
|
||||||
|
|
||||||
|
def create_state_changed_event(self, event_time_fired, entity_id, state):
|
||||||
|
""" Create state changed event. """
|
||||||
|
|
||||||
|
# Logbook only cares about state change events that
|
||||||
|
# contain an old state but will not actually act on it.
|
||||||
|
state = ha.State(entity_id, state).as_dict()
|
||||||
|
|
||||||
|
return ha.Event(EVENT_STATE_CHANGED, {
|
||||||
|
'entity_id': entity_id,
|
||||||
|
'old_state': state,
|
||||||
|
'new_state': state,
|
||||||
|
}, time_fired=event_time_fired)
|
70
tests/test_component_recorder.py
Normal file
70
tests/test_component_recorder.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
tests.test_component_recorder
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests Recorder component.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
|
||||||
|
from homeassistant.const import MATCH_ALL
|
||||||
|
from homeassistant.components import recorder
|
||||||
|
|
||||||
|
from helpers import get_test_home_assistant
|
||||||
|
|
||||||
|
|
||||||
|
class TestRecorder(unittest.TestCase):
|
||||||
|
""" Test the chromecast module. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
recorder.setup(self.hass, {})
|
||||||
|
self.hass.start()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
os.remove(self.hass.config.path(recorder.DB_FILE))
|
||||||
|
|
||||||
|
def test_saving_state(self):
|
||||||
|
""" Tests saving and restoring a state. """
|
||||||
|
entity_id = 'test.recorder'
|
||||||
|
state = 'restoring_from_db'
|
||||||
|
attributes = {'test_attr': 5, 'test_attr_10': 'nice'}
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, state, attributes)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
states = recorder.query_states('SELECT * FROM states')
|
||||||
|
|
||||||
|
self.assertEqual(1, len(states))
|
||||||
|
self.assertEqual(self.hass.states.get(entity_id), states[0])
|
||||||
|
|
||||||
|
def test_saving_event(self):
|
||||||
|
""" Tests saving and restoring an event. """
|
||||||
|
event_type = 'EVENT_TEST'
|
||||||
|
event_data = {'test_attr': 5, 'test_attr_10': 'nice'}
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def event_listener(event):
|
||||||
|
""" Records events from eventbus. """
|
||||||
|
if event.event_type == event_type:
|
||||||
|
events.append(event)
|
||||||
|
|
||||||
|
self.hass.bus.listen(MATCH_ALL, event_listener)
|
||||||
|
|
||||||
|
self.hass.bus.fire(event_type, event_data)
|
||||||
|
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
recorder._INSTANCE.block_till_done()
|
||||||
|
|
||||||
|
db_events = recorder.query_events(
|
||||||
|
'SELECT * FROM events WHERE event_type = ?', (event_type, ))
|
||||||
|
|
||||||
|
self.assertEqual(events, db_events)
|
@ -6,11 +6,12 @@ Tests Sun component.
|
|||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-public-methods,protected-access
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
import unittest
|
import unittest
|
||||||
import datetime as dt
|
from datetime import timedelta
|
||||||
|
|
||||||
import ephem
|
import ephem
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.components.sun as sun
|
import homeassistant.components.sun as sun
|
||||||
|
|
||||||
|
|
||||||
@ -42,22 +43,20 @@ class TestSun(unittest.TestCase):
|
|||||||
observer.lat = '32.87336' # pylint: disable=assigning-non-slot
|
observer.lat = '32.87336' # pylint: disable=assigning-non-slot
|
||||||
observer.long = '117.22743' # pylint: disable=assigning-non-slot
|
observer.long = '117.22743' # pylint: disable=assigning-non-slot
|
||||||
|
|
||||||
utc_now = dt.datetime.utcnow()
|
utc_now = dt_util.utcnow()
|
||||||
body_sun = ephem.Sun() # pylint: disable=no-member
|
body_sun = ephem.Sun() # pylint: disable=no-member
|
||||||
next_rising_dt = ephem.localtime(
|
next_rising_dt = observer.next_rising(
|
||||||
observer.next_rising(body_sun, start=utc_now))
|
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
next_setting_dt = ephem.localtime(
|
next_setting_dt = observer.next_setting(
|
||||||
observer.next_setting(body_sun, start=utc_now))
|
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
# Home Assistant strips out microseconds
|
# Home Assistant strips out microseconds
|
||||||
# strip it out of the datetime objects
|
# strip it out of the datetime objects
|
||||||
next_rising_dt = next_rising_dt - dt.timedelta(
|
next_rising_dt = dt_util.strip_microseconds(next_rising_dt)
|
||||||
microseconds=next_rising_dt.microsecond)
|
next_setting_dt = dt_util.strip_microseconds(next_setting_dt)
|
||||||
next_setting_dt = next_setting_dt - dt.timedelta(
|
|
||||||
microseconds=next_setting_dt.microsecond)
|
|
||||||
|
|
||||||
self.assertEqual(next_rising_dt, sun.next_rising(self.hass))
|
self.assertEqual(next_rising_dt, sun.next_rising_utc(self.hass))
|
||||||
self.assertEqual(next_setting_dt, sun.next_setting(self.hass))
|
self.assertEqual(next_setting_dt, sun.next_setting_utc(self.hass))
|
||||||
|
|
||||||
# Point it at a state without the proper attributes
|
# Point it at a state without the proper attributes
|
||||||
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON)
|
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON)
|
||||||
@ -84,7 +83,7 @@ class TestSun(unittest.TestCase):
|
|||||||
self.assertIsNotNone(test_time)
|
self.assertIsNotNone(test_time)
|
||||||
|
|
||||||
self.hass.bus.fire(ha.EVENT_TIME_CHANGED,
|
self.hass.bus.fire(ha.EVENT_TIME_CHANGED,
|
||||||
{ha.ATTR_NOW: test_time + dt.timedelta(seconds=5)})
|
{ha.ATTR_NOW: test_time + timedelta(seconds=5)})
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
@ -30,7 +30,11 @@ class TestHomeAssistant(unittest.TestCase):
|
|||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
try:
|
||||||
|
self.hass.stop()
|
||||||
|
except ha.HomeAssistantError:
|
||||||
|
# Already stopped after the block till stopped test
|
||||||
|
pass
|
||||||
|
|
||||||
def test_get_config_path(self):
|
def test_get_config_path(self):
|
||||||
""" Test get_config_path method. """
|
""" Test get_config_path method. """
|
||||||
@ -72,7 +76,7 @@ class TestHomeAssistant(unittest.TestCase):
|
|||||||
|
|
||||||
runs = []
|
runs = []
|
||||||
|
|
||||||
self.hass.track_point_in_time(
|
self.hass.track_point_in_utc_time(
|
||||||
lambda x: runs.append(1), birthday_paulus)
|
lambda x: runs.append(1), birthday_paulus)
|
||||||
|
|
||||||
self._send_time_changed(before_birthday)
|
self._send_time_changed(before_birthday)
|
||||||
@ -88,7 +92,7 @@ class TestHomeAssistant(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(runs))
|
self.assertEqual(1, len(runs))
|
||||||
|
|
||||||
self.hass.track_point_in_time(
|
self.hass.track_point_in_utc_time(
|
||||||
lambda x: runs.append(1), birthday_paulus)
|
lambda x: runs.append(1), birthday_paulus)
|
||||||
|
|
||||||
self._send_time_changed(after_birthday)
|
self._send_time_changed(after_birthday)
|
||||||
|
@ -35,17 +35,6 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual("Test_More", util.slugify("Test More"))
|
self.assertEqual("Test_More", util.slugify("Test More"))
|
||||||
self.assertEqual("Test_More", util.slugify("Test_(More)"))
|
self.assertEqual("Test_More", util.slugify("Test_(More)"))
|
||||||
|
|
||||||
def test_datetime_to_str(self):
|
|
||||||
""" Test datetime_to_str. """
|
|
||||||
self.assertEqual("12:00:00 09-07-1986",
|
|
||||||
util.datetime_to_str(datetime(1986, 7, 9, 12, 0, 0)))
|
|
||||||
|
|
||||||
def test_str_to_datetime(self):
|
|
||||||
""" Test str_to_datetime. """
|
|
||||||
self.assertEqual(datetime(1986, 7, 9, 12, 0, 0),
|
|
||||||
util.str_to_datetime("12:00:00 09-07-1986"))
|
|
||||||
self.assertIsNone(util.str_to_datetime("not a datetime string"))
|
|
||||||
|
|
||||||
def test_split_entity_id(self):
|
def test_split_entity_id(self):
|
||||||
""" Test split_entity_id. """
|
""" Test split_entity_id. """
|
||||||
self.assertEqual(['domain', 'object_id'],
|
self.assertEqual(['domain', 'object_id'],
|
||||||
|
137
tests/test_util_dt.py
Normal file
137
tests/test_util_dt.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
tests.test_util
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests Home Assistant date util methods.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-public-methods
|
||||||
|
import unittest
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
TEST_TIME_ZONE = 'America/Los_Angeles'
|
||||||
|
|
||||||
|
|
||||||
|
class TestDateUtil(unittest.TestCase):
|
||||||
|
""" Tests util date methods. """
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.orig_default_time_zone = dt_util.DEFAULT_TIME_ZONE
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
dt_util.set_default_time_zone(self.orig_default_time_zone)
|
||||||
|
|
||||||
|
def test_get_time_zone_retrieves_valid_time_zone(self):
|
||||||
|
""" Test getting a time zone. """
|
||||||
|
time_zone = dt_util.get_time_zone(TEST_TIME_ZONE)
|
||||||
|
|
||||||
|
self.assertIsNotNone(time_zone)
|
||||||
|
self.assertEqual(TEST_TIME_ZONE, time_zone.zone)
|
||||||
|
|
||||||
|
def test_get_time_zone_returns_none_for_garbage_time_zone(self):
|
||||||
|
""" Test getting a non existing time zone. """
|
||||||
|
time_zone = dt_util.get_time_zone("Non existing time zone")
|
||||||
|
|
||||||
|
self.assertIsNone(time_zone)
|
||||||
|
|
||||||
|
def test_set_default_time_zone(self):
|
||||||
|
""" Test setting default time zone. """
|
||||||
|
time_zone = dt_util.get_time_zone(TEST_TIME_ZONE)
|
||||||
|
|
||||||
|
dt_util.set_default_time_zone(time_zone)
|
||||||
|
|
||||||
|
# We cannot compare the timezones directly because of DST
|
||||||
|
self.assertEqual(time_zone.zone, dt_util.now().tzinfo.zone)
|
||||||
|
|
||||||
|
def test_utcnow(self):
|
||||||
|
""" Test the UTC now method. """
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
dt_util.utcnow().replace(tzinfo=None),
|
||||||
|
datetime.utcnow(),
|
||||||
|
delta=timedelta(seconds=1))
|
||||||
|
|
||||||
|
def test_now(self):
|
||||||
|
""" Test the now method. """
|
||||||
|
dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE))
|
||||||
|
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
dt_util.as_utc(dt_util.now()).replace(tzinfo=None),
|
||||||
|
datetime.utcnow(),
|
||||||
|
delta=timedelta(seconds=1))
|
||||||
|
|
||||||
|
def test_as_utc_with_naive_object(self):
|
||||||
|
utcnow = datetime.utcnow()
|
||||||
|
|
||||||
|
self.assertEqual(utcnow,
|
||||||
|
dt_util.as_utc(utcnow).replace(tzinfo=None))
|
||||||
|
|
||||||
|
def test_as_utc_with_utc_object(self):
|
||||||
|
utcnow = dt_util.utcnow()
|
||||||
|
|
||||||
|
self.assertEqual(utcnow, dt_util.as_utc(utcnow))
|
||||||
|
|
||||||
|
def test_as_utc_with_local_object(self):
|
||||||
|
dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE))
|
||||||
|
|
||||||
|
localnow = dt_util.now()
|
||||||
|
|
||||||
|
utcnow = dt_util.as_utc(localnow)
|
||||||
|
|
||||||
|
self.assertEqual(localnow, utcnow)
|
||||||
|
self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo)
|
||||||
|
|
||||||
|
def test_as_local_with_naive_object(self):
|
||||||
|
now = dt_util.now()
|
||||||
|
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
now, dt_util.as_local(datetime.utcnow()),
|
||||||
|
delta=timedelta(seconds=1))
|
||||||
|
|
||||||
|
def test_as_local_with_local_object(self):
|
||||||
|
now = dt_util.now()
|
||||||
|
|
||||||
|
self.assertEqual(now, now)
|
||||||
|
|
||||||
|
def test_as_local_with_utc_object(self):
|
||||||
|
dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE))
|
||||||
|
|
||||||
|
utcnow = dt_util.utcnow()
|
||||||
|
localnow = dt_util.as_local(utcnow)
|
||||||
|
|
||||||
|
self.assertEqual(localnow, utcnow)
|
||||||
|
self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo)
|
||||||
|
|
||||||
|
def test_utc_from_timestamp(self):
|
||||||
|
""" Test utc_from_timestamp method. """
|
||||||
|
self.assertEqual(
|
||||||
|
datetime(1986, 7, 9, tzinfo=dt_util.UTC),
|
||||||
|
dt_util.utc_from_timestamp(521251200))
|
||||||
|
|
||||||
|
def test_datetime_to_str(self):
|
||||||
|
""" Test datetime_to_str. """
|
||||||
|
self.assertEqual(
|
||||||
|
"12:00:00 09-07-1986",
|
||||||
|
dt_util.datetime_to_str(datetime(1986, 7, 9, 12, 0, 0)))
|
||||||
|
|
||||||
|
def test_datetime_to_local_str(self):
|
||||||
|
""" Test datetime_to_local_str. """
|
||||||
|
self.assertEqual(
|
||||||
|
dt_util.datetime_to_str(dt_util.now()),
|
||||||
|
dt_util.datetime_to_local_str(dt_util.utcnow()))
|
||||||
|
|
||||||
|
def test_str_to_datetime_converts_correctly(self):
|
||||||
|
""" Test str_to_datetime converts strings. """
|
||||||
|
self.assertEqual(
|
||||||
|
datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC),
|
||||||
|
dt_util.str_to_datetime("12:00:00 09-07-1986"))
|
||||||
|
|
||||||
|
def test_str_to_datetime_returns_none_for_incorrect_format(self):
|
||||||
|
""" Test str_to_datetime returns None if incorrect format. """
|
||||||
|
self.assertIsNone(dt_util.str_to_datetime("not a datetime string"))
|
||||||
|
|
||||||
|
def test_strip_microseconds(self):
|
||||||
|
test_time = datetime(2015, 1, 1, microsecond=5000)
|
||||||
|
|
||||||
|
self.assertNotEqual(0, test_time.microsecond)
|
||||||
|
self.assertEqual(0, dt_util.strip_microseconds(test_time).microsecond)
|
Loading…
x
Reference in New Issue
Block a user