Discovery (#74)

* add support for Home Assistants MQTT discovery

* add format_topic a flexible separator

* add mqtt discovery for miflora

* add mqtt discovery for mithermometer

* add mqtt discovery for thermostat

* add unit to illuminance in miflora

* switch to 'away_mode_command_topic' instead of 'away_command_topic'

* revert changing the date format

* add mac to ientifiers and change the id generation

* add mac to ientifiers and change the id generation on thermostat

* add mac to ientifiers and change the id generation for mithermometer
This commit is contained in:
Marcel Hoppe
2019-06-21 12:25:27 +02:00
committed by zewelor
parent 01b30f6c4a
commit 8fbf3540bd
8 changed files with 192 additions and 35 deletions
+5 -2
View File
@@ -8,8 +8,11 @@ class BaseWorker:
def _setup(self):
return
def format_topic(self, *args):
return '/'.join([self.topic_prefix, *args])
def format_id(self, *args, separator='/'):
return separator.join(["bt-mqtt-gateway", repr(self), *args])
def format_topic(self, *args, separator='/'):
return separator.join([self.topic_prefix, *args])
def __repr__(self):
return self.__module__.split(".")[-1]
+47 -9
View File
@@ -1,6 +1,4 @@
import logging
from mqtt import MqttMessage
from mqtt import MqttMessage, MqttConfigMessage
from workers.base import BaseWorker
import logger
@@ -18,18 +16,58 @@ class MifloraWorker(BaseWorker):
_LOGGER.info("Adding %d %s devices", len(self.devices), repr(self))
for name, mac in self.devices.items():
_LOGGER.debug("Adding %s device '%s' (%s)", repr(self), name, mac)
self.devices[name] = MiFloraPoller(mac, BluepyBackend)
self.devices[name] = {"mac": mac, "poller": MiFloraPoller(mac, BluepyBackend)}
def config(self):
ret = []
for name, data in self.devices.items():
ret += self.config_device(name, data["mac"])
return ret
def config_device(self, name, mac):
ret = []
device={"identifiers": [mac, self.format_id(name, separator="_")],
"manufacturer": "Xiaomi",
"model": "MiFlora",
"name": self.format_topic(name, separator=" ").title()}
for attr in monitoredAttrs:
if attr == 'light':
payload = {"unique_id": self.format_id(name, 'illuminance', separator="_"),
"state_topic": self.format_topic(name, attr),
"device_class": 'illuminance',
"unit_of_measurement": "lux",
"device": device}
elif attr == 'moisture':
payload = {"unique_id": self.format_id(name, attr, separator="_"),
"state_topic": self.format_topic(name, attr),
"icon": 'mdi:water',
"unit_of_measurement": "%",
"device": device}
elif attr == 'conductivity':
payload = {"unique_id": self.format_id(name, attr, separator="_"),
"state_topic": self.format_topic(name, attr),
"icon": 'mdi:leaf',
"unit_of_measurement": "µS/cm",
"device": device}
else:
payload = {"unique_id": self.format_id(name, attr, separator="_"),
"state_topic": self.format_topic(name, attr),
"device_class": attr,
"device": device}
ret.append(MqttConfigMessage(MqttConfigMessage.SENSOR, self.format_topic(name, attr, separator="_"), payload=payload))
return ret
def status_update(self):
from btlewrap.base import BluetoothBackendException
_LOGGER.info("Updating %d %s devices", len(self.devices), repr(self))
ret = []
for name, poller in self.devices.items():
_LOGGER.debug("Updating %s device '%s' (%s)", repr(self), name, poller._mac)
for name, data in self.devices.items():
_LOGGER.debug("Updating %s device '%s' (%s)", repr(self), name, data["mac"])
from btlewrap import BluetoothBackendException
try:
ret += self.update_device_state(name, poller)
ret += self.update_device_state(name, data["poller"])
except BluetoothBackendException as e:
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), name, poller._mac, type(e).__name__, suppress=True)
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), name, data["mac"], type(e).__name__, suppress=True)
return ret
def update_device_state(self, name, poller):
+23 -8
View File
@@ -1,11 +1,11 @@
import logging
from mqtt import MqttMessage
from mqtt import MqttMessage, MqttConfigMessage
from workers.base import BaseWorker
import logger
REQUIREMENTS = ['git+https://github.com/cybe/mithermometer.git@cd8dba297927da823fbfa8f50bd97393ea6a93c1#egg=mithermometer']
REQUIREMENTS = ['mithermometer']
monitoredAttrs = ["temperature", "humidity", "battery"]
_LOGGER = logger.get(__name__)
@@ -18,18 +18,33 @@ class MithermometerWorker(BaseWorker):
_LOGGER.info("Adding %d %s devices", len(self.devices), repr(self))
for name, mac in self.devices.items():
_LOGGER.debug("Adding %s device '%s' (%s)", repr(self), name, mac)
self.devices[name] = MiThermometerPoller(mac, BluepyBackend)
self.devices[name] = {"mac":mac, "poller": MiThermometerPoller(mac, BluepyBackend)}
def config(self):
ret = []
for name, data in self.devices.items():
ret += self.config_device(name, data["mac"])
return ret
def config_device(self, name, mac):
ret = []
device={"identifiers": [mac, self.format_id(name, separator="_")], "manufacturer": "Xiaomi", "model": "LYWSD(CGQ/01ZM)", "name": self.format_topic(name, separator=" ").title()}
for attr in monitoredAttrs:
payload = {"unique_id": self.format_id(name, attr, separator="_"), "state_topic": self.format_topic(name, attr), "device_class": attr, "device": device}
ret.append(MqttConfigMessage(MqttConfigMessage.SENSOR, self.format_topic(name, attr, separator="_"), payload=payload))
return ret
def status_update(self):
from btlewrap.base import BluetoothBackendException
_LOGGER.info("Updating %d %s devices", len(self.devices), repr(self))
ret = []
for name, poller in self.devices.items():
_LOGGER.debug("Updating %s device '%s' (%s)", repr(self), name, poller._mac)
for name, data in self.devices.items():
_LOGGER.debug("Updating %s device '%s' (%s)", repr(self), name, data["mac"])
from btlewrap import BluetoothBackendException
try:
ret += self.update_device_state(name, poller)
ret += self.update_device_state(name, data["poller"])
except BluetoothBackendException as e:
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), name, poller._mac, type(e).__name__, suppress=True)
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), name, data["mac"], type(e).__name__, suppress=True)
return ret
def update_device_state(self, name, poller):
+73 -15
View File
@@ -1,7 +1,7 @@
from builtins import staticmethod
import logging
from mqtt import MqttMessage
from mqtt import MqttMessage, MqttConfigMessage
from workers.base import BaseWorker
import logger
@@ -13,6 +13,7 @@ _LOGGER = logger.get(__name__)
STATE_AWAY = 'away'
STATE_ECO = 'eco'
STATE_HEAT = 'heat'
STATE_AUTO = 'auto'
STATE_MANUAL = 'manual'
STATE_ON = 'on'
STATE_OFF = 'off'
@@ -26,10 +27,10 @@ class ThermostatWorker(BaseWorker):
self._mapped_modes = {
Mode.Closed: STATE_OFF,
Mode.Open: STATE_ON,
Mode.Auto: STATE_HEAT,
Mode.Auto: STATE_AUTO,
Mode.Manual: STATE_MANUAL,
Mode.Away: STATE_ECO,
Mode.Boost: 'boost',
Mode.Boost: STATE_HEAT,
}
self._reverse_modes = {v: k for k, v in self._mapped_modes.items()}
@@ -56,35 +57,92 @@ class ThermostatWorker(BaseWorker):
else:
return STATE_HEAT
def _setup(self):
from eq3bt import Thermostat
_LOGGER.info("Adding %d %s devices", len(self.devices), repr(self))
for name, mac in self.devices.items():
_LOGGER.debug("Adding %s device '%s' (%s)", repr(self), name, mac)
self.devices[name] = Thermostat(mac)
self.devices[name] = {"mac": mac, "thermostat": Thermostat(mac)}
self._modes_mapper = self.ModesMapper()
def config(self):
ret = []
for name, data in self.devices.items():
ret += self.config_device(name, data["mac"])
return ret
def config_device(self, name, mac):
ret = []
device={"identifiers": [mac, self.format_id(name, separator="_")],
"manufacturer": "eQ-3",
"model": "Smart Radiator Thermostat",
"name": self.format_topic(name, separator=" ").title()}
payload = {"unique_id": self.format_id(name, 'climate', separator="_"),
"qos": 1,
"temperature_state_topic": self.format_topic(name, 'target_temperature'),
"temperature_command_topic": self.format_topic(name, 'target_temperature', 'set'),
"mode_state_topic": self.format_topic(name, 'mode'),
"mode_command_topic": self.format_topic(name, 'mode', 'set'),
"away_mode_state_topic": self.format_topic(name, 'away'),
"away_mode_command_topic": self.format_topic(name, 'away', 'set'),
"min_temp": 5.0,
"max_temp": 29.5,
"temp_step": 0.5,
"payload_on": "'on'",
"payload_off": "'off'",
"modes": ["heat", "auto", "manual", "eco", 'off'],
"device": device}
ret.append(MqttConfigMessage(MqttConfigMessage.CLIMATE, self.format_topic(name, 'climate', separator="_"), payload=payload))
payload = {"unique_id": self.format_id(name, 'window_open', separator="_"),
"state_topic": self.format_topic(name, 'window_open'),
"device_class": 'window',
"device": device}
ret.append(MqttConfigMessage(MqttConfigMessage.BINARY_SENSOR, self.format_topic(name, 'window_open', separator="_"), payload=payload))
payload = {"unique_id": self.format_id(name, 'low_battery', separator="_"),
"state_topic": self.format_topic(name, 'low_battery'),
"device_class": 'battery',
"device": device}
ret.append(MqttConfigMessage(MqttConfigMessage.BINARY_SENSOR, self.format_topic(name, 'low_battery', separator="_"), payload=payload))
payload = {"unique_id": self.format_id(name, 'locked', separator="_"),
"state_topic": self.format_topic(name, 'locked'),
"device_class": 'lock',
"device": device}
ret.append(MqttConfigMessage(MqttConfigMessage.BINARY_SENSOR, self.format_topic(name, 'locked', separator="_"),
payload=payload))
payload = {"unique_id": self.format_id(name, "valve_state", separator="_"),
"state_topic": self.format_topic(name, "valve_state"),
"unit_of_measurement": "%",
"device": device}
ret.append(MqttConfigMessage(MqttConfigMessage.SENSOR, self.format_topic(name, 'valve_state', separator="_"), payload=payload))
return ret
def status_update(self):
from bluepy import btle
ret = []
_LOGGER.info("Updating %d %s devices", len(self.devices), repr(self))
for name, thermostat in self.devices.items():
_LOGGER.debug("Updating %s device '%s' (%s)", repr(self), name, thermostat._conn._mac)
for name, data in self.devices.items():
_LOGGER.debug("Updating %s device '%s' (%s)", repr(self), name, data["mac"])
try:
ret += self.update_device_state(name, thermostat)
ret += self.update_device_state(name, data["thermostat"])
except btle.BTLEException as e:
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), name, thermostat._conn._mac, type(e).__name__, suppress=True)
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), name, data["mac"], type(e).__name__, suppress=True)
return ret
def on_command(self, topic, value):
from bluepy import btle
_, device_name, method, _ = topic.split('/')
thermostat = self.devices[device_name]
data = self.devices[device_name]
value = value.decode('utf-8')
@@ -98,17 +156,17 @@ class ThermostatWorker(BaseWorker):
elif method == "target_temperature":
value = float(value)
_LOGGER.info("Setting %s to %s on %s device '%s' (%s)", method, value, repr(self), device_name, thermostat._conn._mac)
_LOGGER.info("Setting %s to %s on %s device '%s' (%s)", method, value, repr(self), device_name, data["mac"])
try:
setattr(thermostat, method, value)
setattr(data["thermostat"], method, value)
except btle.BTLEException as e:
logger.log_exception(_LOGGER, "Error setting %s to %s on %s device '%s' (%s): %s", method, value, repr(self), device_name, thermostat._conn._mac, type(e).__name__)
logger.log_exception(_LOGGER, "Error setting %s to %s on %s device '%s' (%s): %s", method, value, repr(self), device_name, data["mac"], type(e).__name__)
return []
try:
return self.update_device_state(device_name, thermostat)
return self.update_device_state(device_name, data["thermostat"])
except btle.BTLEException as e:
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), device_name, thermostat._conn._mac, type(e).__name__, suppress=True)
logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), device_name, data["mac"], type(e).__name__, suppress=True)
return []
def update_device_state(self, name, thermostat):