Make mysensors component async (#13641)
* Make mysensors component async * Use async dispatcher and discovery. * Run I/O in executor. * Make mysensors actuator methods async. * Upgrade pymysensors to 0.13.0. * Use async serial gateway. * Use async TCP gateway. * Use async mqtt gateway. * Start gateway before hass start event * Make sure gateway is started after discovery of persistent devices and after corresponding platforms have been loaded. * Don't wait to start gateway until after hass start. * Bump pymysensors to 0.14.0
This commit is contained in:
parent
ef8fc1f201
commit
be3b227a87
7 changed files with 110 additions and 80 deletions
|
@ -115,7 +115,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
|||
"""List of available fan modes."""
|
||||
return ['Auto', 'Min', 'Normal', 'Max']
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -143,9 +143,9 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
|||
if self.gateway.optimistic:
|
||||
# Optimistically assume that device has changed state
|
||||
self._values[value_type] = value
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new target temperature."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
self.gateway.set_child_value(
|
||||
|
@ -153,9 +153,9 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
|||
if self.gateway.optimistic:
|
||||
# Optimistically assume that device has changed state
|
||||
self._values[set_req.V_HVAC_SPEED] = fan_mode
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
"""Set new target temperature."""
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, self.value_type,
|
||||
|
@ -163,7 +163,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
|
|||
if self.gateway.optimistic:
|
||||
# Optimistically assume that device has changed state
|
||||
self._values[self.value_type] = operation_mode
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the controller with the latest value from a sensor."""
|
||||
|
|
|
@ -42,7 +42,7 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
|
|||
set_req = self.gateway.const.SetReq
|
||||
return self._values.get(set_req.V_DIMMER)
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
async def async_open_cover(self, **kwargs):
|
||||
"""Move the cover up."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
self.gateway.set_child_value(
|
||||
|
@ -53,9 +53,9 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
|
|||
self._values[set_req.V_DIMMER] = 100
|
||||
else:
|
||||
self._values[set_req.V_LIGHT] = STATE_ON
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
async def async_close_cover(self, **kwargs):
|
||||
"""Move the cover down."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
self.gateway.set_child_value(
|
||||
|
@ -66,9 +66,9 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
|
|||
self._values[set_req.V_DIMMER] = 0
|
||||
else:
|
||||
self._values[set_req.V_LIGHT] = STATE_OFF
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def set_cover_position(self, **kwargs):
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
position = kwargs.get(ATTR_POSITION)
|
||||
set_req = self.gateway.const.SetReq
|
||||
|
@ -77,9 +77,9 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
|
|||
if self.gateway.optimistic:
|
||||
# Optimistically assume that cover has changed state.
|
||||
self._values[set_req.V_DIMMER] = position
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def stop_cover(self, **kwargs):
|
||||
async def async_stop_cover(self, **kwargs):
|
||||
"""Stop the device."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
self.gateway.set_child_value(
|
||||
|
|
|
@ -130,7 +130,7 @@ class MySensorsLight(mysensors.MySensorsEntity, Light):
|
|||
self._white = white
|
||||
self._values[self.value_type] = hex_color
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
value_type = self.gateway.const.SetReq.V_LIGHT
|
||||
self.gateway.set_child_value(
|
||||
|
@ -139,7 +139,7 @@ class MySensorsLight(mysensors.MySensorsEntity, Light):
|
|||
# optimistically assume that light has changed state
|
||||
self._state = False
|
||||
self._values[value_type] = STATE_OFF
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def _async_update_light(self):
|
||||
"""Update the controller with values from light child."""
|
||||
|
@ -171,12 +171,12 @@ class MySensorsLightDimmer(MySensorsLight):
|
|||
"""Flag supported features."""
|
||||
return SUPPORT_BRIGHTNESS
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self._turn_on_light()
|
||||
self._turn_on_dimmer(**kwargs)
|
||||
if self.gateway.optimistic:
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the controller with the latest value from a sensor."""
|
||||
|
@ -196,13 +196,13 @@ class MySensorsLightRGB(MySensorsLight):
|
|||
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR
|
||||
return SUPPORT_COLOR
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self._turn_on_light()
|
||||
self._turn_on_dimmer(**kwargs)
|
||||
self._turn_on_rgb_and_w('%02x%02x%02x', **kwargs)
|
||||
if self.gateway.optimistic:
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the controller with the latest value from a sensor."""
|
||||
|
@ -225,10 +225,10 @@ class MySensorsLightRGBW(MySensorsLightRGB):
|
|||
return SUPPORT_BRIGHTNESS | SUPPORT_MYSENSORS_RGBW
|
||||
return SUPPORT_MYSENSORS_RGBW
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self._turn_on_light()
|
||||
self._turn_on_dimmer(**kwargs)
|
||||
self._turn_on_rgb_and_w('%02x%02x%02x%02x', **kwargs)
|
||||
if self.gateway.optimistic:
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
|
|
@ -4,6 +4,7 @@ Connect to a MySensors gateway via pymysensors API.
|
|||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/mysensors/
|
||||
"""
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
import os
|
||||
|
@ -16,17 +17,17 @@ import voluptuous as vol
|
|||
from homeassistant.components.mqtt import (
|
||||
valid_publish_topic, valid_subscribe_topic)
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, CONF_NAME, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START,
|
||||
ATTR_BATTERY_LEVEL, CONF_NAME, CONF_OPTIMISTIC,
|
||||
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, dispatcher_send)
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
REQUIREMENTS = ['pymysensors==0.11.1']
|
||||
REQUIREMENTS = ['pymysensors==0.14.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -280,67 +281,62 @@ MYSENSORS_CONST_SCHEMA = {
|
|||
}
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the MySensors component."""
|
||||
import mysensors.mysensors as mysensors
|
||||
|
||||
version = config[DOMAIN].get(CONF_VERSION)
|
||||
persistence = config[DOMAIN].get(CONF_PERSISTENCE)
|
||||
|
||||
def setup_gateway(device, persistence_file, baud_rate, tcp_port, in_prefix,
|
||||
async def setup_gateway(
|
||||
device, persistence_file, baud_rate, tcp_port, in_prefix,
|
||||
out_prefix):
|
||||
"""Return gateway after setup of the gateway."""
|
||||
if device == MQTT_COMPONENT:
|
||||
if not setup_component(hass, MQTT_COMPONENT, config):
|
||||
return
|
||||
if not await async_setup_component(hass, MQTT_COMPONENT, config):
|
||||
return None
|
||||
mqtt = hass.components.mqtt
|
||||
retain = config[DOMAIN].get(CONF_RETAIN)
|
||||
|
||||
def pub_callback(topic, payload, qos, retain):
|
||||
"""Call MQTT publish function."""
|
||||
mqtt.publish(topic, payload, qos, retain)
|
||||
mqtt.async_publish(topic, payload, qos, retain)
|
||||
|
||||
def sub_callback(topic, sub_cb, qos):
|
||||
"""Call MQTT subscribe function."""
|
||||
mqtt.subscribe(topic, sub_cb, qos)
|
||||
gateway = mysensors.MQTTGateway(
|
||||
pub_callback, sub_callback,
|
||||
@callback
|
||||
def internal_callback(*args):
|
||||
"""Call callback."""
|
||||
sub_cb(*args)
|
||||
|
||||
hass.async_add_job(
|
||||
mqtt.async_subscribe(topic, internal_callback, qos))
|
||||
|
||||
gateway = mysensors.AsyncMQTTGateway(
|
||||
pub_callback, sub_callback, in_prefix=in_prefix,
|
||||
out_prefix=out_prefix, retain=retain, loop=hass.loop,
|
||||
event_callback=None, persistence=persistence,
|
||||
persistence_file=persistence_file,
|
||||
protocol_version=version, in_prefix=in_prefix,
|
||||
out_prefix=out_prefix, retain=retain)
|
||||
protocol_version=version)
|
||||
else:
|
||||
try:
|
||||
is_serial_port(device)
|
||||
gateway = mysensors.SerialGateway(
|
||||
device, event_callback=None, persistence=persistence,
|
||||
await hass.async_add_job(is_serial_port, device)
|
||||
gateway = mysensors.AsyncSerialGateway(
|
||||
device, baud=baud_rate, loop=hass.loop,
|
||||
event_callback=None, persistence=persistence,
|
||||
persistence_file=persistence_file,
|
||||
protocol_version=version, baud=baud_rate)
|
||||
protocol_version=version)
|
||||
except vol.Invalid:
|
||||
try:
|
||||
socket.getaddrinfo(device, None)
|
||||
# valid ip address
|
||||
gateway = mysensors.TCPGateway(
|
||||
device, event_callback=None, persistence=persistence,
|
||||
persistence_file=persistence_file,
|
||||
protocol_version=version, port=tcp_port)
|
||||
except OSError:
|
||||
# invalid ip address
|
||||
return
|
||||
gateway = mysensors.AsyncTCPGateway(
|
||||
device, port=tcp_port, loop=hass.loop, event_callback=None,
|
||||
persistence=persistence, persistence_file=persistence_file,
|
||||
protocol_version=version)
|
||||
gateway.metric = hass.config.units.is_metric
|
||||
gateway.optimistic = config[DOMAIN].get(CONF_OPTIMISTIC)
|
||||
gateway.device = device
|
||||
gateway.event_callback = gw_callback_factory(hass)
|
||||
|
||||
def gw_start(event):
|
||||
"""Trigger to start of the gateway and any persistence."""
|
||||
if persistence:
|
||||
discover_persistent_devices(hass, gateway)
|
||||
gateway.start()
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||
lambda event: gateway.stop())
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, gw_start)
|
||||
await gateway.start_persistence()
|
||||
|
||||
return gateway
|
||||
|
||||
|
@ -357,7 +353,7 @@ def setup(hass, config):
|
|||
tcp_port = gway.get(CONF_TCP_PORT)
|
||||
in_prefix = gway.get(CONF_TOPIC_IN_PREFIX, '')
|
||||
out_prefix = gway.get(CONF_TOPIC_OUT_PREFIX, '')
|
||||
ready_gateway = setup_gateway(
|
||||
ready_gateway = await setup_gateway(
|
||||
device, persistence_file, baud_rate, tcp_port, in_prefix,
|
||||
out_prefix)
|
||||
if ready_gateway is not None:
|
||||
|
@ -371,9 +367,36 @@ def setup(hass, config):
|
|||
|
||||
hass.data[MYSENSORS_GATEWAYS] = gateways
|
||||
|
||||
hass.async_add_job(finish_setup(hass, gateways))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def finish_setup(hass, gateways):
|
||||
"""Load any persistent devices and platforms and start gateway."""
|
||||
discover_tasks = []
|
||||
start_tasks = []
|
||||
for gateway in gateways.values():
|
||||
discover_tasks.append(discover_persistent_devices(hass, gateway))
|
||||
start_tasks.append(gw_start(hass, gateway))
|
||||
if discover_tasks:
|
||||
# Make sure all devices and platforms are loaded before gateway start.
|
||||
await asyncio.wait(discover_tasks, loop=hass.loop)
|
||||
if start_tasks:
|
||||
await asyncio.wait(start_tasks, loop=hass.loop)
|
||||
|
||||
|
||||
async def gw_start(hass, gateway):
|
||||
"""Start the gateway."""
|
||||
@callback
|
||||
def gw_stop(event):
|
||||
"""Trigger to stop the gateway."""
|
||||
hass.async_add_job(gateway.stop())
|
||||
|
||||
await gateway.start()
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gw_stop)
|
||||
|
||||
|
||||
def validate_child(gateway, node_id, child):
|
||||
"""Validate that a child has the correct values according to schema.
|
||||
|
||||
|
@ -431,14 +454,18 @@ def validate_child(gateway, node_id, child):
|
|||
return validated
|
||||
|
||||
|
||||
@callback
|
||||
def discover_mysensors_platform(hass, platform, new_devices):
|
||||
"""Discover a MySensors platform."""
|
||||
discovery.load_platform(
|
||||
hass, platform, DOMAIN, {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN})
|
||||
task = hass.async_add_job(discovery.async_load_platform(
|
||||
hass, platform, DOMAIN,
|
||||
{ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN}))
|
||||
return task
|
||||
|
||||
|
||||
def discover_persistent_devices(hass, gateway):
|
||||
async def discover_persistent_devices(hass, gateway):
|
||||
"""Discover platforms for devices loaded via persistence file."""
|
||||
tasks = []
|
||||
new_devices = defaultdict(list)
|
||||
for node_id in gateway.sensors:
|
||||
node = gateway.sensors[node_id]
|
||||
|
@ -447,7 +474,9 @@ def discover_persistent_devices(hass, gateway):
|
|||
for platform, dev_ids in validated.items():
|
||||
new_devices[platform].extend(dev_ids)
|
||||
for platform, dev_ids in new_devices.items():
|
||||
discover_mysensors_platform(hass, platform, dev_ids)
|
||||
tasks.append(discover_mysensors_platform(hass, platform, dev_ids))
|
||||
if tasks:
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
|
||||
def get_mysensors_devices(hass, domain):
|
||||
|
@ -459,6 +488,7 @@ def get_mysensors_devices(hass, domain):
|
|||
|
||||
def gw_callback_factory(hass):
|
||||
"""Return a new callback for the gateway."""
|
||||
@callback
|
||||
def mysensors_callback(msg):
|
||||
"""Handle messages from a MySensors gateway."""
|
||||
start = timer()
|
||||
|
@ -489,7 +519,7 @@ def gw_callback_factory(hass):
|
|||
# Only one signal per device is needed.
|
||||
# A device can have multiple platforms, ie multiple schemas.
|
||||
# FOR LATER: Add timer to not signal if another update comes in.
|
||||
dispatcher_send(hass, signal)
|
||||
async_dispatcher_send(hass, signal)
|
||||
end = timer()
|
||||
if end - start > 0.1:
|
||||
_LOGGER.debug(
|
||||
|
|
|
@ -42,7 +42,7 @@ class MySensorsNotificationService(BaseNotificationService):
|
|||
"""Initialize the service."""
|
||||
self.devices = mysensors.get_mysensors_devices(hass, DOMAIN)
|
||||
|
||||
def send_message(self, message="", **kwargs):
|
||||
async def async_send_message(self, message="", **kwargs):
|
||||
"""Send a message to a user."""
|
||||
target_devices = kwargs.get(ATTR_TARGET)
|
||||
devices = [device for device in self.devices.values()
|
||||
|
|
|
@ -42,7 +42,7 @@ async def async_setup_platform(
|
|||
hass, DOMAIN, discovery_info, device_class_map,
|
||||
async_add_devices=async_add_devices)
|
||||
|
||||
def send_ir_code_service(service):
|
||||
async def async_send_ir_code_service(service):
|
||||
"""Set IR code as device state attribute."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
ir_code = service.data.get(ATTR_IR_CODE)
|
||||
|
@ -58,10 +58,10 @@ async def async_setup_platform(
|
|||
|
||||
kwargs = {ATTR_IR_CODE: ir_code}
|
||||
for device in _devices:
|
||||
device.turn_on(**kwargs)
|
||||
await device.async_turn_on(**kwargs)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SEND_IR_CODE, send_ir_code_service,
|
||||
DOMAIN, SERVICE_SEND_IR_CODE, async_send_ir_code_service,
|
||||
schema=SEND_IR_CODE_SERVICE_SCHEMA)
|
||||
|
||||
|
||||
|
@ -84,23 +84,23 @@ class MySensorsSwitch(mysensors.MySensorsEntity, SwitchDevice):
|
|||
"""Return True if switch is on."""
|
||||
return self._values.get(self.value_type) == STATE_ON
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, self.value_type, 1)
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
self._values[self.value_type] = STATE_ON
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the switch off."""
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, self.value_type, 0)
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
self._values[self.value_type] = STATE_OFF
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
|
||||
class MySensorsIRSwitch(MySensorsSwitch):
|
||||
|
@ -117,7 +117,7 @@ class MySensorsIRSwitch(MySensorsSwitch):
|
|||
set_req = self.gateway.const.SetReq
|
||||
return self._values.get(set_req.V_LIGHT) == STATE_ON
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the IR switch on."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
if ATTR_IR_CODE in kwargs:
|
||||
|
@ -130,11 +130,11 @@ class MySensorsIRSwitch(MySensorsSwitch):
|
|||
# optimistically assume that switch has changed state
|
||||
self._values[self.value_type] = self._ir_code
|
||||
self._values[set_req.V_LIGHT] = STATE_ON
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
# turn off switch after switch was turned on
|
||||
self.turn_off()
|
||||
await self.async_turn_off()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the IR switch off."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
self.gateway.set_child_value(
|
||||
|
@ -142,7 +142,7 @@ class MySensorsIRSwitch(MySensorsSwitch):
|
|||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
self._values[set_req.V_LIGHT] = STATE_OFF
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the controller with the latest value from a sensor."""
|
||||
|
|
|
@ -869,7 +869,7 @@ pymusiccast==0.1.6
|
|||
pymyq==0.0.8
|
||||
|
||||
# homeassistant.components.mysensors
|
||||
pymysensors==0.11.1
|
||||
pymysensors==0.14.0
|
||||
|
||||
# homeassistant.components.lock.nello
|
||||
pynello==1.5.1
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue