diff --git a/_templates/button_template.py b/_templates/button_template.py index 21aa26c..d116302 100644 --- a/_templates/button_template.py +++ b/_templates/button_template.py @@ -3,21 +3,22 @@ # Created on 2019-09-10 """ -example config: +example config for remoteConfig module or as json in components.py: { package: component: Button constructor_args: { - # mqtt_topic: null #optional, defaults to //Buzzer/set - # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery + # mqtt_topic: null # optional, defaults to //Button<_count>/set + # 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. } } """ # A button is basically a switch with a single-shot action that deactivates itself afterwards. -__updated__ = "2019-10-20" -__version__ = "0.5" +__updated__ = "2019-10-31" +__version__ = "0.6" from pysmartnode import config from pysmartnode.utils.component.button import ComponentButton @@ -44,13 +45,18 @@ def __init__(self, mqtt_topic=None, friendly_name=None, discover=True): global _count self._count = _count _count += 1 + + ### + # set the initial state otherwise it will be "None" (unknown) and the first request + # will set it accordingly which in case of a button will always be an activation. + initial_state = False # A button will always be False as it is single-shot, + # unless you have a device with a long single-shot action active during reboot. + # You might be able to poll the current state of a device to set the inital state correctly + # mqtt_topic can be adapted otherwise a default mqtt_topic will be generated if None super().__init__(COMPONENT_NAME, __version__, mqtt_topic, instance_name=None, - wait_for_lock=False, discover=discover) - self._frn = friendly_name - self._state = False # A button will always be False as it is single-shot, - # unless you have a device with a long single-shot action. Then you might - # be able to poll its current state. + wait_for_lock=False, discover=discover, friendly_name=friendly_name, + initial_state=initial_state) # If the device needs extra code, launch a new coroutine. diff --git a/_templates/component_template.py b/_templates/component_template.py index f30cf72..d9bb111 100644 --- a/_templates/component_template.py +++ b/_templates/component_template.py @@ -18,8 +18,8 @@ } """ -__updated__ = "2019-10-21" -__version__ = "1.5" +__updated__ = "2019-10-31" +__version__ = "1.6" import uasyncio as asyncio from pysmartnode import config @@ -76,7 +76,12 @@ def __init__(self, my_value, # extend or shrink according to your sensor self.my_value = my_value self._frn = friendly_name # will default to unique name in discovery if None - asyncio.get_event_loop().create_task(self._loop()) + + self._loop_coro = self._loop() + # the component might get removed in which case it should be able to locate and stop + # any running loops it created (otherwise the component will create Exceptions and + # won't be able to be fully removed from RAM) + asyncio.get_event_loop().create_task(self._loop_coro) gc.collect() async def _init_network(self): @@ -100,6 +105,15 @@ async def _loop(self): await asyncio.sleep(5) await _mqtt.publish(self._command_topic[:-4], "ON", qos=1) # publishing to state_topic + async def _remove(self): + """Will be called if the component gets removed""" + # Cancel any loops/asyncio coroutines started by the component + try: + asyncio.cancel(self._loop_coro) + except Exception: + pass + await super()._remove() + async def _discovery(self): name = "{!s}{!s}".format(COMPONENT_NAME, self._count) component_topic = _mqtt.getDeviceTopic(name) diff --git a/_templates/switch_template.py b/_templates/switch_template.py index 7fe3739..389c492 100644 --- a/_templates/switch_template.py +++ b/_templates/switch_template.py @@ -10,14 +10,15 @@ package: component: Switch constructor_args: { - # mqtt_topic: null #optional, defaults to //Buzzer/set + # mqtt_topic: null # optional, defaults to //Switch<_count>/set # 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-20" -__version__ = "1.6" +__updated__ = "2019-10-31" +__version__ = "1.7" from pysmartnode import config from pysmartnode.utils.component.switch import ComponentSwitch @@ -44,17 +45,20 @@ def __init__(self, mqtt_topic=None, friendly_name=None, discover=True): global _count self._count = _count _count += 1 - # mqtt_topic can be adapted otherwise a default mqtt_topic will - # be generated if None is passed - super().__init__(COMPONENT_NAME, __version__, mqtt_topic, instance_name=None, - wait_for_lock=True, discover=discover) - self._frn = friendly_name + ### - # set the initial state otherwise it will be "None" (unknown). - self._state = False - # Might be that you can read your devices state on startup or know the state because + # set the initial state otherwise it will be "None" (unknown) and the first request + # will set it accordingly. + initial_state = None + # should be False/True if can read your devices state on startup or know the state because # you initialize a pin in a certain state. ### + + # mqtt_topic can be adapted otherwise a default mqtt_topic will be generated if None + super().__init__(COMPONENT_NAME, __version__, mqtt_topic, instance_name=None, + wait_for_lock=True, discover=discover, friendly_name=friendly_name, + initial_state=initial_state) + # If the device needs extra code, launch a new coroutine. ##################### diff --git a/pysmartnode/utils/component/button.py b/pysmartnode/utils/component/button.py index 17e46e2..4a8a2a2 100644 --- a/pysmartnode/utils/component/button.py +++ b/pysmartnode/utils/component/button.py @@ -2,8 +2,8 @@ # Copyright Kevin Köck 2019 Released under the MIT license # Created on 2019-09-10 -__updated__ = "2019-10-20" -__version__ = "0.6" +__updated__ = "2019-10-31" +__version__ = "0.7" from .switch import ComponentSwitch from pysmartnode.utils.component import Component @@ -22,7 +22,7 @@ class ComponentButton(ComponentSwitch): """ def __init__(self, component_name, version, command_topic=None, instance_name=None, - wait_for_lock=False, discover=True): + wait_for_lock=False, discover=True, friendly_name=None, initial_state=False): """ :param component_name: name of the component that is subclassing this switch (used for discovery and topics) :param version: version of the component module. will be logged over mqtt @@ -32,9 +32,12 @@ def __init__(self, component_name, version, command_topic=None, instance_name=No meaning the previous device request has to finish before the new one is started. Otherwise the new one will get ignored. With a single-shot action it usually doesn't make sense to wait for the lock. + :param friendly_name: friendly name for homeassistant gui + :param initial_state: the initial state of the button, typically False ("OFF") for Pushbutton """ super().__init__(component_name, version, command_topic, instance_name, wait_for_lock, - discover, restore_state=False) + discover, restore_state=False, friendly_name=friendly_name, + initial_state=initial_state) # discover: boolean, if this component should publish its mqtt discovery. # This can be used to prevent combined Components from exposing underlying # hardware components like a power switch diff --git a/pysmartnode/utils/component/switch.py b/pysmartnode/utils/component/switch.py index d75961f..d03b206 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-30" -__version__ = "0.8" +__updated__ = "2019-10-31" +__version__ = "0.9" from pysmartnode.utils.component import Component from .definitions import DISCOVERY_SWITCH @@ -22,7 +22,8 @@ class ComponentSwitch(Component): """ def __init__(self, component_name, version, command_topic=None, instance_name=None, - wait_for_lock=True, discover=True, restore_state=True): + wait_for_lock=True, discover=True, restore_state=True, friendly_name=None, + initial_state=None): """ :param component_name: name of the component that is subclassing this switch (used for discovery and topics) :param version: version of the component module. will be logged over mqtt @@ -32,13 +33,15 @@ def __init__(self, component_name, version, command_topic=None, instance_name=No :param restore_state: restore the retained state topic state meaning the previous device request has to finish before the new one is started. Otherwise the new one will get ignored. + :param friendly_name: friendly name for homeassistant gui + :param initial_state: intitial state of the switch. By default unknown so first state change request will set initial state. """ super().__init__(component_name, version, discover=discover) # discover: boolean, if this component should publish its mqtt discovery. # This can be used to prevent combined Components from exposing underlying # hardware components like a power switch - self._state = None # initial state is unknown + self._state = initial_state # initial state is unknown if None self._topic = command_topic or _mqtt.getDeviceTopic( "{!s}{!s}/set".format(component_name, self._count)) _mqtt.subscribeSync(self._topic, self.on_message, self, check_retained_state=restore_state) @@ -48,6 +51,7 @@ def __init__(self, component_name, version, command_topic=None, instance_name=No self._name = instance_name self._count = "" # declare in subclass self._event = None + self._frn = friendly_name gc.collect() def getStateChangeEvent(self):