mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
RENAME: StateMachine tracks now entities, not categories.
This commit is contained in:
parent
e7f5953362
commit
e9e1b007ed
16
README.md
16
README.md
@ -62,11 +62,11 @@ Other status codes that can occur are:
|
||||
The api supports the following actions:
|
||||
|
||||
**/api/states - GET**<br>
|
||||
Returns a list of categories for which a state is available
|
||||
Returns a list of entity ids for which a state is available
|
||||
|
||||
```json
|
||||
{
|
||||
"categories": [
|
||||
"entity_ids": [
|
||||
"Paulus_Nexus_4",
|
||||
"weather.sun",
|
||||
"all_devices"
|
||||
@ -103,8 +103,8 @@ Returns a dict with as keys the domain and as value a list of published services
|
||||
}
|
||||
```
|
||||
|
||||
**/api/states/<category>** - GET<br>
|
||||
Returns the current state from a category
|
||||
**/api/states/<entity_id>** - GET<br>
|
||||
Returns the current state from an entity
|
||||
|
||||
```json
|
||||
{
|
||||
@ -112,14 +112,14 @@ Returns the current state from a category
|
||||
"next_rising": "07:04:15 29-10-2013",
|
||||
"next_setting": "18:00:31 29-10-2013"
|
||||
},
|
||||
"category": "weather.sun",
|
||||
"entity_id": "weather.sun",
|
||||
"last_changed": "23:24:33 28-10-2013",
|
||||
"state": "below_horizon"
|
||||
}
|
||||
```
|
||||
|
||||
**/api/states/<category>** - POST<br>
|
||||
Updates the current state of a category. Returns status code 201 if successful with location header of updated resource and the new state in the body.<br>
|
||||
**/api/states/<entity_id>** - POST<br>
|
||||
Updates the current state of an entity. Returns status code 201 if successful with location header of updated resource and the new state in the body.<br>
|
||||
parameter: new_state - string<br>
|
||||
optional parameter: attributes - JSON encoded object
|
||||
|
||||
@ -129,7 +129,7 @@ optional parameter: attributes - JSON encoded object
|
||||
"next_rising": "07:04:15 29-10-2013",
|
||||
"next_setting": "18:00:31 29-10-2013"
|
||||
},
|
||||
"category": "weather.sun",
|
||||
"entity_id": "weather.sun",
|
||||
"last_changed": "23:24:33 28-10-2013",
|
||||
"state": "below_horizon"
|
||||
}
|
||||
|
@ -3,13 +3,13 @@ homeassistant
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Home Assistant is a Home Automation framework for observing the state
|
||||
of objects and react to changes.
|
||||
of entities and react to changes.
|
||||
"""
|
||||
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
from collections import defaultdict, namedtuple
|
||||
from collections import namedtuple
|
||||
import datetime as dt
|
||||
|
||||
import homeassistant.util as util
|
||||
@ -78,33 +78,33 @@ def _matcher(subject, pattern):
|
||||
return MATCH_ALL == pattern or subject in pattern
|
||||
|
||||
|
||||
def split_state_category(category):
|
||||
""" Splits a state category into domain, object_id. """
|
||||
return category.split(".", 1)
|
||||
def split_entity_id(entity_id):
|
||||
""" Splits a state entity_id into domain, object_id. """
|
||||
return entity_id.split(".", 1)
|
||||
|
||||
|
||||
def filter_categories(categories, domain_filter=None, strip_domain=False):
|
||||
""" Filter a list of categories based on domain. Setting strip_domain
|
||||
def filter_entity_ids(entity_ids, domain_filter=None, strip_domain=False):
|
||||
""" Filter a list of entities based on domain. Setting strip_domain
|
||||
will only return the object_ids. """
|
||||
return [
|
||||
split_state_category(cat)[1] if strip_domain else cat
|
||||
for cat in categories if
|
||||
not domain_filter or cat.startswith(domain_filter)
|
||||
split_entity_id(entity_id)[1] if strip_domain else entity_id
|
||||
for entity_id in entity_ids if
|
||||
not domain_filter or entity_id.startswith(domain_filter)
|
||||
]
|
||||
|
||||
|
||||
def track_state_change(bus, category, action, from_state=None, to_state=None):
|
||||
def track_state_change(bus, entity_id, action, from_state=None, to_state=None):
|
||||
""" Helper method to track specific state changes. """
|
||||
from_state = _process_match_param(from_state)
|
||||
to_state = _process_match_param(to_state)
|
||||
|
||||
def listener(event):
|
||||
""" State change listener that listens for specific state changes. """
|
||||
if category == event.data['category'] and \
|
||||
if entity_id == event.data['entity_id'] and \
|
||||
_matcher(event.data['old_state'].state, from_state) and \
|
||||
_matcher(event.data['new_state'].state, to_state):
|
||||
|
||||
action(event.data['category'],
|
||||
action(event.data['entity_id'],
|
||||
event.data['old_state'],
|
||||
event.data['new_state'])
|
||||
|
||||
@ -304,7 +304,11 @@ class State(object):
|
||||
else:
|
||||
self.last_changed = last_changed
|
||||
|
||||
def to_json_dict(self, category=None):
|
||||
def copy(self):
|
||||
""" Creates a copy of itself. """
|
||||
return State(self.state, dict(self.attributes), self.last_changed)
|
||||
|
||||
def to_json_dict(self, entity_id=None):
|
||||
""" Converts State to a dict to be used within JSON.
|
||||
Ensures: state == State.from_json_dict(state.to_json_dict()) """
|
||||
|
||||
@ -312,15 +316,11 @@ class State(object):
|
||||
'attributes': self.attributes,
|
||||
'last_changed': util.datetime_to_str(self.last_changed)}
|
||||
|
||||
if category:
|
||||
json_dict['category'] = category
|
||||
if entity_id:
|
||||
json_dict['entity_id'] = entity_id
|
||||
|
||||
return json_dict
|
||||
|
||||
def copy(self):
|
||||
""" Creates a copy of itself. """
|
||||
return State(self.state, dict(self.attributes), self.last_changed)
|
||||
|
||||
@staticmethod
|
||||
def from_json_dict(json_dict):
|
||||
""" Static method to create a state from a dict.
|
||||
@ -345,75 +345,75 @@ class State(object):
|
||||
|
||||
|
||||
class StateMachine(object):
|
||||
""" Helper class that tracks the state of different categories. """
|
||||
""" Helper class that tracks the state of different entities. """
|
||||
|
||||
def __init__(self, bus):
|
||||
self.states = dict()
|
||||
self.states = {}
|
||||
self.bus = bus
|
||||
self.lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def categories(self):
|
||||
""" List of categories which states are being tracked. """
|
||||
def entity_ids(self):
|
||||
""" List of entitie ids that are being tracked. """
|
||||
with self.lock:
|
||||
return self.states.keys()
|
||||
|
||||
def remove_category(self, category):
|
||||
""" Removes a category from the state machine.
|
||||
def remove_entity(self, entity_id):
|
||||
""" Removes a entity from the state machine.
|
||||
|
||||
Returns boolean to indicate if a category was removed. """
|
||||
Returns boolean to indicate if a entity was removed. """
|
||||
with self.lock:
|
||||
try:
|
||||
del self.states[category]
|
||||
del self.states[entity_id]
|
||||
|
||||
return True
|
||||
|
||||
except KeyError:
|
||||
# if category does not exist
|
||||
# if entity does not exist
|
||||
return False
|
||||
|
||||
def set_state(self, category, new_state, attributes=None):
|
||||
""" Set the state of a category, add category if it does not exist.
|
||||
def set_state(self, entity_id, new_state, attributes=None):
|
||||
""" Set the state of an entity, add entity if it does not exist.
|
||||
|
||||
Attributes is an optional dict to specify attributes of this state. """
|
||||
|
||||
attributes = attributes or {}
|
||||
|
||||
with self.lock:
|
||||
# Add category if it does not exist
|
||||
if category not in self.states:
|
||||
self.states[category] = State(new_state, attributes)
|
||||
# Add entity if it does not exist
|
||||
if entity_id not in self.states:
|
||||
self.states[entity_id] = State(new_state, attributes)
|
||||
|
||||
# Change state and fire listeners
|
||||
else:
|
||||
old_state = self.states[category]
|
||||
old_state = self.states[entity_id]
|
||||
|
||||
if old_state.state != new_state or \
|
||||
old_state.attributes != attributes:
|
||||
|
||||
self.states[category] = State(new_state, attributes)
|
||||
self.states[entity_id] = State(new_state, attributes)
|
||||
|
||||
self.bus.fire_event(EVENT_STATE_CHANGED,
|
||||
{'category': category,
|
||||
{'entity_id': entity_id,
|
||||
'old_state': old_state,
|
||||
'new_state': self.states[category]})
|
||||
'new_state': self.states[entity_id]})
|
||||
|
||||
def get_state(self, category):
|
||||
def get_state(self, entity_id):
|
||||
""" Returns a dict (state, last_changed, attributes) describing
|
||||
the state of the specified category. """
|
||||
the state of the specified entity. """
|
||||
with self.lock:
|
||||
try:
|
||||
# Make a copy so people won't mutate the state
|
||||
return self.states[category].copy()
|
||||
return self.states[entity_id].copy()
|
||||
|
||||
except KeyError:
|
||||
# If category does not exist
|
||||
# If entity does not exist
|
||||
return None
|
||||
|
||||
def is_state(self, category, state):
|
||||
""" Returns True if category exists and is specified state. """
|
||||
def is_state(self, entity_id, state):
|
||||
""" Returns True if entity exists and is specified state. """
|
||||
try:
|
||||
return self.get_state(category).state == state
|
||||
return self.get_state(entity_id).state == state
|
||||
except AttributeError:
|
||||
# get_state returned None
|
||||
return False
|
||||
@ -438,8 +438,10 @@ class Timer(threading.Thread):
|
||||
|
||||
last_fired_on_second = -1
|
||||
|
||||
calc_now = dt.datetime.now
|
||||
|
||||
while True:
|
||||
now = dt.datetime.now()
|
||||
now = calc_now()
|
||||
|
||||
# First check checks if we are not on a second matching the
|
||||
# timer interval. Second check checks if we did not already fire
|
||||
@ -457,7 +459,7 @@ class Timer(threading.Thread):
|
||||
|
||||
time.sleep(slp_seconds)
|
||||
|
||||
now = dt.datetime.now()
|
||||
now = calc_now()
|
||||
|
||||
last_fired_on_second = now.second
|
||||
|
||||
|
@ -138,10 +138,10 @@ def from_config_file(config_path):
|
||||
|
||||
# Init groups
|
||||
if has_section("groups"):
|
||||
for name, categories in config.items("groups"):
|
||||
for name, entity_ids in config.items("groups"):
|
||||
add_status("Group - {}".format(name),
|
||||
group.setup(bus, statemachine, name,
|
||||
categories.split(",")))
|
||||
entity_ids.split(",")))
|
||||
|
||||
# Light trigger
|
||||
if light_control:
|
||||
|
@ -8,7 +8,7 @@ Component design guidelines:
|
||||
|
||||
Each component defines a constant DOMAIN that is equal to its filename.
|
||||
|
||||
Each component that tracks states should create state category names in the
|
||||
Each component that tracks states should create state entity names in the
|
||||
format "<DOMAIN>.<OBJECT_ID>".
|
||||
|
||||
Each component should publish services only under its own domain.
|
||||
|
@ -16,7 +16,7 @@ DOMAIN = "chromecast"
|
||||
|
||||
SERVICE_YOUTUBE_VIDEO = "play_youtube_video"
|
||||
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + '.{}'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
STATE_NO_APP = "none"
|
||||
|
||||
ATTR_FRIENDLY_NAME = "friendly_name"
|
||||
@ -29,15 +29,15 @@ def turn_off(statemachine, cc_id=None):
|
||||
""" Exits any running app on the specified ChromeCast and shows
|
||||
idle screen. Will quit all ChromeCasts if nothing specified. """
|
||||
|
||||
cats = [STATE_CATEGORY_FORMAT.format(cc_id)] if cc_id \
|
||||
else ha.filter_categories(statemachine.categories, DOMAIN)
|
||||
entity_ids = [ENTITY_ID_FORMAT.format(cc_id)] if cc_id \
|
||||
else ha.filter_entity_ids(statemachine.entity_ids, DOMAIN)
|
||||
|
||||
for cat in cats:
|
||||
state = statemachine.get_state(cat)
|
||||
for entity_id in entity_ids:
|
||||
state = statemachine.get_state(entity_id)
|
||||
|
||||
if state and \
|
||||
state.state != STATE_NO_APP or \
|
||||
state.state != pychromecast.APP_ID_HOME:
|
||||
if (state and
|
||||
(state.state != STATE_NO_APP or
|
||||
state.state != pychromecast.APP_ID_HOME)):
|
||||
|
||||
pychromecast.quit_app(state.attributes[ATTR_HOST])
|
||||
|
||||
@ -53,7 +53,7 @@ def setup(bus, statemachine, host):
|
||||
logger.error("Could not find Chromecast")
|
||||
return False
|
||||
|
||||
category = STATE_CATEGORY_FORMAT.format(util.slugify(
|
||||
entity = ENTITY_ID_FORMAT.format(util.slugify(
|
||||
device.friendly_name))
|
||||
|
||||
bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF,
|
||||
@ -80,7 +80,7 @@ def setup(bus, statemachine, host):
|
||||
status = pychromecast.get_app_status(host)
|
||||
|
||||
if status:
|
||||
statemachine.set_state(category, status.name,
|
||||
statemachine.set_state(entity, status.name,
|
||||
{ATTR_FRIENDLY_NAME:
|
||||
pychromecast.get_friendly_name(
|
||||
status.name),
|
||||
@ -88,7 +88,7 @@ def setup(bus, statemachine, host):
|
||||
ATTR_STATE: status.state,
|
||||
ATTR_OPTIONS: status.options})
|
||||
else:
|
||||
statemachine.set_state(category, STATE_NO_APP, {ATTR_HOST: host})
|
||||
statemachine.set_state(entity, STATE_NO_APP, {ATTR_HOST: host})
|
||||
|
||||
ha.track_time_change(bus, update_chromecast_state)
|
||||
|
||||
|
@ -22,20 +22,20 @@ def setup(bus, statemachine, light_group=None):
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
device_state_categories = ha.filter_categories(statemachine.categories,
|
||||
device_tracker.DOMAIN)
|
||||
device_entity_ids = ha.filter_entity_ids(statemachine.entity_ids,
|
||||
device_tracker.DOMAIN)
|
||||
|
||||
if not device_state_categories:
|
||||
if not device_entity_ids:
|
||||
logger.error("LightTrigger:No devices found to track")
|
||||
|
||||
return False
|
||||
|
||||
if not light_group:
|
||||
light_group = light.STATE_GROUP_NAME_ALL_LIGHTS
|
||||
light_group = light.GROUP_NAME_ALL_LIGHTS
|
||||
|
||||
# Get the light IDs from the specified group
|
||||
light_ids = ha.filter_categories(
|
||||
group.get_categories(statemachine, light_group), light.DOMAIN, True)
|
||||
light_ids = ha.filter_entity_ids(
|
||||
group.get_entity_ids(statemachine, light_group), light.DOMAIN, True)
|
||||
|
||||
if not light_ids:
|
||||
logger.error("LightTrigger:No lights found to turn on ")
|
||||
@ -48,7 +48,7 @@ def setup(bus, statemachine, light_group=None):
|
||||
len(light_ids))
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def handle_sun_rising(category, old_state, new_state):
|
||||
def handle_sun_rising(entity, old_state, new_state):
|
||||
"""The moment sun sets we want to have all the lights on.
|
||||
We will schedule to have each light start after one another
|
||||
and slowly transition in."""
|
||||
@ -76,7 +76,7 @@ def setup(bus, statemachine, light_group=None):
|
||||
|
||||
# Track every time sun rises so we can schedule a time-based
|
||||
# pre-sun set event
|
||||
ha.track_state_change(bus, sun.STATE_CATEGORY, handle_sun_rising,
|
||||
ha.track_state_change(bus, sun.ENTITY_ID, handle_sun_rising,
|
||||
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
|
||||
|
||||
# If the sun is already above horizon
|
||||
@ -84,14 +84,14 @@ def setup(bus, statemachine, light_group=None):
|
||||
if sun.is_up(statemachine):
|
||||
handle_sun_rising(None, None, None)
|
||||
|
||||
def handle_device_state_change(category, old_state, new_state):
|
||||
def handle_device_state_change(entity, old_state, new_state):
|
||||
""" Function to handle tracked device state changes. """
|
||||
lights_are_on = group.is_on(statemachine, light_group)
|
||||
|
||||
light_needed = not (lights_are_on or sun.is_up(statemachine))
|
||||
|
||||
# Specific device came home ?
|
||||
if (category != device_tracker.STATE_CATEGORY_ALL_DEVICES and
|
||||
if (entity != device_tracker.ENTITY_ID_ALL_DEVICES and
|
||||
new_state.state == ha.STATE_HOME):
|
||||
|
||||
# These variables are needed for the elif check
|
||||
@ -103,7 +103,7 @@ def setup(bus, statemachine, light_group=None):
|
||||
|
||||
logger.info(
|
||||
"Home coming event for {}. Turning lights on".
|
||||
format(category))
|
||||
format(entity))
|
||||
|
||||
for light_id in light_ids:
|
||||
light.turn_on(bus, light_id)
|
||||
@ -127,7 +127,7 @@ def setup(bus, statemachine, light_group=None):
|
||||
break
|
||||
|
||||
# Did all devices leave the house?
|
||||
elif (category == device_tracker.STATE_CATEGORY_ALL_DEVICES and
|
||||
elif (entity == device_tracker.ENTITY_ID_ALL_DEVICES and
|
||||
new_state.state == ha.STATE_NOT_HOME and lights_are_on):
|
||||
|
||||
logger.info(
|
||||
@ -136,12 +136,12 @@ def setup(bus, statemachine, light_group=None):
|
||||
general.shutdown_devices(bus, statemachine)
|
||||
|
||||
# Track home coming of each seperate device
|
||||
for category in device_state_categories:
|
||||
ha.track_state_change(bus, category, handle_device_state_change,
|
||||
for entity in device_entity_ids:
|
||||
ha.track_state_change(bus, entity, handle_device_state_change,
|
||||
ha.STATE_NOT_HOME, ha.STATE_HOME)
|
||||
|
||||
# Track when all devices are gone to shut down lights
|
||||
ha.track_state_change(bus, device_tracker.STATE_CATEGORY_ALL_DEVICES,
|
||||
ha.track_state_change(bus, device_tracker.ENTITY_ID_ALL_DEVICES,
|
||||
handle_device_state_change, ha.STATE_HOME,
|
||||
ha.STATE_NOT_HOME)
|
||||
|
||||
|
@ -24,11 +24,11 @@ DOMAIN = "device_tracker"
|
||||
|
||||
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
|
||||
|
||||
STATE_GROUP_NAME_ALL_DEVICES = 'all_tracked_devices'
|
||||
STATE_CATEGORY_ALL_DEVICES = group.STATE_CATEGORY_FORMAT.format(
|
||||
STATE_GROUP_NAME_ALL_DEVICES)
|
||||
GROUP_NAME_ALL_DEVICES = 'all_tracked_devices'
|
||||
ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format(
|
||||
GROUP_NAME_ALL_DEVICES)
|
||||
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + '.{}'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
# After how much time do we consider a device not home if
|
||||
# it does not show up on scans
|
||||
@ -43,10 +43,10 @@ KNOWN_DEVICES_FILE = "known_devices.csv"
|
||||
|
||||
def is_home(statemachine, device_id=None):
|
||||
""" Returns if any or specified device is home. """
|
||||
category = STATE_CATEGORY_FORMAT.format(device_id) if device_id \
|
||||
else STATE_CATEGORY_ALL_DEVICES
|
||||
entity = ENTITY_ID_FORMAT.format(device_id) if device_id \
|
||||
else ENTITY_ID_ALL_DEVICES
|
||||
|
||||
return statemachine.is_state(category, ha.STATE_HOME)
|
||||
return statemachine.is_state(entity, ha.STATE_HOME)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
@ -83,14 +83,14 @@ class DeviceTracker(object):
|
||||
|
||||
self.update_devices(device_scanner.scan_devices())
|
||||
|
||||
group.setup(bus, statemachine, STATE_GROUP_NAME_ALL_DEVICES,
|
||||
list(self.device_state_categories))
|
||||
group.setup(bus, statemachine, GROUP_NAME_ALL_DEVICES,
|
||||
list(self.device_entity_ids))
|
||||
|
||||
@property
|
||||
def device_state_categories(self):
|
||||
""" Returns a set containing all categories
|
||||
that are maintained for devices. """
|
||||
return set([self.known_devices[device]['category'] for device
|
||||
def device_entity_ids(self):
|
||||
""" Returns a set containing all device entity ids
|
||||
that are being tracked. """
|
||||
return set([self.known_devices[device]['entity_id'] for device
|
||||
in self.known_devices
|
||||
if self.known_devices[device]['track']])
|
||||
|
||||
@ -111,7 +111,7 @@ class DeviceTracker(object):
|
||||
self.known_devices[device]['last_seen'] = now
|
||||
|
||||
self.statemachine.set_state(
|
||||
self.known_devices[device]['category'], ha.STATE_HOME)
|
||||
self.known_devices[device]['entity_id'], ha.STATE_HOME)
|
||||
|
||||
# For all devices we did not find, set state to NH
|
||||
# But only if they have been gone for longer then the error time span
|
||||
@ -122,7 +122,7 @@ class DeviceTracker(object):
|
||||
self.error_scanning):
|
||||
|
||||
self.statemachine.set_state(
|
||||
self.known_devices[device]['category'],
|
||||
self.known_devices[device]['entity_id'],
|
||||
ha.STATE_NOT_HOME)
|
||||
|
||||
# If we come along any unknown devices we will write them to the
|
||||
@ -180,9 +180,9 @@ class DeviceTracker(object):
|
||||
with open(KNOWN_DEVICES_FILE) as inp:
|
||||
default_last_seen = datetime(1990, 1, 1)
|
||||
|
||||
# Temp variable to keep track of which categories we use
|
||||
# so we can ensure we have unique categories.
|
||||
used_categories = []
|
||||
# Temp variable to keep track of which entity ids we use
|
||||
# so we can ensure we have unique entity ids.
|
||||
used_entity_ids = []
|
||||
|
||||
try:
|
||||
for row in csv.DictReader(inp):
|
||||
@ -195,23 +195,23 @@ class DeviceTracker(object):
|
||||
row['last_seen'] = default_last_seen
|
||||
|
||||
# Make sure that each device is mapped
|
||||
# to a unique category name
|
||||
# to a unique entity_id name
|
||||
name = util.slugify(row['name']) if row['name'] \
|
||||
else "unnamed_device"
|
||||
|
||||
category = STATE_CATEGORY_FORMAT.format(name)
|
||||
entity_id = ENTITY_ID_FORMAT.format(name)
|
||||
tries = 1
|
||||
|
||||
while category in used_categories:
|
||||
while entity_id in used_entity_ids:
|
||||
tries += 1
|
||||
|
||||
suffix = "_{}".format(tries)
|
||||
|
||||
category = STATE_CATEGORY_FORMAT.format(
|
||||
entity_id = ENTITY_ID_FORMAT.format(
|
||||
name + suffix)
|
||||
|
||||
row['category'] = category
|
||||
used_categories.append(category)
|
||||
row['entity_id'] = entity_id
|
||||
used_entity_ids.append(entity_id)
|
||||
|
||||
known_devices[device] = row
|
||||
|
||||
@ -220,21 +220,21 @@ class DeviceTracker(object):
|
||||
"No devices to track. Please update {}.".format(
|
||||
KNOWN_DEVICES_FILE))
|
||||
|
||||
# Remove categories that are no longer maintained
|
||||
new_categories = set([known_devices[device]['category']
|
||||
# Remove entities that are no longer maintained
|
||||
new_entity_ids = set([known_devices[device]['entity_id']
|
||||
for device in known_devices
|
||||
if known_devices[device]['track']])
|
||||
|
||||
for category in \
|
||||
self.device_state_categories - new_categories:
|
||||
for entity_id in \
|
||||
self.device_entity_ids - new_entity_ids:
|
||||
|
||||
self.logger.info(
|
||||
"DeviceTracker:Removing category {}".format(
|
||||
category))
|
||||
self.statemachine.remove_category(category)
|
||||
"DeviceTracker:Removing entity {}".format(
|
||||
entity_id))
|
||||
self.statemachine.remove_entity(entity_id)
|
||||
|
||||
# File parsed, warnings given if necessary
|
||||
# categories cleaned up, make it available
|
||||
# entities cleaned up, make it available
|
||||
self.known_devices = known_devices
|
||||
|
||||
self.logger.info(
|
||||
|
@ -11,9 +11,9 @@ import homeassistant as ha
|
||||
|
||||
DOMAIN = "group"
|
||||
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + ".{}"
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
STATE_ATTR_CATEGORIES = "categories"
|
||||
STATE_ATTR_ENTITY_IDS = "entity_ids"
|
||||
|
||||
_GROUP_TYPES = {
|
||||
"on_off": (ha.STATE_ON, ha.STATE_OFF),
|
||||
@ -46,29 +46,32 @@ def is_on(statemachine, group):
|
||||
return False
|
||||
|
||||
|
||||
def get_categories(statemachine, group):
|
||||
""" Get the categories that make up this group. """
|
||||
state = statemachine.get_state(group)
|
||||
|
||||
return state.attributes[STATE_ATTR_CATEGORIES] if state else []
|
||||
def get_entity_ids(statemachine, group):
|
||||
""" Get the entity ids that make up this group. """
|
||||
try:
|
||||
return statemachine.get_state(group).attributes[STATE_ATTR_ENTITY_IDS]
|
||||
except (AttributeError, KeyError):
|
||||
# AttributeError if state did not exist
|
||||
# KeyError if key did not exist in attributes
|
||||
return []
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def setup(bus, statemachine, name, categories):
|
||||
def setup(bus, statemachine, name, entity_ids):
|
||||
""" Sets up a group state that is the combined state of
|
||||
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Loop over the given categories to:
|
||||
# Loop over the given entities to:
|
||||
# - determine which group type this is (on_off, device_home)
|
||||
# - if all states exist and have valid states
|
||||
# - retrieve the current state of the group
|
||||
errors = []
|
||||
group_type, group_on, group_off, group_state = None, None, None, None
|
||||
|
||||
for cat in categories:
|
||||
state = statemachine.get_state(cat)
|
||||
for entity_id in entity_ids:
|
||||
state = statemachine.get_state(entity_id)
|
||||
|
||||
# Try to determine group type if we didn't yet
|
||||
if not group_type and state:
|
||||
@ -85,15 +88,15 @@ def setup(bus, statemachine, name, categories):
|
||||
|
||||
break
|
||||
|
||||
# Check if category exists
|
||||
# Check if entity exists
|
||||
if not state:
|
||||
errors.append("Category {} does not exist".format(cat))
|
||||
errors.append("Entity {} does not exist".format(entity_id))
|
||||
|
||||
# Check if category is valid state
|
||||
# Check if entity is valid state
|
||||
elif state.state != group_off and state.state != group_on:
|
||||
|
||||
errors.append("State of {} is {} (expected: {}, {})".format(
|
||||
cat, state.state, group_off, group_on))
|
||||
entity_id, state.state, group_off, group_on))
|
||||
|
||||
# Keep track of the group state to init later on
|
||||
elif group_state == group_off and state.state == group_on:
|
||||
@ -105,15 +108,15 @@ def setup(bus, statemachine, name, categories):
|
||||
|
||||
return False
|
||||
|
||||
group_cat = STATE_CATEGORY_FORMAT.format(name)
|
||||
state_attr = {STATE_ATTR_CATEGORIES: categories}
|
||||
group_entity_id = ENTITY_ID_FORMAT.format(name)
|
||||
state_attr = {STATE_ATTR_ENTITY_IDS: entity_ids}
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def _update_group_state(category, old_state, new_state):
|
||||
def _update_group_state(entity_id, old_state, new_state):
|
||||
""" Updates the group state based on a state change by a tracked
|
||||
category. """
|
||||
entity. """
|
||||
|
||||
cur_group_state = statemachine.get_state(group_cat).state
|
||||
cur_group_state = statemachine.get_state(group_entity_id).state
|
||||
|
||||
# if cur_group_state = OFF and new_state = ON: set ON
|
||||
# if cur_group_state = ON and new_state = OFF: research
|
||||
@ -121,18 +124,18 @@ def setup(bus, statemachine, name, categories):
|
||||
|
||||
if cur_group_state == group_off and new_state.state == group_on:
|
||||
|
||||
statemachine.set_state(group_cat, group_on, state_attr)
|
||||
statemachine.set_state(group_entity_id, group_on, state_attr)
|
||||
|
||||
elif cur_group_state == group_on and new_state.state == group_off:
|
||||
|
||||
# Check if any of the other states is still on
|
||||
if not any([statemachine.is_state(cat, group_on)
|
||||
for cat in categories if cat != category]):
|
||||
statemachine.set_state(group_cat, group_off, state_attr)
|
||||
if not any([statemachine.is_state(ent_id, group_on)
|
||||
for ent_id in entity_ids if entity_id != ent_id]):
|
||||
statemachine.set_state(group_entity_id, group_off, state_attr)
|
||||
|
||||
for cat in categories:
|
||||
ha.track_state_change(bus, cat, _update_group_state)
|
||||
for entity_id in entity_ids:
|
||||
ha.track_state_change(bus, entity_id, _update_group_state)
|
||||
|
||||
statemachine.set_state(group_cat, group_state, state_attr)
|
||||
statemachine.set_state(group_entity_id, group_state, state_attr)
|
||||
|
||||
return True
|
||||
|
@ -18,31 +18,31 @@ Other status codes that can occur are:
|
||||
The api supports the following actions:
|
||||
|
||||
/api/states - GET
|
||||
Returns a list of categories for which a state is available
|
||||
Returns a list of entities for which a state is available
|
||||
Example result:
|
||||
{
|
||||
"categories": [
|
||||
"entity_ids": [
|
||||
"Paulus_Nexus_4",
|
||||
"weather.sun",
|
||||
"all_devices"
|
||||
]
|
||||
}
|
||||
|
||||
/api/states/<category> - GET
|
||||
Returns the current state from a category
|
||||
/api/states/<entity_id> - GET
|
||||
Returns the current state from an entity
|
||||
Example result:
|
||||
{
|
||||
"attributes": {
|
||||
"next_rising": "07:04:15 29-10-2013",
|
||||
"next_setting": "18:00:31 29-10-2013"
|
||||
},
|
||||
"category": "weather.sun",
|
||||
"entity_id": "weather.sun",
|
||||
"last_changed": "23:24:33 28-10-2013",
|
||||
"state": "below_horizon"
|
||||
}
|
||||
|
||||
/api/states/<category> - POST
|
||||
Updates the current state of a category. Returns status code 201 if successful
|
||||
/api/states/<entity_id> - POST
|
||||
Updates the current state of an entity. Returns status code 201 if successful
|
||||
with location header of updated resource and as body the new state.
|
||||
parameter: new_state - string
|
||||
optional parameter: attributes - JSON encoded object
|
||||
@ -52,7 +52,7 @@ Example result:
|
||||
"next_rising": "07:04:15 29-10-2013",
|
||||
"next_setting": "18:00:31 29-10-2013"
|
||||
},
|
||||
"category": "weather.sun",
|
||||
"entity_id": "weather.sun",
|
||||
"last_changed": "23:24:33 28-10-2013",
|
||||
"state": "below_horizon"
|
||||
}
|
||||
@ -94,7 +94,7 @@ URL_CHANGE_STATE = "/change_state"
|
||||
URL_FIRE_EVENT = "/fire_event"
|
||||
|
||||
URL_API_STATES = "/api/states"
|
||||
URL_API_STATES_CATEGORY = "/api/states/{}"
|
||||
URL_API_STATES_ENTITY = "/api/states/{}"
|
||||
URL_API_EVENTS = "/api/events"
|
||||
URL_API_EVENTS_EVENT = "/api/events/{}"
|
||||
URL_API_SERVICES = "/api/services"
|
||||
@ -150,10 +150,10 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
# /states
|
||||
('GET', '/api/states', '_handle_get_api_states'),
|
||||
('GET',
|
||||
re.compile(r'/api/states/(?P<category>[a-zA-Z\._0-9]+)'),
|
||||
'_handle_get_api_states_category'),
|
||||
re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
|
||||
'_handle_get_api_states_entity'),
|
||||
('POST',
|
||||
re.compile(r'/api/states/(?P<category>[a-zA-Z\._0-9]+)'),
|
||||
re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
|
||||
'_handle_change_state'),
|
||||
|
||||
# /events
|
||||
@ -317,8 +317,6 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
self.server.flash_message = None
|
||||
|
||||
# Describe state machine:
|
||||
categories = []
|
||||
|
||||
write(("<div class='row'>"
|
||||
"<div class='col-xs-12'>"
|
||||
"<div class='panel panel-primary'>"
|
||||
@ -328,17 +326,15 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
"class='form-change-state'>"
|
||||
"<input type='hidden' name='api_password' value='{}'>"
|
||||
"<table class='table'><tr>"
|
||||
"<th>Category</th><th>State</th>"
|
||||
"<th>Entity ID</th><th>State</th>"
|
||||
"<th>Attributes</th><th>Last Changed</th>"
|
||||
"</tr>").format(self.server.api_password))
|
||||
|
||||
for category in \
|
||||
sorted(self.server.statemachine.categories,
|
||||
for entity_id in \
|
||||
sorted(self.server.statemachine.entity_ids,
|
||||
key=lambda key: key.lower()):
|
||||
|
||||
categories.append(category)
|
||||
|
||||
state = self.server.statemachine.get_state(category)
|
||||
state = self.server.statemachine.get_state(entity_id)
|
||||
|
||||
attributes = "<br>".join(
|
||||
["{}: {}".format(attr, state.attributes[attr])
|
||||
@ -347,14 +343,14 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
write(("<tr>"
|
||||
"<td>{}</td><td>{}</td><td>{}</td><td>{}</td>"
|
||||
"</tr>").format(
|
||||
category,
|
||||
entity_id,
|
||||
state.state,
|
||||
attributes,
|
||||
util.datetime_to_str(state.last_changed)))
|
||||
|
||||
# Change state form
|
||||
write(("<tr><td><input name='category' class='form-control' "
|
||||
"placeholder='Category'></td>"
|
||||
write(("<tr><td><input name='entity_id' class='form-control' "
|
||||
"placeholder='Entity ID'></td>"
|
||||
"<td><input name='new_state' class='form-control' "
|
||||
"placeholder='New State'></td>"
|
||||
"<td><textarea rows='3' name='attributes' class='form-control' "
|
||||
@ -488,18 +484,18 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def _handle_change_state(self, path_match, data):
|
||||
""" Handles updating the state of a category.
|
||||
""" Handles updating the state of an entity.
|
||||
|
||||
This handles the following paths:
|
||||
/change_state
|
||||
/api/states/<category>
|
||||
/api/states/<entity_id>
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
category = path_match.group('category')
|
||||
entity_id = path_match.group('entity_id')
|
||||
except IndexError:
|
||||
# If group 'category' does not exist in path_match
|
||||
category = data['category'][0]
|
||||
# If group 'entity_id' does not exist in path_match
|
||||
entity_id = data['entity_id'][0]
|
||||
|
||||
new_state = data['new_state'][0]
|
||||
|
||||
@ -510,21 +506,21 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
attributes = None
|
||||
|
||||
# Write state
|
||||
self.server.statemachine.set_state(category,
|
||||
self.server.statemachine.set_state(entity_id,
|
||||
new_state,
|
||||
attributes)
|
||||
|
||||
# Return state if json, else redirect to main page
|
||||
if self.use_json:
|
||||
state = self.server.statemachine.get_state(category)
|
||||
state = self.server.statemachine.get_state(entity_id)
|
||||
|
||||
self._write_json(state.to_json_dict(category),
|
||||
self._write_json(state.to_json_dict(entity_id),
|
||||
status_code=HTTP_CREATED,
|
||||
location=
|
||||
URL_API_STATES_CATEGORY.format(category))
|
||||
URL_API_STATES_ENTITY.format(entity_id))
|
||||
else:
|
||||
self._message(
|
||||
"State of {} changed to {}".format(category, new_state))
|
||||
"State of {} changed to {}".format(entity_id, new_state))
|
||||
|
||||
except KeyError:
|
||||
# If new_state don't exist in post data
|
||||
@ -607,20 +603,20 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def _handle_get_api_states(self, path_match, data):
|
||||
""" Returns the categories which state is being tracked. """
|
||||
self._write_json({'categories': self.server.statemachine.categories})
|
||||
""" Returns the entitie ids which state are being tracked. """
|
||||
self._write_json({'entity_ids': self.server.statemachine.entity_ids})
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def _handle_get_api_states_category(self, path_match, data):
|
||||
""" Returns the state of a specific category. """
|
||||
category = path_match.group('category')
|
||||
def _handle_get_api_states_entity(self, path_match, data):
|
||||
""" Returns the state of a specific entity. """
|
||||
entity_id = path_match.group('entity_id')
|
||||
|
||||
state = self.server.statemachine.get_state(category)
|
||||
state = self.server.statemachine.get_state(entity_id)
|
||||
|
||||
if state:
|
||||
self._write_json(state.to_json_dict(category))
|
||||
self._write_json(state.to_json_dict(entity_id))
|
||||
else:
|
||||
# If category does not exist
|
||||
# If entity_id does not exist
|
||||
self._message("State does not exist.", HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
def _handle_get_api_events(self, path_match, data):
|
||||
|
@ -14,21 +14,21 @@ import homeassistant.components.group as group
|
||||
|
||||
DOMAIN = "light"
|
||||
|
||||
STATE_GROUP_NAME_ALL_LIGHTS = 'all_lights'
|
||||
STATE_CATEGORY_ALL_LIGHTS = group.STATE_CATEGORY_FORMAT.format(
|
||||
STATE_GROUP_NAME_ALL_LIGHTS)
|
||||
GROUP_NAME_ALL_LIGHTS = 'all_lights'
|
||||
ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format(
|
||||
GROUP_NAME_ALL_LIGHTS)
|
||||
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + ".{}"
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
|
||||
def is_on(statemachine, light_id=None):
|
||||
""" Returns if the lights are on based on the statemachine. """
|
||||
category = STATE_CATEGORY_FORMAT.format(light_id) if light_id \
|
||||
else STATE_CATEGORY_ALL_LIGHTS
|
||||
entity_id = ENTITY_ID_FORMAT.format(light_id) if light_id \
|
||||
else ENTITY_ID_ALL_LIGHTS
|
||||
|
||||
return statemachine.is_state(category, ha.STATE_ON)
|
||||
return statemachine.is_state(entity_id, ha.STATE_ON)
|
||||
|
||||
|
||||
def turn_on(bus, light_id=None, transition_seconds=None):
|
||||
@ -80,21 +80,21 @@ def setup(bus, statemachine, light_control):
|
||||
for light_id in light_control.light_ids}
|
||||
|
||||
for light_id, state in status.items():
|
||||
state_category = STATE_CATEGORY_FORMAT.format(light_id)
|
||||
entity_id = ENTITY_ID_FORMAT.format(light_id)
|
||||
|
||||
new_state = ha.STATE_ON if state else ha.STATE_OFF
|
||||
|
||||
statemachine.set_state(state_category, new_state)
|
||||
statemachine.set_state(entity_id, new_state)
|
||||
|
||||
ha.track_time_change(bus, update_light_state, second=[0, 30])
|
||||
|
||||
update_light_state(None)
|
||||
|
||||
# Track the all lights state
|
||||
light_cats = [STATE_CATEGORY_FORMAT.format(light_id) for light_id
|
||||
entity_ids = [ENTITY_ID_FORMAT.format(light_id) for light_id
|
||||
in light_control.light_ids]
|
||||
|
||||
group.setup(bus, statemachine, STATE_GROUP_NAME_ALL_LIGHTS, light_cats)
|
||||
group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids)
|
||||
|
||||
def handle_light_service(service):
|
||||
""" Hande a turn light on or off service call. """
|
||||
|
@ -10,7 +10,7 @@ from datetime import timedelta
|
||||
import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
|
||||
STATE_CATEGORY = "weather.sun"
|
||||
ENTITY_ID = "weather.sun"
|
||||
|
||||
STATE_ABOVE_HORIZON = "above_horizon"
|
||||
STATE_BELOW_HORIZON = "below_horizon"
|
||||
@ -21,12 +21,12 @@ STATE_ATTR_NEXT_SETTING = "next_setting"
|
||||
|
||||
def is_up(statemachine):
|
||||
""" Returns if the sun is currently up based on the statemachine. """
|
||||
return statemachine.is_state(STATE_CATEGORY, STATE_ABOVE_HORIZON)
|
||||
return statemachine.is_state(ENTITY_ID, STATE_ABOVE_HORIZON)
|
||||
|
||||
|
||||
def next_setting(statemachine):
|
||||
""" Returns the datetime object representing the next sun setting. """
|
||||
state = statemachine.get_state(STATE_CATEGORY)
|
||||
state = statemachine.get_state(ENTITY_ID)
|
||||
|
||||
return None if not state else util.str_to_datetime(
|
||||
state.attributes[STATE_ATTR_NEXT_SETTING])
|
||||
@ -34,7 +34,7 @@ def next_setting(statemachine):
|
||||
|
||||
def next_rising(statemachine):
|
||||
""" Returns the datetime object representing the next sun setting. """
|
||||
state = statemachine.get_state(STATE_CATEGORY)
|
||||
state = statemachine.get_state(ENTITY_ID)
|
||||
|
||||
return None if not state else util.str_to_datetime(
|
||||
state.attributes[STATE_ATTR_NEXT_RISING])
|
||||
@ -79,7 +79,7 @@ def setup(bus, statemachine, latitude, longitude):
|
||||
STATE_ATTR_NEXT_SETTING: util.datetime_to_str(next_setting_dt)
|
||||
}
|
||||
|
||||
statemachine.set_state(STATE_CATEGORY, new_state, state_attributes)
|
||||
statemachine.set_state(ENTITY_ID, new_state, state_attributes)
|
||||
|
||||
# +10 seconds to be sure that the change has occured
|
||||
ha.track_time_change(bus, update_sun_state,
|
||||
|
@ -202,13 +202,13 @@ class StateMachine(ha.StateMachine):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
@property
|
||||
def categories(self):
|
||||
""" List of categories which states are being tracked. """
|
||||
def entity_ids(self):
|
||||
""" List of entity ids which states are being tracked. """
|
||||
|
||||
try:
|
||||
req = self._call_api(METHOD_GET, hah.URL_API_STATES)
|
||||
|
||||
return req.json()['categories']
|
||||
return req.json()['entity_ids']
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.logger.exception("StateMachine:Error connecting to server")
|
||||
@ -218,19 +218,19 @@ class StateMachine(ha.StateMachine):
|
||||
self.logger.exception("StateMachine:Got unexpected result")
|
||||
return []
|
||||
|
||||
except KeyError: # If 'categories' key not in parsed json
|
||||
except KeyError: # If 'entity_ids' key not in parsed json
|
||||
self.logger.exception("StateMachine:Got unexpected result (2)")
|
||||
return []
|
||||
|
||||
def remove_category(self, category):
|
||||
def remove_entity(self, entity_id):
|
||||
""" This method is not implemented for remote statemachine.
|
||||
|
||||
Throws NotImplementedError. """
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def set_state(self, category, new_state, attributes=None):
|
||||
""" Set the state of a category, add category if it does not exist.
|
||||
def set_state(self, entity_id, new_state, attributes=None):
|
||||
""" Set the state of a entity, add entity if it does not exist.
|
||||
|
||||
Attributes is an optional dict to specify attributes of this state. """
|
||||
|
||||
@ -243,7 +243,7 @@ class StateMachine(ha.StateMachine):
|
||||
|
||||
try:
|
||||
req = self._call_api(METHOD_POST,
|
||||
hah.URL_API_STATES_CATEGORY.format(category),
|
||||
hah.URL_API_STATES_ENTITY.format(entity_id),
|
||||
data)
|
||||
|
||||
if req.status_code != 201:
|
||||
@ -260,13 +260,12 @@ class StateMachine(ha.StateMachine):
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def get_state(self, category):
|
||||
""" Returns a dict (state,last_changed, attributes) describing
|
||||
the state of the specified category. """
|
||||
def get_state(self, entity_id):
|
||||
""" Returns the state of the specified entity. """
|
||||
|
||||
try:
|
||||
req = self._call_api(METHOD_GET,
|
||||
hah.URL_API_STATES_CATEGORY.format(category))
|
||||
hah.URL_API_STATES_ENTITY.format(entity_id))
|
||||
|
||||
if req.status_code == 200:
|
||||
data = req.json()
|
||||
@ -274,7 +273,7 @@ class StateMachine(ha.StateMachine):
|
||||
return ha.State.from_json_dict(data)
|
||||
|
||||
elif req.status_code == 422:
|
||||
# Category does not exist
|
||||
# Entity does not exist
|
||||
return None
|
||||
|
||||
else:
|
||||
|
@ -77,12 +77,12 @@ class TestHTTPInterface(unittest.TestCase):
|
||||
""" Test if we get access denied if we omit or provide
|
||||
a wrong api password. """
|
||||
req = requests.get(
|
||||
_url(hah.URL_API_STATES_CATEGORY.format("test")))
|
||||
_url(hah.URL_API_STATES_ENTITY.format("test")))
|
||||
|
||||
self.assertEqual(req.status_code, 401)
|
||||
|
||||
req = requests.get(
|
||||
_url(hah.URL_API_STATES_CATEGORY.format("test")),
|
||||
_url(hah.URL_API_STATES_ENTITY.format("test")),
|
||||
params={"api_password": "not the password"})
|
||||
|
||||
self.assertEqual(req.status_code, 401)
|
||||
@ -92,7 +92,7 @@ class TestHTTPInterface(unittest.TestCase):
|
||||
self.statemachine.set_state("test.test", "not_to_be_set_state")
|
||||
|
||||
requests.post(_url(hah.URL_CHANGE_STATE),
|
||||
data={"category": "test.test",
|
||||
data={"entity_id": "test.test",
|
||||
"new_state": "debug_state_change2",
|
||||
"api_password": API_PASSWORD})
|
||||
|
||||
@ -122,20 +122,20 @@ class TestHTTPInterface(unittest.TestCase):
|
||||
|
||||
self.assertEqual(len(test_value), 1)
|
||||
|
||||
def test_api_list_state_categories(self):
|
||||
""" Test if the debug interface allows us to list state categories. """
|
||||
def test_api_list_state_entities(self):
|
||||
""" Test if the debug interface allows us to list state entities. """
|
||||
req = requests.get(_url(hah.URL_API_STATES),
|
||||
data={"api_password": API_PASSWORD})
|
||||
|
||||
data = req.json()
|
||||
|
||||
self.assertEqual(self.statemachine.categories,
|
||||
data['categories'])
|
||||
self.assertEqual(self.statemachine.entity_ids,
|
||||
data['entity_ids'])
|
||||
|
||||
def test_api_get_state(self):
|
||||
""" Test if the debug interface allows us to get a state. """
|
||||
req = requests.get(
|
||||
_url(hah.URL_API_STATES_CATEGORY.format("test")),
|
||||
_url(hah.URL_API_STATES_ENTITY.format("test")),
|
||||
data={"api_password": API_PASSWORD})
|
||||
|
||||
data = ha.State.from_json_dict(req.json())
|
||||
@ -149,17 +149,17 @@ class TestHTTPInterface(unittest.TestCase):
|
||||
def test_api_get_non_existing_state(self):
|
||||
""" Test if the debug interface allows us to get a state. """
|
||||
req = requests.get(
|
||||
_url(hah.URL_API_STATES_CATEGORY.format("does_not_exist")),
|
||||
_url(hah.URL_API_STATES_ENTITY.format("does_not_exist")),
|
||||
params={"api_password": API_PASSWORD})
|
||||
|
||||
self.assertEqual(req.status_code, 422)
|
||||
|
||||
def test_api_state_change(self):
|
||||
""" Test if we can change the state of a category that exists. """
|
||||
""" Test if we can change the state of an entity that exists. """
|
||||
|
||||
self.statemachine.set_state("test.test", "not_to_be_set_state")
|
||||
|
||||
requests.post(_url(hah.URL_API_STATES_CATEGORY.format("test.test")),
|
||||
requests.post(_url(hah.URL_API_STATES_ENTITY.format("test.test")),
|
||||
data={"new_state": "debug_state_change2",
|
||||
"api_password": API_PASSWORD})
|
||||
|
||||
@ -167,20 +167,20 @@ class TestHTTPInterface(unittest.TestCase):
|
||||
"debug_state_change2")
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def test_api_state_change_of_non_existing_category(self):
|
||||
def test_api_state_change_of_non_existing_entity(self):
|
||||
""" Test if the API allows us to change a state of
|
||||
a non existing category. """
|
||||
a non existing entity. """
|
||||
|
||||
new_state = "debug_state_change"
|
||||
|
||||
req = requests.post(
|
||||
_url(hah.URL_API_STATES_CATEGORY.format(
|
||||
"test_category_that_does_not_exist")),
|
||||
_url(hah.URL_API_STATES_ENTITY.format(
|
||||
"test_entity_that_does_not_exist")),
|
||||
data={"new_state": new_state,
|
||||
"api_password": API_PASSWORD})
|
||||
|
||||
cur_state = (self.statemachine.
|
||||
get_state("test_category_that_does_not_exist").state)
|
||||
get_state("test_entity_that_does_not_exist").state)
|
||||
|
||||
self.assertEqual(req.status_code, 201)
|
||||
self.assertEqual(cur_state, new_state)
|
||||
@ -326,14 +326,14 @@ class TestRemote(unittest.TestCase):
|
||||
cls.sm_with_remote_eb.set_state("test", "a_state")
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def test_remote_sm_list_state_categories(self):
|
||||
""" Test if the debug interface allows us to list state categories. """
|
||||
def test_remote_sm_list_state_entities(self):
|
||||
""" Test if the debug interface allows us to list state entity ids. """
|
||||
|
||||
self.assertEqual(self.statemachine.categories,
|
||||
self.remote_sm.categories)
|
||||
self.assertEqual(self.statemachine.entity_ids,
|
||||
self.remote_sm.entity_ids)
|
||||
|
||||
def test_remote_sm_get_state(self):
|
||||
""" Test if the debug interface allows us to list state categories. """
|
||||
""" Test if debug interface allows us to get state of an entity. """
|
||||
remote_state = self.remote_sm.get_state("test")
|
||||
|
||||
state = self.statemachine.get_state("test")
|
||||
@ -343,11 +343,11 @@ class TestRemote(unittest.TestCase):
|
||||
self.assertEqual(remote_state.attributes, state.attributes)
|
||||
|
||||
def test_remote_sm_get_non_existing_state(self):
|
||||
""" Test if the debug interface allows us to list state categories. """
|
||||
""" Test remote state machine to get state of non existing entity. """
|
||||
self.assertEqual(self.remote_sm.get_state("test_does_not_exist"), None)
|
||||
|
||||
def test_remote_sm_state_change(self):
|
||||
""" Test if we can change the state of a category that exists. """
|
||||
""" Test if we can change the state of an existing entity. """
|
||||
|
||||
self.remote_sm.set_state("test", "set_remotely", {"test": 1})
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user