-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add version For the compatibility with the future version of HASS * Compatibility with Home Assistant 2022.3.0 Import Coco Submodule in this module * Revert "Merge branch 'master' of https://github.com/filipvh/hass-nhc2 into filipvh-master" This reverts commit dc11149, reversing changes made to d59f22d. * Update fan.py Patch SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM error in HASS 2022.4.0 Co-authored-by: cebos <cedric.bosch@chc.be>
- Loading branch information
Showing
26 changed files
with
1,210 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
import json | ||
import logging | ||
import os | ||
import threading | ||
from time import sleep | ||
from typing import Callable | ||
|
||
import paho.mqtt.client as mqtt | ||
|
||
from .coco_device_class import CoCoDeviceClass | ||
from .coco_fan import CoCoFan | ||
from .coco_light import CoCoLight | ||
from .coco_shutter import CoCoShutter | ||
from .coco_switch import CoCoSwitch | ||
from .coco_switched_fan import CoCoSwitchedFan | ||
from .coco_climate import CoCoThermostat | ||
from .coco_generic import CoCoGeneric | ||
|
||
from .const import * | ||
from .helpers import * | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
sem = threading.Semaphore() | ||
DEVICE_SETS = { | ||
CoCoDeviceClass.SWITCHED_FANS: {INTERNAL_KEY_CLASS: CoCoSwitchedFan, INTERNAL_KEY_MODELS: LIST_VALID_SWITCHED_FANS}, | ||
CoCoDeviceClass.FANS: {INTERNAL_KEY_CLASS: CoCoFan, INTERNAL_KEY_MODELS: LIST_VALID_FANS}, | ||
CoCoDeviceClass.SHUTTERS: {INTERNAL_KEY_CLASS: CoCoShutter, INTERNAL_KEY_MODELS: LIST_VALID_SHUTTERS}, | ||
CoCoDeviceClass.SWITCHES: {INTERNAL_KEY_CLASS: CoCoSwitch, INTERNAL_KEY_MODELS: LIST_VALID_SWITCHES}, | ||
CoCoDeviceClass.LIGHTS: {INTERNAL_KEY_CLASS: CoCoLight, INTERNAL_KEY_MODELS: LIST_VALID_LIGHTS}, | ||
CoCoDeviceClass.THERMOSTATS: {INTERNAL_KEY_CLASS: CoCoThermostat, INTERNAL_KEY_MODELS: LIST_VALID_THERMOSTATS}, | ||
CoCoDeviceClass.GENERIC: {INTERNAL_KEY_CLASS: CoCoGeneric, INTERNAL_KEY_MODELS: LIST_VALID_GENERICS} | ||
} | ||
|
||
|
||
class CoCo: | ||
def __init__(self, address, username, password, port=8883, ca_path=None, switches_as_lights=False): | ||
|
||
if switches_as_lights: | ||
DEVICE_SETS[CoCoDeviceClass.LIGHTS] = {INTERNAL_KEY_CLASS: CoCoLight, | ||
INTERNAL_KEY_MODELS: LIST_VALID_LIGHTS + LIST_VALID_SWITCHES} | ||
DEVICE_SETS[CoCoDeviceClass.SWITCHES] = {INTERNAL_KEY_CLASS: CoCoSwitch, INTERNAL_KEY_MODELS: []} | ||
# The device control buffer fields | ||
self._keep_thread_running = True | ||
self._device_control_buffer = {} | ||
self._device_control_buffer_size = DEVICE_CONTROL_BUFFER_SIZE | ||
self._device_control_buffer_command_size = DEVICE_CONTROL_BUFFER_COMMAND_SIZE | ||
self._device_control_buffer_command_count = 0 | ||
self._device_control_buffer_thread = threading.Thread(target=self._publish_device_control_commands) | ||
self._device_control_buffer_thread.start() | ||
|
||
if ca_path is None: | ||
ca_path = os.path.dirname(os.path.realpath(__file__)) + MQTT_CERT_FILE | ||
client = mqtt.Client(protocol=MQTT_PROTOCOL, transport=MQTT_TRANSPORT) | ||
client.username_pw_set(username, password) | ||
client.tls_set(ca_path) | ||
client.tls_insecure_set(True) | ||
self._client = client | ||
self._address = address | ||
self._port = port | ||
self._profile_creation_id = username | ||
self._all_devices = None | ||
self._device_callbacks = {} | ||
self._devices = {} | ||
self._devices_callback = {} | ||
self._system_info = None | ||
self._system_info_callback = lambda x: None | ||
|
||
def __del__(self): | ||
self._keep_thread_running = False | ||
self._client.disconnect() | ||
|
||
def connect(self): | ||
|
||
def _on_message(client, userdata, message): | ||
topic = message.topic | ||
response = json.loads(message.payload) | ||
|
||
if topic == self._profile_creation_id + MQTT_TOPIC_PUBLIC_RSP and \ | ||
response[KEY_METHOD] == MQTT_METHOD_SYSINFO_PUBLISH: | ||
self._system_info = response | ||
self._system_info_callback(self._system_info) | ||
|
||
elif topic == (self._profile_creation_id + MQTT_TOPIC_SUFFIX_RSP) and \ | ||
response[KEY_METHOD] == MQTT_METHOD_DEVICES_LIST: | ||
self._client.unsubscribe(self._profile_creation_id + MQTT_TOPIC_SUFFIX_RSP) | ||
self._process_devices_list(response) | ||
|
||
elif topic == (self._profile_creation_id + MQTT_TOPIC_SUFFIX_SYS_EVT) and \ | ||
response[KEY_METHOD] == MQTT_METHOD_SYSINFO_PUBLISHED: | ||
# If the connected controller publishes sysinfo... we expect something to have changed. | ||
client.subscribe(self._profile_creation_id + MQTT_TOPIC_SUFFIX_RSP, qos=1) | ||
client.publish(self._profile_creation_id + MQTT_TOPIC_SUFFIX_CMD, | ||
json.dumps({KEY_METHOD: MQTT_METHOD_DEVICES_LIST}), 1) | ||
|
||
elif topic == (self._profile_creation_id + MQTT_TOPIC_SUFFIX_EVT) \ | ||
and (response[KEY_METHOD] == MQTT_METHOD_DEVICES_STATUS or response[ | ||
KEY_METHOD] == MQTT_METHOD_DEVICES_CHANGED): | ||
devices = extract_devices(response) | ||
for device in devices: | ||
try: | ||
if KEY_UUID in device: | ||
self._device_callbacks[device[KEY_UUID]][INTERNAL_KEY_CALLBACK](device) | ||
except: | ||
pass | ||
|
||
def _on_connect(client, userdata, flags, rc): | ||
if rc == 0: | ||
_LOGGER.info('Connected!') | ||
client.subscribe(self._profile_creation_id + MQTT_TOPIC_SUFFIX_RSP, qos=1) | ||
client.subscribe(self._profile_creation_id + MQTT_TOPIC_PUBLIC_RSP, qos=1) | ||
client.subscribe(self._profile_creation_id + MQTT_TOPIC_SUFFIX_EVT, qos=1) | ||
client.subscribe(self._profile_creation_id + MQTT_TOPIC_SUFFIX_SYS_EVT, qos=1) | ||
client.publish(self._profile_creation_id + MQTT_TOPIC_PUBLIC_CMD, | ||
json.dumps({KEY_METHOD: MQTT_METHOD_SYSINFO_PUBLISH}), 1) | ||
client.publish(self._profile_creation_id + MQTT_TOPIC_SUFFIX_CMD, | ||
json.dumps({KEY_METHOD: MQTT_METHOD_DEVICES_LIST}), 1) | ||
elif MQTT_RC_CODES[rc]: | ||
raise Exception(MQTT_RC_CODES[rc]) | ||
else: | ||
raise Exception('Unknown error') | ||
|
||
def _on_disconnect(client, userdata, rc): | ||
_LOGGER.warning('Disconnected') | ||
for uuid, device_callback in self._device_callbacks.items(): | ||
offline = {'Online': 'False', KEY_UUID: uuid} | ||
device_callback[INTERNAL_KEY_CALLBACK](offline) | ||
|
||
self._client.on_message = _on_message | ||
self._client.on_connect = _on_connect | ||
self._client.on_disconnect = _on_disconnect | ||
|
||
self._client.connect_async(self._address, self._port) | ||
self._client.loop_start() | ||
|
||
def disconnect(self): | ||
self._client.loop_stop() | ||
self._client.disconnect() | ||
|
||
def get_systeminfo(self, callback): | ||
self._system_info_callback = callback | ||
if self._system_info: | ||
self._system_info_callback(self._system_info) | ||
|
||
def get_devices(self, device_class: CoCoDeviceClass, callback: Callable): | ||
self._devices_callback[device_class] = callback | ||
if self._devices and device_class in self._devices: | ||
self._devices_callback[device_class](self._devices[device_class]) | ||
|
||
def _publish_device_control_commands(self): | ||
while self._keep_thread_running: | ||
device_commands_to_process = None | ||
sem.acquire() | ||
if len(self._device_control_buffer.keys()) > 0: | ||
device_commands_to_process = self._device_control_buffer | ||
self._device_control_buffer = {} | ||
self._device_control_buffer_command_count = 0 | ||
sem.release() | ||
if device_commands_to_process is not None: | ||
command = process_device_commands(device_commands_to_process) | ||
self._client.publish(self._profile_creation_id + MQTT_TOPIC_SUFFIX_CMD, json.dumps(command), 1) | ||
sleep(0.05) | ||
|
||
def _add_device_control(self, uuid, property_key, property_value): | ||
while len(self._device_control_buffer.keys()) >= self._device_control_buffer_size or \ | ||
self._device_control_buffer_command_count >= self._device_control_buffer_command_size: | ||
pass | ||
sem.acquire() | ||
self._device_control_buffer_command_count += 1 | ||
if uuid not in self._device_control_buffer: | ||
self._device_control_buffer[uuid] = {} | ||
self._device_control_buffer[uuid][property_key] = property_value | ||
sem.release() | ||
|
||
# Processes response on devices.list | ||
def _process_devices_list(self, response): | ||
|
||
# Only add devices that are actionable | ||
actionable_devices = list( | ||
filter(lambda d: d[KEY_TYPE] == DEV_TYPE_ACTION, extract_devices(response))) | ||
actionable_devices.extend(list( | ||
filter(lambda d: d[KEY_TYPE] == "thermostat", extract_devices(response)))) | ||
|
||
# Only prepare for devices that don't already exist | ||
# TODO - Can't we do this when we need it (in initialize_devices ?) | ||
existing_uuids = list(self._device_callbacks.keys()) | ||
for actionable_device in actionable_devices: | ||
if actionable_device[KEY_UUID] not in existing_uuids: | ||
self._device_callbacks[actionable_device[KEY_UUID]] = \ | ||
{INTERNAL_KEY_CALLBACK: None, KEY_ENTITY: None} | ||
|
||
# Initialize | ||
self.initialize_devices(CoCoDeviceClass.SWITCHED_FANS, actionable_devices) | ||
self.initialize_devices(CoCoDeviceClass.FANS, actionable_devices) | ||
self.initialize_devices(CoCoDeviceClass.SWITCHES, actionable_devices) | ||
self.initialize_devices(CoCoDeviceClass.LIGHTS, actionable_devices) | ||
self.initialize_devices(CoCoDeviceClass.SHUTTERS, actionable_devices) | ||
self.initialize_devices(CoCoDeviceClass.THERMOSTATS, actionable_devices) | ||
self.initialize_devices(CoCoDeviceClass.GENERIC, actionable_devices) | ||
|
||
def initialize_devices(self, device_class, actionable_devices): | ||
|
||
base_devices = [x for x in actionable_devices if x[KEY_MODEL] | ||
in DEVICE_SETS[device_class][INTERNAL_KEY_MODELS]] | ||
if device_class not in self._devices: | ||
self._devices[device_class] = [] | ||
for base_device in base_devices: | ||
if self._device_callbacks[base_device[KEY_UUID]] and self._device_callbacks[base_device[KEY_UUID]][ | ||
KEY_ENTITY] and \ | ||
self._device_callbacks[base_device[KEY_UUID]][KEY_ENTITY].uuid: | ||
self._device_callbacks[base_device[KEY_UUID]][KEY_ENTITY].update_dev(base_device) | ||
else: | ||
self._device_callbacks[base_device[KEY_UUID]][KEY_ENTITY] = \ | ||
DEVICE_SETS[device_class][INTERNAL_KEY_CLASS](base_device, | ||
self._device_callbacks[ | ||
base_device[ | ||
KEY_UUID]], | ||
self._client, | ||
self._profile_creation_id, | ||
self._add_device_control) | ||
self._devices[device_class].append(self._device_callbacks[base_device[KEY_UUID]][KEY_ENTITY]) | ||
if device_class in self._devices_callback: | ||
self._devices_callback[device_class](self._devices[device_class]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
-----BEGIN CERTIFICATE----- | ||
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 | ||
-----END CERTIFICATE----- | ||
-----BEGIN CERTIFICATE----- | ||
MIIF6jCCA9KgAwIBAgIJANTA8rXGnhG7MA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD | ||
VQQGEwJCRTEYMBYGA1UECAwPT29zdC1WbGFhbmRlcmVuMRUwEwYDVQQHDAxTaW50 | ||
LU5pa2xhYXMxDTALBgNVBAoMBE5pa28xFTATBgNVBAsMDEhvbWUgQ29udHJvbDEb | ||
MBkGCSqGSIb3DQEJARYMaW5mb0BuaWtvLmJlMB4XDTcwMDEwMTAwMDAwNVoXDTM3 | ||
MDEyOTAwMDAwNVowgYExCzAJBgNVBAYTAkJFMRgwFgYDVQQIDA9Pb3N0LVZsYWFu | ||
ZGVyZW4xFTATBgNVBAcMDFNpbnQtTmlrbGFhczENMAsGA1UECgwETmlrbzEVMBMG | ||
A1UECwwMSG9tZSBDb250cm9sMRswGQYJKoZIhvcNAQkBFgxpbmZvQG5pa28uYmUw | ||
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpKNKKHC0fCND19D96G78G | ||
Zdj+OGLvy/DRJswbLepG8cPedqEZwXjn762fvLdTlcTX/ohkeG4QPb1mxPzjpEgl | ||
M5aNmp2rmlAFVtLWILQx7mWir5FjG5eTyYi2fbYHnPQpx8XuVk2INENd85818R4j | ||
RfouYLaZWSd8wc7LP20N0rVtjg5RJ/zAkQ6A7KzdgeOkKhn07wSGBWu9vDw7gCdL | ||
+Oyeo4LQmABXB7up8nIDCl+o23QL4/aSzdrS5cBCXoPWwto7OiXw0RRcEbpumQyW | ||
mTGS8jT2FCUNAIWAxC3pKEIXbzf03pLo7EMfFcmjsLDcvcnkB+EJX0fuATwl5CLz | ||
SneUFY7MNTpv9xgZFX83LhoiFbycZwzWEUr/Q0pmHYZdmezm84+W6EA3E9qH+oR8 | ||
V09bwEMAMSQpbebEB8JmvvwykQHxowkpnV01bmimBEOaquAmyfiW3YSO90vJu+kg | ||
Zrkihc0AEMFcDbLRCEKvx/u6Hs2xMmVPz0W9mPW37t5zKOV0vcrHmFgMp+9EyDAQ | ||
vfNofLx790lD1LFp3qvD/H0+IbydQoEc7Q1/tTQDjL45TLNXwwBWQVQLIEQY5sqN | ||
n8p2ita3MPpSnu5XU93pBcns8jUNlc6/wFIMSBDWK40RiJKzTsr/2jTGVqZX8PXA | ||
rDnIoa0Eapt0nq87qnkQzQIDAQABo2MwYTAdBgNVHQ4EFgQU5rQ0Z/a3fq5iKhZV | ||
qmabTvd4kFkwHwYDVR0jBBgwFoAU5rQ0Z/a3fq5iKhZVqmabTvd4kFkwDwYDVR0T | ||
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAFLw | ||
6lbxex6hElSrqOZljoFQzQg78dmtUFm4BgKu5EAqn1Ug/OHOKk8LBbNMf2X0Y+4i | ||
SO4g8yO/C9M/YPxjnM5As1ouTu7UzQL4hEJynyHoPuCBD8nZxuaKeGMX7nQYm7GD | ||
0iaL0iP9gFwv/A2O/isQUB/sTAclhm1zKAw4f/SaBq8t22wf59e8na0xIfHui0PD | ||
s8PfRbC4xIOKMxHkHFv+DHeMGjCbR4x20RV/z4JNx1ALEBGo6Oh7Dph/maAQWbje | ||
x9BCstNR3V1Bhx9rUe7BjIMyJUGEItpZXG+N+qnQr2K7xDdloJl4X0flIa74sdUE | ||
K4s0X7p+JixLMSxbu5oS6W+d3g6EG0ZgEUwwwc98D1fsm1ziNqwcnYMkI6P2601G | ||
kEaK/54kYqCxvw6fu5+PNmsDD8ptdazoO3/UOxWvspI1U3drcpnaEHuNclEF7WeL | ||
yqTfi+8UiL9xJgq9ivjKjZdchkdaD2THgrnzs0XxLbZnwAPeh3cHooUJQkInmKp3 | ||
O05Gv0rnSr29bH8vh/sy4/yJJCUd036pF9C8mPHAYsvNDVGaGYVmNt5P28z3PO16 | ||
YKNJCOJ0x333F6PJaqWAQQP9bGMuJThX8ZQ9Fd8KMXVUfFVKICEkb4erWpL2RIz3 | ||
9JFSC56ZtXv2losfASTyXJwCpyib7FcTZ1rJze+l | ||
-----END CERTIFICATE----- |
Oops, something went wrong.