From 61e6987101c8c9f3209f76ffacab52703861df8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Fri, 1 Nov 2019 09:35:33 +0100 Subject: [PATCH] Discovery can be deleted when component removed, code beautifications --- README.md | 24 +++-- _templates/component_template.py | 30 ++++--- _templates/components.py | 2 +- dev/ecMeter.py | 9 +- dev/moisture.py | 30 ++++--- dev/phSensor.py | 13 +-- .../devices/arduinoGPIO/arduinoControl.py | 3 +- .../components/devices/climate/__init__.py | 89 ++++++++++--------- .../components/devices/climate/heat.py | 10 +-- pysmartnode/components/devices/climate/off.py | 10 +-- pysmartnode/components/machine/adc.py | 22 ++--- pysmartnode/components/machine/deepsleep.py | 8 +- pysmartnode/components/machine/easyGPIO.py | 31 +++---- pysmartnode/components/machine/i2c.py | 8 +- pysmartnode/components/machine/pin.py | 14 +-- pysmartnode/components/machine/stats.py | 19 ++-- pysmartnode/components/machine/watchdog.py | 8 +- pysmartnode/components/machine/wifi_led.py | 2 +- pysmartnode/components/multiplexer/amux.py | 10 +-- pysmartnode/components/multiplexer/mux.py | 8 +- pysmartnode/components/multiplexer/pmux.py | 7 +- pysmartnode/components/sensors/battery.py | 14 +-- pysmartnode/components/sensors/bell.py | 35 +++++--- pysmartnode/components/sensors/dht22.py | 6 +- pysmartnode/components/sensors/ds18.py | 21 ++--- pysmartnode/components/sensors/hcsr04.py | 2 +- pysmartnode/components/sensors/htu21d.py | 13 +-- pysmartnode/components/switches/buzzer.py | 8 +- pysmartnode/components/switches/gpio.py | 8 +- pysmartnode/components/switches/led.py | 8 +- .../switches/switch_extension/__init__.py | 22 +++-- pysmartnode/networking/wifi_esp32_lobo.py | 11 ++- pysmartnode/utils/component/__init__.py | 30 +++++-- pysmartnode/utils/component/sensor.py | 17 ++-- pysmartnode/utils/component/switch.py | 12 ++- pysmartnode/utils/event.py | 8 +- pysmartnode/utils/sys_vars.py | 8 +- 37 files changed, 325 insertions(+), 255 deletions(-) diff --git a/README.md b/README.md index ea4837e..9b674d3 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,24 @@ Every loaded component will be published as *log.info* to */log/info/Sensors all have a similar API to have a standardized usage. -For example all temperature sensors provide the function (coroutine actually) *sensor.temperature(publish=True)* that returns the temperature as float and takes the argument *publish=True* controlling if the read value should be published to mqtt in addition to reading the value. -This should make working with different types of sensors easier. If you are e.g. building a heating controller and need a temperature from some sensor, you can just connect any sensor and provide the heating code with that sensor by configuration. -As every temperature sensor has the function (actually coroutine) *temperature()* returning the current temperature in float (or None on error) it does not care about which sensor is connected. -
Switch components can be integrated even easier using the [switch base class](./pysmartnode/utils/component/switch.py). +Components can be and do anything. There is a basic API and [base class](./pysmartnode/utils/component/__init__.py) which helps with homeassistant mqtt discovery and the basic API. +
Sensors all have a similar API to have a standardized usage through the [sensor base class](./pysmartnode/utils/component/sensor.py). +The sensor base class makes developing sensors very easy as it takes care of mqtt discovery, reading and publishing intervals and the standardized API. +All sensors now have a common API: +- getValue(sensor_type)->sensor_type last read value +- getTopic(sensor_type)->mqtt topic of sensor_type +- getTemplate(sensor_type)->homeassistant value template of sensor_type +- getTimestamp(sensor_type)->timestamp of last successful sensor reading +- getReadingsEvent()->Event being set on next sensor reading + +
Sensor_types in definitions but can be custom, those are only the ones supported by Homeassistant. + +
Common features: +- Reading interval and publish interval separated and not impacting each other +- Reading and publish intervals can be changed during runtime, optionally by mqtt +- Sensor subclass only needs to implement a _read() function reading the sensor and submitting the read values. Base class does everything else (publishing, discovery, ...) +
This should make working with different types of sensors easier. If you are e.g. building a heating controller and need a temperature from some sensor, you can just connect any sensor and provide the heating code with that sensor by configuration. +
Switch components can be integrated similarily easy using the [switch base class](./pysmartnode/utils/component/switch.py).
Templates for how to use the components can be found in [templates](./_templates). ### MQTT-Discovery diff --git a/_templates/component_template.py b/_templates/component_template.py index d9bb111..e018b64 100644 --- a/_templates/component_template.py +++ b/_templates/component_template.py @@ -1,8 +1,6 @@ -''' -Created on 2018-06-22 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2018 Released under the MIT license +# Created on 2018-06-22 """ example config for MyComponent: @@ -13,13 +11,14 @@ my_value: "hi there" # mqtt_topic: sometopic # optional, defaults to home////set # mqtt_topic2: sometopic # optional, defautls to home/sometopic - # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery + # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery + # discover: true # optional, if false no discovery message for homeassistant will be sent. } } """ -__updated__ = "2019-10-31" -__version__ = "1.6" +__updated__ = "2019-11-01" +__version__ = "1.7" import uasyncio as asyncio from pysmartnode import config @@ -114,7 +113,13 @@ async def _remove(self): pass await super()._remove() - async def _discovery(self): + async def _discovery(self, register=True): + """ + Send discovery messages + :param register: if True send discovery message, if False send empty discovery message + to remove the component from homeassistant. + :return: + """ name = "{!s}{!s}".format(COMPONENT_NAME, self._count) component_topic = _mqtt.getDeviceTopic(name) # component topic could be something completely user defined. @@ -122,8 +127,11 @@ async def _discovery(self): component_topic = self._command_topic[:-4] # get the state topic of custom component topic friendly_name = self._frn # define a friendly name for the homeassistant gui. # Doesn't need to be unique - await self._publishDiscovery(_COMPONENT_TYPE, component_topic, name, DISCOVERY_SWITCH, - friendly_name) + if register: + await self._publishDiscovery(_COMPONENT_TYPE, component_topic, name, DISCOVERY_SWITCH, + friendly_name) + else: + await self._deleteDiscovery(_COMPONENT_TYPE, name) del name, component_topic, friendly_name gc.collect() diff --git a/_templates/components.py b/_templates/components.py index 5d1f729..f4bc0fa 100644 --- a/_templates/components.py +++ b/_templates/components.py @@ -41,7 +41,7 @@ } # Alternatively or additionally you can register components manually, -# which saves a lot of RAM as the dict doesn't get loaded into RAM. +# which saves a lot of RAM as no dict doesn't get loaded into RAM. # This example provides the same configuration as the COMPONENT dict above: from pysmartnode import config diff --git a/dev/ecMeter.py b/dev/ecMeter.py index ea3214f..6d53f66 100644 --- a/dev/ecMeter.py +++ b/dev/ecMeter.py @@ -117,12 +117,15 @@ async def _init(self): await gen() await asyncio.sleep(interval) - async def _discovery(self): + async def _discovery(self, register=True): sens = '"unit_of_meas":"mS",' \ '"val_tpl":"{{ value|float }}",' name = "{!s}{!s}{!s}".format(COMPONENT_NAME, self._count, "EC25") - await self._publishDiscovery(_COMPONENT_TYPE, self._topic_ec, name, sens, - self._frn_ec or "EC25") + if register: + await self._publishDiscovery(_COMPONENT_TYPE, self._topic_ec, name, sens, + self._frn_ec or "EC25") + else: + await self._deleteDiscovery(_COMPONENT_TYPE, name) del sens, name gc.collect() sens = '"unit_of_meas":"ppm",' \ diff --git a/dev/moisture.py b/dev/moisture.py index 3edf1fe..4815a21 100644 --- a/dev/moisture.py +++ b/dev/moisture.py @@ -176,7 +176,7 @@ async def _read(self, publish=True, timeout=5) -> list: return [None] * len(sensors) return res - async def _discovery(self): + async def _discovery(self, register=True): amux = not isinstance(self._adc, pyADC) if type(self._sensor_types) == list: im = len(self._sensor_types) @@ -187,18 +187,26 @@ async def _discovery(self): for i in range(im): if self._pub_cv: name = "{!s}{!s}CV".format(COMPONENT_NAME, i) - sens = DISCOVERY_BINARY_SENSOR.format("moisture") # device_class - t = "{!s}/{!s}/conv".format(self.humidityTopic(), i) - await self._publishDiscovery("binary_sensor", t, name, sens, - self._frn_cv or "Moisture") + if register: + sens = DISCOVERY_BINARY_SENSOR.format("moisture") # device_class + t = "{!s}/{!s}/conv".format(self.humidityTopic(), i) + await self._publishDiscovery("binary_sensor", t, name, sens, + self._frn_cv or "Moisture") + del sens + else: + await self._deleteDiscovery("binary_sensor", name) name = "{!s}{!s}".format(COMPONENT_NAME, i) t = "{!s}/{!s}".format(self.humidityTopic(), i) - sens = self._composeSensorType("humidity", # device_class - "%", # unit_of_measurement - _VAL_T_HUMIDITY) # value_template - await self._publishDiscovery(_COMPONENT_TYPE, t, name, sens, - self._frn or "Moisture rel.") - del name, sens, t + if register: + sens = self._composeSensorType("humidity", # device_class + "%", # unit_of_measurement + _VAL_T_HUMIDITY) # value_template + await self._publishDiscovery(_COMPONENT_TYPE, t, name, sens, + self._frn or "Moisture rel.") + del sens + else: + await self._deleteDiscovery("binary_sensor", name) + del name, t gc.collect() async def humidity(self, publish=True, timeout=5, no_stale=False) -> list: diff --git a/dev/phSensor.py b/dev/phSensor.py index 339121b..0f0eb02 100644 --- a/dev/phSensor.py +++ b/dev/phSensor.py @@ -32,8 +32,8 @@ growing solution 3.24 3.1 (this is very wrong.., ph actually ~5.2) """ -__updated__ = "2019-10-27" -__version__ = "0.5" +__updated__ = "2019-11-01" +__version__ = "0.6" from pysmartnode import config from pysmartnode.components.machine.adc import ADC @@ -92,10 +92,13 @@ async def _loop(self): self.__ph = await self._read() await asyncio.sleep(interval) - async def _discovery(self): + async def _discovery(self, register=True): name = "{!s}{!s}".format(COMPONENT_NAME, self._count) - await self._publishDiscovery(_COMPONENT_TYPE, self.acidityTopic(), name, PH_TYPE, - self._frn or "pH") + if register: + await self._publishDiscovery(_COMPONENT_TYPE, self.acidityTopic(), name, PH_TYPE, + self._frn or "pH") + else: + await self._deleteDiscovery(_COMPONENT_TYPE, name) async def _read(self, publish=True, timeout=5) -> float: buf = [] diff --git a/pysmartnode/components/devices/arduinoGPIO/arduinoControl.py b/pysmartnode/components/devices/arduinoGPIO/arduinoControl.py index 7019d9f..9e9c11e 100644 --- a/pysmartnode/components/devices/arduinoGPIO/arduinoControl.py +++ b/pysmartnode/components/devices/arduinoGPIO/arduinoControl.py @@ -42,7 +42,8 @@ } """ -from pysmartnode.libraries.arduinoGPIO.arduinoGPIO.arduinoControl import ArduinoControl as _ArduinoControl +from pysmartnode.libraries.arduinoGPIO.arduinoGPIO.arduinoControl import \ + ArduinoControl as _ArduinoControl from pysmartnode.components.machine.pin import Pin as PyPin from pysmartnode import logging diff --git a/pysmartnode/components/devices/climate/__init__.py b/pysmartnode/components/devices/climate/__init__.py index 2b8dbfe..af43177 100644 --- a/pysmartnode/components/devices/climate/__init__.py +++ b/pysmartnode/components/devices/climate/__init__.py @@ -33,8 +33,8 @@ fan_unit """ -__updated__ = "2019-10-30" -__version__ = "0.7" +__updated__ = "2019-11-01" +__version__ = "0.8" from pysmartnode import config from pysmartnode import logging @@ -68,35 +68,11 @@ _count = 0 -class BaseMode: - """ - Base class for all modes - """ - - def __init__(self, climate): - pass - - async def trigger(self, climate, current_temp): - """Triggered whenever the situation is evaluated again""" - raise NotImplementedError - - async def activate(self, climate): - """Triggered whenever the mode changes and this mode has been activated""" - raise NotImplementedError - - async def deactivate(self, climate): - """Triggered whenever the mode changes and this mode has been deactivated""" - raise NotImplementedError - - def __str__(self): - """Name of the mode, has to be the same as the classname/module""" - raise NotImplementedError - - class Climate(Component): - def __init__(self, temperature_sensor, heating_unit, modes: list, interval=300, - temp_step=0.1, min_temp=16, max_temp=28, temp_low=20, temp_high=21, - away_temp_low=16, away_temp_high=17, + def __init__(self, temperature_sensor: ComponentSensor, heating_unit: ComponentSwitch, + modes: list, interval: float = 300, temp_step=0.1, min_temp: float = 16, + max_temp: float = 28, temp_low: float = 20, temp_high: float = 21, + away_temp_low: float = 16, away_temp_high: float = 17, friendly_name=None, discover=True): self.checkSensorType(temperature_sensor, SENSOR_TEMPERATURE) self.checkSwitchType(heating_unit) @@ -110,8 +86,8 @@ def __init__(self, temperature_sensor, heating_unit, modes: list, interval=300, self._temp_step = temp_step self._min_temp = min_temp self._max_temp = max_temp - self.temp_sensor = temperature_sensor - self.heating_unit = heating_unit + self.temp_sensor: ComponentSensor = temperature_sensor + self.heating_unit: ComponentSwitch = heating_unit self._modes = {} if "off" not in modes: modes.append("off") @@ -302,20 +278,49 @@ async def changeTempLow(self, topic, msg, retain): self.event.set() return False - async def _discovery(self): + async def _discovery(self, register=True): name = "{!s}{!s}".format(COMPONENT_NAME, self._count) base_topic = _mqtt.getRealTopic(_mqtt.getDeviceTopic(name)) modes = ujson.dumps([str(mode) for mode in self._modes]) gc.collect() - sens = CLIMATE_DISCOVERY.format(base_topic, self._frn or name, self._composeAvailability(), - sys_vars.getDeviceID(), name, # unique_id - _mqtt.getRealTopic( - self.temp_sensor.getTopic(SENSOR_TEMPERATURE)), - # current_temp_topic - self.temp_sensor.getTemplate(SENSOR_TEMPERATURE), - # cur_temp_template - self._temp_step, self._min_temp, self._max_temp, - modes, sys_vars.getDeviceDiscovery()) + if register: + sens = CLIMATE_DISCOVERY.format(base_topic, self._frn or name, + self._composeAvailability(), + sys_vars.getDeviceID(), name, # unique_id + _mqtt.getRealTopic( + self.temp_sensor.getTopic(SENSOR_TEMPERATURE)), + # current_temp_topic + self.temp_sensor.getTemplate(SENSOR_TEMPERATURE), + # cur_temp_template + self._temp_step, self._min_temp, self._max_temp, + modes, sys_vars.getDeviceDiscovery()) + else: + sens = "" gc.collect() topic = Component._getDiscoveryTopic(_COMPONENT_TYPE, name) await _mqtt.publish(topic, sens, qos=1, retain=True) + + +class BaseMode: + """ + Base class for all modes + """ + + def __init__(self, climate: Climate): + pass + + async def trigger(self, climate: Climate, current_temp: float) -> bool: + """Triggered whenever the situation is evaluated again""" + raise NotImplementedError + + async def activate(self, climate: Climate) -> bool: + """Triggered whenever the mode changes and this mode has been activated""" + raise NotImplementedError + + async def deactivate(self, climate: Climate) -> bool: + """Triggered whenever the mode changes and this mode has been deactivated""" + raise NotImplementedError + + def __str__(self): + """Name of the mode, has to be the same as the classname/module""" + raise NotImplementedError diff --git a/pysmartnode/components/devices/climate/heat.py b/pysmartnode/components/devices/climate/heat.py index 988bfb7..261892d 100644 --- a/pysmartnode/components/devices/climate/heat.py +++ b/pysmartnode/components/devices/climate/heat.py @@ -2,10 +2,10 @@ # Copyright Kevin Köck 2019 Released under the MIT license # Created on 2019-10-12 -__updated__ = "2019-10-28" +__updated__ = "2019-10-31" __version__ = "0.4" -from pysmartnode.components.devices.climate import BaseMode +from pysmartnode.components.devices.climate import BaseMode, Climate from .definitions import ACTION_HEATING, ACTION_IDLE, MODE_HEAT, CURRENT_ACTION, \ CURRENT_TEMPERATURE_HIGH, CURRENT_TEMPERATURE_LOW @@ -15,7 +15,7 @@ def __init__(self, climate): super().__init__(climate) self._last_state = False - async def trigger(self, climate, current_temp): + async def trigger(self, climate: Climate, current_temp: float) -> bool: """Triggered whenever the situation is evaluated again""" if current_temp is None: await climate.log.asyncLog("warn", "No temperature", timeout=2, await_connection=False) @@ -54,12 +54,12 @@ async def trigger(self, climate, current_temp): climate.state[CURRENT_ACTION] = ACTION_IDLE self._last_state = False - async def activate(self, climate): + async def activate(self, climate: Climate) -> bool: """Triggered whenever the mode changes and this mode has been activated""" self._last_state = climate.heating_unit.state() return True - async def deactivate(self, climate): + async def deactivate(self, climate: Climate) -> bool: """Triggered whenever the mode changes and this mode has been deactivated""" return True # no deinit needed diff --git a/pysmartnode/components/devices/climate/off.py b/pysmartnode/components/devices/climate/off.py index d48e567..5a7e4b5 100644 --- a/pysmartnode/components/devices/climate/off.py +++ b/pysmartnode/components/devices/climate/off.py @@ -2,17 +2,17 @@ # Copyright Kevin Köck 2019 Released under the MIT license # Created on 2019-10-12 -__updated__ = "2019-10-28" +__updated__ = "2019-10-31" __version__ = "0.2" -from pysmartnode.components.devices.climate import BaseMode +from pysmartnode.components.devices.climate import BaseMode, Climate from .definitions import ACTION_OFF, MODE_OFF, CURRENT_ACTION class off(BaseMode): # def __init__(self, climate): - async def trigger(self, climate, current_temp): + async def trigger(self, climate: Climate, current_temp: float) -> bool: """Triggered whenever the situation is evaluated again""" if climate.heating_unit.state() is False and climate.state[CURRENT_ACTION] == ACTION_OFF: return True @@ -21,11 +21,11 @@ async def trigger(self, climate, current_temp): return True return False - async def activate(self, climate): + async def activate(self, climate: Climate) -> bool: """Triggered whenever the mode changes and this mode has been activated""" return True # no init needed - async def deactivate(self, climate): + async def deactivate(self, climate: Climate) -> bool: """Triggered whenever the mode changes and this mode has been deactivated""" return True # no deinit needed diff --git a/pysmartnode/components/machine/adc.py b/pysmartnode/components/machine/adc.py index b6a87d9..2e114e6 100644 --- a/pysmartnode/components/machine/adc.py +++ b/pysmartnode/components/machine/adc.py @@ -1,8 +1,6 @@ -''' -Created on 2018-07-16 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2018-2019 Released under the MIT license +# Created on 2018-07-16 """ example config: @@ -38,7 +36,7 @@ def __init__(self, *args, **kwargs): else: self.readRaw = self.read # on non-hardware ADCs read() always returns raw values - def readVoltage(self): + def readVoltage(self) -> float: """ Return voltage according to used platform. Atten values are not recognized :return: float @@ -50,20 +48,21 @@ def readVoltage(self): elif platform == "esp32_LoBo": return self.read() / 1000 # loboris fork returns mV else: - raise NotImplementedError("Platform {!s} not implemented, please report".format(platform)) + raise NotImplementedError( + "Platform {!s} not implemented, please report".format(platform)) def __str__(self): return "pyADC generic instance" __repr__ = __str__ - def maxVoltage(self): + def maxVoltage(self) -> float: return 3.3 # esp standard voltage # The following methods are overwritten by machineADC, the machine.ADC class, by the proper hardware methods # In other subclasses they have to be implemented - def read(self): + def read(self) -> int: raise NotImplementedError("Implement your subclass correctly!") def atten(self, *args, **kwargs): @@ -78,7 +77,7 @@ class machineADC(machine.ADC, pyADC): pass -def ADC(pin, atten=None, *args, **kwargs): +def ADC(pin, atten=None, *args, **kwargs) -> pyADC: if type(pin) == str: raise TypeError("ADC pin can't be string") if isinstance(pin, pyADC): @@ -107,5 +106,6 @@ def ADC(pin, atten=None, *args, **kwargs): elif platform == "esp8266": return machineADC(pin, *args, **kwargs) # esp8266 does not require a pin object else: - raise NotImplementedError("Platform {!s} not implemented, please report".format(platform)) + raise NotImplementedError( + "Platform {!s} not implemented, please report".format(platform)) raise TypeError("Unknown type {!s} for ADC object".format(type(pin))) diff --git a/pysmartnode/components/machine/deepsleep.py b/pysmartnode/components/machine/deepsleep.py index ea02ade..fda60f4 100644 --- a/pysmartnode/components/machine/deepsleep.py +++ b/pysmartnode/components/machine/deepsleep.py @@ -1,8 +1,6 @@ -''' -Created on 2018-07-16 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2018-2019 Released under the MIT license +# Created on 2018-07-16 """ example config: diff --git a/pysmartnode/components/machine/easyGPIO.py b/pysmartnode/components/machine/easyGPIO.py index 5cb1cbe..63592ca 100644 --- a/pysmartnode/components/machine/easyGPIO.py +++ b/pysmartnode/components/machine/easyGPIO.py @@ -1,8 +1,6 @@ -''' -Created on 30.10.2017 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-10-30 """ example config: @@ -10,15 +8,15 @@ package: .machine.easyGPIO component: GPIO constructor_args: { - #mqtt_topic: null #optional, topic needs to have /GPIO/# at the end; to change a value publish to /GPIO//set - #discover_pins: [1,2,3] # optional, discover all pins of the list. Otherwise no pins are discovered. + # mqtt_topic: null #optional, topic needs to have /GPIO/# at the end; to change a value publish to /GPIO//set + # discover_pins: [1,2,3] # optional, discover all pins of the list. Otherwise no pins are discovered. } } makes esp8266 listen to requested gpio changes or return pin.value() if message is published without payload """ -__updated__ = "2019-10-20" -__version__ = "1.5" +__updated__ = "2019-11-01" +__version__ = "1.7" import gc import machine @@ -26,7 +24,6 @@ from pysmartnode import config from pysmartnode import logging from pysmartnode.utils.component import Component, DISCOVERY_SWITCH -import uasyncio as asyncio _mqtt = config.getMQTT() @@ -37,18 +34,22 @@ class GPIO(Component): - def __init__(self, topic=None, discover_pins=None): + def __init__(self, mqtt_topic=None, discover_pins=None): super().__init__(COMPONENT_NAME, __version__) - self._topic = topic or _mqtt.getDeviceTopic("easyGPIO/+/set") + self._topic = mqtt_topic or _mqtt.getDeviceTopic("easyGPIO/+/set") _mqtt.subscribeSync(self._topic, self.on_message, self, check_retained_state=True) self._d = discover_pins or [] - async def _discovery(self): + async def _discovery(self, register=True): for pin in self._d: name = "{!s}_{!s}".format(COMPONENT_NAME, pin) - await self._publishDiscovery(_COMPONENT_TYPE, "{}{}".format(self._topic[:-5], pin), - name, DISCOVERY_SWITCH) + if register: + await self._publishDiscovery(_COMPONENT_TYPE, "{}{}".format(self._topic[:-5], pin), + name, DISCOVERY_SWITCH) + else: + await self._deleteDiscovery(_COMPONENT_TYPE, name) + @staticmethod async def on_message(self, topic, msg, retain): _log = logging.getLogger("easyGPIO") if topic.endswith("/set") is False: diff --git a/pysmartnode/components/machine/i2c.py b/pysmartnode/components/machine/i2c.py index e3648c2..f75a74d 100644 --- a/pysmartnode/components/machine/i2c.py +++ b/pysmartnode/components/machine/i2c.py @@ -1,8 +1,6 @@ -''' -Created on 28.10.2017 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-10-28 COMPONENT_NAME = "I2C" diff --git a/pysmartnode/components/machine/pin.py b/pysmartnode/components/machine/pin.py index 632a1b5..cf7e222 100644 --- a/pysmartnode/components/machine/pin.py +++ b/pysmartnode/components/machine/pin.py @@ -1,8 +1,6 @@ -''' -Created on 2018-08-17 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2018-2019 Released under the MIT license +# Created on 2018-08-17 """ Unified Pin creation utility. Accepts pin number, name (for esp8266 NodeMCU) and Pin-Object. @@ -37,11 +35,13 @@ def Pin(pin, *args, **kwargs): # generate dictionary on request so no RAM gets reserved all the time pins = {"D%i" % p: v for p, v in enumerate((16, 5, 4, 0, 2, 14, 12, 13, 15, 3, 1))} else: - raise TypeError("Platform {!s} does not support string pin names like {!s}".format(platform, pin)) + raise TypeError( + "Platform {!s} does not support string pin names like {!s}".format(platform, pin)) if pin in pins: pin = pins[pin] else: - raise TypeError("Pin type {!s}, name {!r} not found in dictionary".format(type(pin), pin)) + raise TypeError( + "Pin type {!s}, name {!r} not found in dictionary".format(type(pin), pin)) gc.collect() elif type(pin) != int: # assuming pin object diff --git a/pysmartnode/components/machine/stats.py b/pysmartnode/components/machine/stats.py index 972af18..ca060e0 100644 --- a/pysmartnode/components/machine/stats.py +++ b/pysmartnode/components/machine/stats.py @@ -5,8 +5,8 @@ # This component will be started automatically to provide basic device statistics. # You don't need to configure it to be active. -__updated__ = "2019-10-30" -__version__ = "1.2" +__updated__ = "2019-11-01" +__version__ = "1.3" import gc @@ -107,7 +107,9 @@ async def _publish(self): val["MQTT Reconnects"] = _mqtt.getReconnects() val["MQTT Dropped messages"] = _mqtt.getDroppedMessages() val["MQTT Subscriptions"] = _mqtt.getLenSubscribtions() - val["MQTT TimedOutOps"] = _mqtt.getTimedOutOperations() + if config.DEBUG: + # only needed for debugging and could be understood wrongly otherwise + val["MQTT TimedOutOps"] = _mqtt.getTimedOutOperations() val["Asyncio waitq"] = "{!s}/{!s}".format(len(asyncio.get_event_loop().waitq), config.LEN_ASYNC_QUEUE) await _mqtt.publish(_mqtt.getDeviceTopic("status"), val, qos=1, retain=False, timeout=5) @@ -125,9 +127,12 @@ async def _loop(self): await self._publish() await asyncio.sleep(self._interval) - async def _discovery(self): + async def _discovery(self, register=True): topic = _mqtt.getRealTopic(_mqtt.getDeviceTopic("status")) - await self._publishDiscovery("sensor", topic, "status", STATE_TYPE, - "Status {!s}".format( - config.DEVICE_NAME or sys_vars.getDeviceID())) + if register: + await self._publishDiscovery("sensor", topic, "status", STATE_TYPE, + "Status {!s}".format( + config.DEVICE_NAME or sys_vars.getDeviceID())) + else: + await self._deleteDiscovery("sensor", "status") gc.collect() diff --git a/pysmartnode/components/machine/watchdog.py b/pysmartnode/components/machine/watchdog.py index b2861d0..218af48 100644 --- a/pysmartnode/components/machine/watchdog.py +++ b/pysmartnode/components/machine/watchdog.py @@ -1,8 +1,6 @@ -''' -Created on 29.05.2018 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2018-2019 Released under the MIT license +# Created on 2018-05-29 """ example config: diff --git a/pysmartnode/components/machine/wifi_led.py b/pysmartnode/components/machine/wifi_led.py index a054407..8937c21 100644 --- a/pysmartnode/components/machine/wifi_led.py +++ b/pysmartnode/components/machine/wifi_led.py @@ -27,7 +27,7 @@ class WIFILED(Component): def __init__(self, pin, active_high=True): - super().__init__(COMPONENT_NAME, __version__) + super().__init__(COMPONENT_NAME, __version__, discover=False) self.pin = Pin(pin, machine.Pin.OUT, value=0 if active_high else 1) self._active_high = active_high self._next = [] diff --git a/pysmartnode/components/multiplexer/amux.py b/pysmartnode/components/multiplexer/amux.py index e37c6c9..de46e9f 100644 --- a/pysmartnode/components/multiplexer/amux.py +++ b/pysmartnode/components/multiplexer/amux.py @@ -1,8 +1,7 @@ -''' -Created on 09.08.2017 +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-08-09 -@author: Kevin Köck -''' """ example config: { @@ -66,7 +65,8 @@ def __init__(self, s0, s1, s2, s3=None, mux=None, adc=None, return_voltages=Fals else: self.__size = 8 self._return_voltages = return_voltages - self._adc = _ADC(adc) # no matter what adc is, _ADC will return an object with the unified ADC API + self._adc = _ADC( + adc) # no matter what adc is, _ADC will return an object with the unified ADC API def setReturnVoltages(self, vol): self._return_voltages = vol diff --git a/pysmartnode/components/multiplexer/mux.py b/pysmartnode/components/multiplexer/mux.py index 63633df..3db7e69 100644 --- a/pysmartnode/components/multiplexer/mux.py +++ b/pysmartnode/components/multiplexer/mux.py @@ -1,8 +1,6 @@ -''' -Created on 09.08.2017 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-08-09 """ example config: diff --git a/pysmartnode/components/multiplexer/pmux.py b/pysmartnode/components/multiplexer/pmux.py index 4edfd65..c654d85 100644 --- a/pysmartnode/components/multiplexer/pmux.py +++ b/pysmartnode/components/multiplexer/pmux.py @@ -1,8 +1,7 @@ -''' -Created on 09.08.2017 +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-08-09 -@author: Kevin Köck -''' """ example config: { diff --git a/pysmartnode/components/sensors/battery.py b/pysmartnode/components/sensors/battery.py index 09371a0..bc369d4 100644 --- a/pysmartnode/components/sensors/battery.py +++ b/pysmartnode/components/sensors/battery.py @@ -54,11 +54,11 @@ class Battery(ComponentSensor): - def __init__(self, adc, voltage_max, voltage_min, multiplier_adc, cutoff_pin=None, - precision_voltage=2, interval_reading=1, interval_publish=None, - mqtt_topic=None, friendly_name=None, - friendly_name_abs=None, - discover=True, expose_intervals=False, intervals_topic=None): + def __init__(self, adc, voltage_max: float, voltage_min: float, multiplier_adc: float, + cutoff_pin=None, precision_voltage: int = 2, interval_reading: float = 1, + interval_publish: float = None, mqtt_topic: str = None, friendly_name: str = None, + friendly_name_abs: str = None, discover: bool = True, + expose_intervals: bool = False, intervals_topic: str = None): super().__init__(COMPONENT_NAME, __version__, discover, interval_publish, interval_reading, mqtt_topic, _log, expose_intervals, intervals_topic) self._adc = ADC(adc) # unified ADC interface @@ -79,11 +79,11 @@ def __init__(self, adc, voltage_max, voltage_min, multiplier_adc, cutoff_pin=Non asyncio.get_event_loop().create_task(self._events()) gc.collect() - def getVoltageMax(self): + def getVoltageMax(self) -> float: """Getter for consumers""" return self._voltage_max - def getVoltageMin(self): + def getVoltageMin(self) -> float: """Getter for consumers""" return self._voltage_min diff --git a/pysmartnode/components/sensors/bell.py b/pysmartnode/components/sensors/bell.py index 9f3faff..2e7fb6c 100644 --- a/pysmartnode/components/sensors/bell.py +++ b/pysmartnode/components/sensors/bell.py @@ -19,8 +19,8 @@ } """ -__updated__ = "2019-10-21" -__version__ = "1.3" +__updated__ = "2019-11-01" +__version__ = "1.4" import gc from pysmartnode import config @@ -28,7 +28,7 @@ from pysmartnode.utils.event import Event from pysmartnode.utils.locksync import Lock from pysmartnode.components.machine.pin import Pin -from pysmartnode.utils.component import Component, DISCOVERY_TIMELAPSE +from pysmartnode.utils.component import Component, DISCOVERY_TIMELAPSE, VALUE_TEMPLATE import machine import time import uasyncio as asyncio @@ -112,14 +112,29 @@ def __irqTime(self, t): self._timer_bell.deinit() self._timer_lock.release() - async def _discovery(self): - await self._publishDiscovery("binary_sensor", self.topic(), "bell", '"ic":"mdi:bell",', - self._frn or "Doorbell") + async def _discovery(self, register=True): + if register: + await self._publishDiscovery("binary_sensor", self.getTopic(), "bell", + '"ic":"mdi:bell",', self._frn or "Doorbell") + else: + await self._deleteDiscovery("binary_sensor", "bell") gc.collect() if config.RTC_SYNC_ACTIVE is True: - await self._publishDiscovery("sensor", _mqtt.getDeviceTopic("last_bell"), "last_bell", - DISCOVERY_TIMELAPSE, self._frn_l or "Last Bell") - gc.collect() + if register: + await self._publishDiscovery("sensor", _mqtt.getDeviceTopic("last_bell"), + "last_bell", DISCOVERY_TIMELAPSE, + self._frn_l or "Last Bell") + gc.collect() + else: + await self._deleteDiscovery("sensor", "last_bell") - def topic(self): + def getTopic(self, *args, **kwargs): return self._topic or _mqtt.getDeviceTopic(COMPONENT_NAME) + + @staticmethod + def getTemplate(self, *args, **kwargs): + return VALUE_TEMPLATE + + @staticmethod + def getValue(self, *args, **kwargs): + return None diff --git a/pysmartnode/components/sensors/dht22.py b/pysmartnode/components/sensors/dht22.py index ebd87c4..0b56b63 100644 --- a/pysmartnode/components/sensors/dht22.py +++ b/pysmartnode/components/sensors/dht22.py @@ -23,7 +23,7 @@ } """ -__updated__ = "2019-10-29" +__updated__ = "2019-11-01" __version__ = "1.0" from pysmartnode import config @@ -80,14 +80,14 @@ async def _read(self): await asyncio.sleep(1) self.sensor.measure() except Exception as e: - await _log.asyncLog("error", "DHT22 is not working, {!s}".format(e)) + await _log.asyncLog("error", "DHT22 is not working, {!s}".format(e), timeout=10) return None, None await asyncio.sleep_ms(100) # give other tasks some time as measure() is slow and blocking try: temp = self.sensor.temperature() humid = self.sensor.humidity() except Exception as e: - await _log.asyncLog("error", "Error reading DHT22: {!s}".format(e), timeout=5) + await _log.asyncLog("error", "Error reading DHT22: {!s}".format(e), timeout=10) else: await self._setValue(SENSOR_TEMPERATURE, temp) await self._setValue(SENSOR_HUMIDITY, humid) diff --git a/pysmartnode/components/sensors/ds18.py b/pysmartnode/components/sensors/ds18.py index 2322db6..3c14187 100644 --- a/pysmartnode/components/sensors/ds18.py +++ b/pysmartnode/components/sensors/ds18.py @@ -10,7 +10,7 @@ constructor_args: { pin: 5 # pin number or label (on NodeMCU) # rom: 28FF016664160383" # optional, ROM of the specific DS18 unit, can be string or bytearray (in json bytearray not possible). If not given then the first found ds18 unit will be used, no matter the ROM. Makes it possible to have a generic ds18 unit. - # auto_detect: false # optional, if true and ROM is None then all connected ds18 units will automatically generate a sensor object with the given options. If a sensor is removed, so will its object. Removed sensors won't be removed from Homeassistant! + # auto_detect: false # optional, if true and ROM is None then all connected ds18 units will automatically generate a sensor object with the given options. If a sensor is removed, so will its object. Removed sensors will be removed from Homeassistant too! # interval_publish: 600 # optional, defaults to 600. Set to interval_reading to publish with every reading # interval_reading: 120 # optional, defaults to 120. -1 means do not automatically read sensor and publish # precision_temp: 2 # precision of the temperature value published @@ -61,13 +61,14 @@ class DS18(ComponentSensor): This is not a full component object in terms of mqtt and discovery. This is handled by the controller. It can be used as a temperature component object. """ - _pins = {} + _pins = {} # pin number/name:onewire() _last_conv = {} # onewire:time _lock = config.Lock() - def __init__(self, pin, rom=None, auto_detect=False, interval_publish=None, - interval_reading=None, precision_temp=2, offset_temp=0, mqtt_topic=None, - friendly_name=None, discover=True, expose_intervals=False, intervals_topic=None): + def __init__(self, pin, rom: str = None, auto_detect=False, interval_publish: float = None, + interval_reading: float = None, precision_temp: int = 2, offset_temp: float = 0, + mqtt_topic=None, friendly_name=None, discover=True, expose_intervals=False, + intervals_topic=None): """ Class for a single ds18 unit to provide an interface to a single unit. :param pin: pin number/name/object @@ -101,18 +102,18 @@ def __init__(self, pin, rom=None, auto_detect=False, interval_publish=None, self._expose = expose_intervals super().__init__(COMPONENT_NAME, __version__, discover, interval_publish, interval_reading, mqtt_topic, _log, expose_intervals, intervals_topic) - if rom or not auto_detect: + if rom or not auto_detect: # sensor with rom or generic sensor self._addSensorType(SENSOR_TEMPERATURE, precision_temp, offset_temp, VALUE_TEMPLATE_FLOAT, "°C", friendly_name) self._auto_detect = False self._generic = True if rom is None and not auto_detect else False if type(pin) == ds18x20.DS18X20: - self.sensor = pin + self.sensor: ds18x20.DS18X20 = pin else: self._pins[pin] = ds18x20.DS18X20(onewire.OneWire(Pin(pin))) - self.sensor = self._pins[pin] + self.sensor: ds18x20.DS18X20 = self._pins[pin] self._last_conv[self.sensor] = None - self.rom = rom + self.rom: str = rom global _count self._count = _count _count += 1 @@ -155,7 +156,7 @@ async def _read(self): else: # generic ds18 sensor rom = self.rom2str(roms[0]) if rom != self.rom: # sensor replaced - self.rom = rom + self.rom: str = rom await _log.asyncLog("info", "Found new ds18: {!s}".format(rom), timeout=5) if self.rom is not None: # DS18 sensor unit async with self._lock: diff --git a/pysmartnode/components/sensors/hcsr04.py b/pysmartnode/components/sensors/hcsr04.py index 0848396..f55173c 100644 --- a/pysmartnode/components/sensors/hcsr04.py +++ b/pysmartnode/components/sensors/hcsr04.py @@ -92,7 +92,7 @@ def __init__(self, pin_trigger, pin_echo, timeout=30000, temp_sensor: ComponentS self._ec = Pin(pin_echo, mode=machine.Pin.IN) self._to = timeout self.checkSensorType(temp_sensor, SENSOR_TEMPERATURE) - self._temp = temp_sensor + self._temp: ComponentSensor = temp_sensor global _count self._count = _count _count += 1 diff --git a/pysmartnode/components/sensors/htu21d.py b/pysmartnode/components/sensors/htu21d.py index 4622a3e..c9fe919 100644 --- a/pysmartnode/components/sensors/htu21d.py +++ b/pysmartnode/components/sensors/htu21d.py @@ -25,8 +25,8 @@ } """ -__updated__ = "2019-10-28" -__version__ = "3.0" +__updated__ = "2019-11-01" +__version__ = "3.1" import gc import uasyncio as asyncio @@ -52,11 +52,12 @@ class HTU21D(ComponentSensor): - def __init__(self, i2c, precision_temp, precision_humid, - temp_offset, humid_offset, - mqtt_topic=None, interval=None, interval_reading=None, + def __init__(self, i2c, precision_temp: int = 2, precision_humid: int = 2, + temp_offset: float = 0, humid_offset: float = 0, + mqtt_topic: str = None, interval_publish: float = None, + interval_reading: float = None, friendly_name_temp=None, friendly_name_humid=None, discover=True): - super().__init__(COMPONENT_NAME, __version__, discover, interval, interval_reading, + super().__init__(COMPONENT_NAME, __version__, discover, interval_publish, interval_reading, mqtt_topic) # discover: boolean, if this component should publish its mqtt discovery. # This can be used to prevent combined Components from exposing underlying diff --git a/pysmartnode/components/switches/buzzer.py b/pysmartnode/components/switches/buzzer.py index 80a16bb..848167c 100644 --- a/pysmartnode/components/switches/buzzer.py +++ b/pysmartnode/components/switches/buzzer.py @@ -1,8 +1,6 @@ -''' -Created on 31.10.2017 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-10-31 """ example config: diff --git a/pysmartnode/components/switches/gpio.py b/pysmartnode/components/switches/gpio.py index e9fa32c..d6b6439 100644 --- a/pysmartnode/components/switches/gpio.py +++ b/pysmartnode/components/switches/gpio.py @@ -1,8 +1,6 @@ -''' -Created on 30.10.2017 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-10-30 """ example config: diff --git a/pysmartnode/components/switches/led.py b/pysmartnode/components/switches/led.py index e1d5543..5e874e6 100644 --- a/pysmartnode/components/switches/led.py +++ b/pysmartnode/components/switches/led.py @@ -1,8 +1,6 @@ -''' -Created on 28.10.2017 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-10-28 """ example config: diff --git a/pysmartnode/components/switches/switch_extension/__init__.py b/pysmartnode/components/switches/switch_extension/__init__.py index cb6b371..7c31767 100644 --- a/pysmartnode/components/switches/switch_extension/__init__.py +++ b/pysmartnode/components/switches/switch_extension/__init__.py @@ -29,8 +29,8 @@ to enable the mode. """ -__updated__ = "2019-10-20" -__version__ = "0.5" +__updated__ = "2019-11-01" +__version__ = "0.6" from pysmartnode import config from pysmartnode import logging @@ -260,13 +260,19 @@ async def toggle(self): def state(self): return self._component.state() - async def _discovery(self): + async def _discovery(self, register=True): count = self._component._count if hasattr(self._component, "_count") else "" for mode in self._modes_enabled: name = "{!s}{!s}_{!s}_{!s}".format(COMPONENT_NAME, count, "mode", mode) - await self._publishDiscovery(_COMPONENT_TYPE, - "{!s}/{!s}".format(self._topic_mode[:-4], mode), name, - DISCOVERY_SWITCH, - "{!s} Mode {!s}".format(self._component._frn, mode)) + if register: + await self._publishDiscovery(_COMPONENT_TYPE, + "{!s}/{!s}".format(self._topic_mode[:-4], mode), name, + DISCOVERY_SWITCH, + "{!s} Mode {!s}".format(self._component._frn, mode)) + else: + await self._deleteDiscovery(_COMPONENT_TYPE, name) name = "{!s}{!s}_{!s}".format(COMPONENT_NAME, count, "mode") - await self._publishDiscovery("sensor", self._topic_mode[:-4], name, "", self._frn_mode) + if register: + await self._publishDiscovery("sensor", self._topic_mode[:-4], name, "", self._frn_mode) + else: + await self._deleteDiscovery("sensor", name) diff --git a/pysmartnode/networking/wifi_esp32_lobo.py b/pysmartnode/networking/wifi_esp32_lobo.py index f4ed477..6256c40 100644 --- a/pysmartnode/networking/wifi_esp32_lobo.py +++ b/pysmartnode/networking/wifi_esp32_lobo.py @@ -1,8 +1,6 @@ -''' -Created on 20.03.2018 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2018-2019 Released under the MIT license +# Created on 2018-03-20 from pysmartnode import config import machine @@ -12,7 +10,8 @@ if hasattr(config, "RTC_SYNC_ACTIVE") and config.RTC_SYNC_ACTIVE: rtc = machine.RTC() print("Synchronize time from NTP server ...") - rtc.ntp_sync(server=config.RTC_SERVER, update_period=36000, tz=config.RTC_TIMEZONE) # update every 10h + rtc.ntp_sync(server=config.RTC_SERVER, update_period=36000, + tz=config.RTC_TIMEZONE) # update every 10h tmo = 100 while not rtc.synced(): time.sleep_ms(100) diff --git a/pysmartnode/utils/component/__init__.py b/pysmartnode/utils/component/__init__.py index e94245b..e101522 100644 --- a/pysmartnode/utils/component/__init__.py +++ b/pysmartnode/utils/component/__init__.py @@ -2,8 +2,8 @@ # Copyright Kevin Köck 2019 Released under the MIT license # Created on 2019-04-26 -__updated__ = "2019-10-18" -__version__ = "1.2" +__updated__ = "2019-11-01" +__version__ = "1.3" from pysmartnode import config import uasyncio as asyncio @@ -63,7 +63,6 @@ async def removeComponent(component): "Can't remove a component that is not an instance of pysmartnode.utils.component.Component") return False # call cleanup method, should stop running loops - await _mqtt.unsubscribe(None, component) await component._remove() global _components c = _components @@ -79,7 +78,14 @@ async def removeComponent(component): c = c._next_component async def _remove(self): - pass + """Cleanup method. Stop all loops and unsubscribe all topics.""" + await _mqtt.unsubscribe(None, self) + await config._log.asyncLog("info", + "Removed component {!s} module {!r} version {!s}".format( + config.getComponentName(self), self.COMPONENT_NAME, + self.VERSION), timeout=5) + if config.MQTT_DISCOVERY_ENABLED and self.__discover: + await self._discovery(False) @staticmethod async def __initNetworkProcess(): @@ -97,14 +103,17 @@ async def _init_network(self): self.COMPONENT_NAME, self.VERSION, config.getComponentName(self)), timeout=5) gc.collect() - if config.MQTT_DISCOVERY_ENABLED is True and self.__discover is True: - await self._discovery() + if config.MQTT_DISCOVERY_ENABLED and self.__discover: + await self._discovery(True) gc.collect() - async def _discovery(self): + async def _discovery(self, register=True): """ Implement in subclass. - Is only called by self._init_network if config.MQTT_DISCOVERY_ON_RECONNECT is True. + Is only called by self._init_network if config.MQTT_DISCOVERY_ON_RECONNECT is True + and by self._remove() when a componen is removed during runtime (e.g. sensor change). + If register is False, send discovery message with empty message "" to remove the component + from Homeassistant. """ pass @@ -118,6 +127,11 @@ async def _publishDiscovery(component_type, component_topic, unique_name, discov del msg, topic gc.collect() + @staticmethod + async def _deleteDiscovery(component_type, unique_name): + topic = Component._getDiscoveryTopic(component_type, unique_name) + await _mqtt.publish(topic, "", qos=1, retain=True) + @staticmethod def _composeAvailability(): return DISCOVERY_AVAILABILITY.format(config.MQTT_HOME, sys_vars.getDeviceID(), diff --git a/pysmartnode/utils/component/sensor.py b/pysmartnode/utils/component/sensor.py index 3f6eaf3..77823f4 100644 --- a/pysmartnode/utils/component/sensor.py +++ b/pysmartnode/utils/component/sensor.py @@ -2,8 +2,8 @@ # Copyright Kevin Köck 2019 Released under the MIT license # Created on 2019-10-27 -__updated__ = "2019-10-30" -__version__ = "0.3" +__updated__ = "2019-11-01" +__version__ = "0.4" from pysmartnode.utils.component import Component from pysmartnode import config @@ -160,7 +160,7 @@ def _default_name(self): """ return "{!s}{!s}".format(self.COMPONENT_NAME, self._count) - async def _discovery(self): + async def _discovery(self, register=True): for sensor_type in self._values: val = self._values[sensor_type] if len(self._values) > 0: @@ -168,10 +168,13 @@ async def _discovery(self): else: name = self._default_name() tp = val[6] or self._composeSensorType(sensor_type, val[3], val[2]) - await self._publishDiscovery("binary_sensor" if val[7] else "sensor", - self.getTopic(sensor_type), name, tp, - val[4] or "{}{}".format(sensor_type[0].upper(), - sensor_type[1:])) + if register: + await self._publishDiscovery("binary_sensor" if val[7] else "sensor", + self.getTopic(sensor_type), name, tp, + val[4] or "{}{}".format(sensor_type[0].upper(), + sensor_type[1:])) + else: + await self._deleteDiscovery("binary_sensor", name) del name, tp gc.collect() diff --git a/pysmartnode/utils/component/switch.py b/pysmartnode/utils/component/switch.py index d03b206..990f4ae 100644 --- a/pysmartnode/utils/component/switch.py +++ b/pysmartnode/utils/component/switch.py @@ -2,8 +2,8 @@ # Copyright Kevin Köck 2019 Released under the MIT license # Created on 2019-09-10 -__updated__ = "2019-10-31" -__version__ = "0.9" +__updated__ = "2019-11-01" +__version__ = "1.0" from pysmartnode.utils.component import Component from .definitions import DISCOVERY_SWITCH @@ -116,9 +116,13 @@ async def toggle(self): def state(self): return self._state - async def _discovery(self): + async def _discovery(self, register=True): name = self._name or "{!s}{!s}".format(self.COMPONENT_NAME, self._count) - await self._publishDiscovery("switch", self._topic[:-4], name, DISCOVERY_SWITCH, self._frn) + if register: + await self._publishDiscovery("switch", self._topic[:-4], name, DISCOVERY_SWITCH, + self._frn) + else: + await self._deleteDiscovery("switch", name) # note that _publishDiscovery does expect the state topic # but we have the command topic stored. diff --git a/pysmartnode/utils/event.py b/pysmartnode/utils/event.py index 9bb7251..d121af8 100644 --- a/pysmartnode/utils/event.py +++ b/pysmartnode/utils/event.py @@ -1,8 +1,6 @@ -''' -Created on 10.08.2017 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2017-2019 Released under the MIT license +# Created on 2017-08-10 __updated__ = "2018-07-14" diff --git a/pysmartnode/utils/sys_vars.py b/pysmartnode/utils/sys_vars.py index 0c69e90..899c1f0 100644 --- a/pysmartnode/utils/sys_vars.py +++ b/pysmartnode/utils/sys_vars.py @@ -1,8 +1,6 @@ -''' -Created on 02.02.2018 - -@author: Kevin Köck -''' +# Author: Kevin Köck +# Copyright Kevin Köck 2018-2019 Released under the MIT license +# Created on 2018-02-02 __updated__ = "2019-10-22"