├── .gitmodules ├── COMPONENTS.md ├── LICENSE ├── README.md ├── _deprecated ├── callRegular.py ├── debug.py ├── heater │ ├── JunkersZWR183.py │ ├── __init__.py │ ├── core.py │ ├── hardware │ │ ├── __init__.py │ │ └── pin.py │ ├── modes │ │ ├── __init__.py │ │ └── remoteControl.py │ └── plugins │ │ ├── __init__.py │ │ ├── daynight.py │ │ └── remoteTemperature.py ├── loadComponentsFile.py ├── mqtt_receive_config.py ├── mqtt_timeout.py ├── tempHumidWrapper.py ├── testing │ └── utils │ │ ├── __init__.py │ │ ├── subscribe_file.py │ │ ├── subscription.py │ │ ├── subscriptions_all.py │ │ └── tree.py └── tools │ ├── esp32_lobo │ ├── esp32_build.sh │ ├── esp32_flash.sh │ ├── esp32_initialize.sh │ ├── esp32_monitor.sh │ ├── esp32_renew.sh │ ├── esp32_renew_PSRAM.sh │ ├── esp32_sync.sh │ ├── mncfg_exit.txt │ └── set_port.sh │ └── esp8266_remove_hints.sh ├── _templates ├── button_template.py ├── component_template.py ├── components.py ├── sensor_template.py └── switch_template.py ├── _testing ├── __init__.py ├── sensor.py └── switch.py ├── boot.py ├── changelog.md ├── config_example.py ├── dev ├── __init__.py ├── custom_components │ ├── __init__.py │ └── unix │ │ ├── __init__.py │ │ └── rfpump.py ├── displays │ ├── __init__.py │ └── ssd1306.py ├── gpio_rpi.py ├── moisture.py ├── mqtt_iot.py ├── phSensor.py └── unix │ ├── __init__.py │ ├── popen_base.py │ ├── rf433switch.py │ └── switch.py ├── external_modules ├── micropython_stat.egg-info │ └── PKG-INFO └── stat.py ├── file_flowchart.graphml ├── file_flowchart.jpg ├── main.py ├── pysmartnode ├── __init__.py ├── components │ ├── __init__.py │ ├── devices │ │ ├── __init__.py │ │ ├── arduinoGPIO │ │ │ ├── __init__.py │ │ │ ├── arduino.py │ │ │ └── arduinoControl.py │ │ └── climate │ │ │ ├── __init__.py │ │ │ ├── definitions.py │ │ │ ├── heat.py │ │ │ └── off.py │ ├── machine │ │ ├── __init__.py │ │ ├── adc.py │ │ ├── button.py │ │ ├── deepsleep.py │ │ ├── easyGPIO.py │ │ ├── i2c.py │ │ ├── pin.py │ │ ├── remoteConfig.py │ │ ├── stats.py │ │ ├── watchdog.py │ │ └── wifi_led.py │ ├── multiplexer │ │ ├── __init__.py │ │ ├── amux.py │ │ ├── mux.py │ │ └── pmux.py │ ├── sensors │ │ ├── __init__.py │ │ ├── battery.py │ │ ├── bell │ │ │ ├── __init__.py │ │ │ ├── irq.py │ │ │ └── poll.py │ │ ├── dht22.py │ │ ├── ds18.py │ │ ├── ecMeter.py │ │ ├── hcsr04.py │ │ ├── htu21d.py │ │ ├── pms5003.py │ │ ├── remoteSensor.py │ │ └── waterSensor.py │ └── switches │ │ ├── __init__.py │ │ ├── buzzer.py │ │ ├── generic_switch.py │ │ ├── gpio.py │ │ ├── led.py │ │ ├── remote433mhz.py │ │ ├── remoteSwitch.py │ │ └── switch_extension │ │ ├── __init__.py │ │ ├── repeating.py │ │ └── safety_off.py ├── config.py ├── config_base.py ├── logging │ ├── __init__.py │ ├── logging_full.py │ └── logging_light.py ├── main.py ├── networking │ ├── __init__.py │ ├── mqtt.py │ ├── ntp.py │ ├── wifi_esp32.py │ └── wifi_esp8266.py └── utils │ ├── __init__.py │ ├── abutton.py │ ├── aswitch.py │ ├── component │ ├── __init__.py │ ├── button.py │ ├── definitions.py │ ├── sensor.py │ └── switch.py │ ├── locksync.py │ ├── registerComponents.py │ ├── sys_vars.py │ └── wrappers │ ├── __init__.py │ ├── async_wrapper.py │ ├── callAsyncSafe.py │ └── timeit.py └── tools ├── esp32 ├── esp32_get_repository.sh └── ftp │ ├── esp32_sync_ftp.sh │ ├── generate_bytecode.sh │ └── renew.sh ├── esp8266 ├── esp8266_build.sh ├── esp8266_build_1M.sh ├── esp8266_erase_flash.sh ├── esp8266_flash.sh ├── esp8266_flash_1M.sh ├── esp8266_get_repository.sh ├── esp8266_initialize.sh ├── esp8266_renew.sh ├── esp8266_sync.sh ├── pysmartnode_1M │ ├── esp8266_1m.ld │ ├── manifest.py │ ├── mpconfigboard.h │ └── mpconfigboard.mk └── pysmartnode_4M │ ├── manifest.py │ ├── mpconfigboard.h │ └── mpconfigboard.mk ├── local └── generate_component_definitions.py └── unix └── sync.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libraries"] 2 | path = libraries 3 | url = https://github.com/kevinkk525/micropython-device-libraries 4 | [submodule "external_modules/micropython_mqtt_as"] 5 | path = external_modules/micropython_mqtt_as 6 | url = https://github.com/kevinkk525/micropython-mqtt 7 | [submodule "pysmartnode/libraries"] 8 | path = pysmartnode/libraries 9 | url = https://github.com/kevinkk525/micropython-device-libraries 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Peter Hinch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_deprecated/callRegular.py: -------------------------------------------------------------------------------- 1 | import uasyncio as asyncio 2 | from pysmartnode import config 3 | from pysmartnode.utils.wrappers.callAsyncSafe import callAsyncSafe as _callAsyncSafe 4 | 5 | 6 | async def callRegular(func, interval=None): 7 | interval = interval or config.INTERVAL_SEND_SENSOR 8 | while True: 9 | if type(func) == type(_callAsyncSafe): 10 | await func() 11 | else: 12 | func() 13 | await asyncio.sleep(interval) 14 | 15 | 16 | async def callRegularPublish(func, topic, interval=None, retain=None, qos=None): 17 | interval = interval or config.INTERVAL_SEND_SENSOR 18 | mqtt = config.getMQTT() 19 | while True: 20 | if type(func) == type(_callAsyncSafe): 21 | res = await func() 22 | else: 23 | res = func() 24 | await mqtt.publish(topic, res, qos, retain) 25 | await asyncio.sleep(interval) 26 | -------------------------------------------------------------------------------- /_deprecated/debug.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 14.04.2018 3 | 4 | @author: Kevin 5 | ''' 6 | 7 | __updated__ = "2018-05-22" 8 | __version__ = "1.2" 9 | 10 | import gc 11 | from pysmartnode import config 12 | from pysmartnode import logging 13 | 14 | _log = logging.getLogger("machine.debug") 15 | import uasyncio as asyncio 16 | import time 17 | 18 | gc.collect() 19 | 20 | 21 | def overwatch(coro_name, threshold, asyncr=False): 22 | def func_wrapper(coro): 23 | if asyncr is True: 24 | raise TypeError("overwatch does not support coroutines") 25 | # as it makes not sense. a freeze would trigger every coroutine 26 | else: 27 | def wrapper(*args, **kwargs): 28 | startt = time.ticks_ms() 29 | res = coro(*args, **kwargs) 30 | if str(type(res)) == "": 31 | _log.error("Coroutine in sync overwatch") 32 | endt = time.ticks_ms() 33 | diff = time.ticks_diff(endt, startt) 34 | if diff > threshold: 35 | _log.error("Coro {!s} took {!s}ms, threshold {!s}ms".format(coro_name, diff, threshold)) 36 | return res 37 | 38 | return wrapper 39 | 40 | return func_wrapper 41 | 42 | 43 | def stability_log(interval=600): 44 | asyncio.get_event_loop().create_task(_stability_log(interval)) 45 | asyncio.get_event_loop().create_task(_interrupt(600)) 46 | 47 | 48 | async def _interrupt(interval): # interval in sec 49 | import machine 50 | timer = machine.Timer(1) 51 | global interrupt_array 52 | interrupt_array = [time.ticks_ms(), time.ticks_ms()] 53 | timer.init(period=interval * 1000, mode=machine.Timer.PERIODIC, callback=__interrupt) 54 | _log.debug("Interrupt initialized") 55 | while True: 56 | await asyncio.sleep(interval) 57 | if time.ticks_diff(interrupt_array[1], interrupt_array[0]) > interval * 1.1 * 1000: 58 | _log.warn( 59 | "Interrupt has difference of {!s}".format(time.ticks_diff(interrupt_array[1], interrupt_array[0]))) 60 | 61 | 62 | def __interrupt(t): 63 | global interrupt_array 64 | interrupt_array[0] = interrupt_array[1] 65 | interrupt_array[1] = time.ticks_ms() 66 | print(interrupt_array[1], "inside interrupt") 67 | 68 | 69 | async def _stability_log(interval): 70 | st = time.ticks_ms() 71 | while True: 72 | await asyncio.sleep(interval) 73 | st_new = time.ticks_ms() 74 | diff = time.ticks_diff(st_new, st) / 1000 75 | st = st_new 76 | _log.debug("Still online, diff to last log: {!s}".format(diff)) 77 | if diff > 600 * 1.1 or diff < 600 * 0.9: 78 | _log.warn("Diff to last log not within 10%: {!s}, expected {!s}".format(diff, interval)) 79 | gc.collect() 80 | 81 | 82 | def start_get_stuck(interval=5000): 83 | asyncio.get_event_loop().create_task(_get_stuck(interval)) 84 | 85 | 86 | async def _get_stuck(interval): 87 | # await asyncio.sleep_ms(10) 88 | print("Starting stuck") 89 | t = time.ticks_ms() 90 | while time.ticks_ms() - t < interval: 91 | pass 92 | print("Stop stuck") 93 | # await asyncio.sleep_ms(10) 94 | # print("end_stuck") 95 | -------------------------------------------------------------------------------- /_deprecated/heater/JunkersZWR183.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 2018-08-13 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | """ 8 | example config: 9 | { 10 | package: .devices.heater.JunkersZWR183 11 | component: Heater 12 | constructor_args: { 13 | PIN: D5 # pin that controls the heater 14 | TEMP_SENSOR: htu # name of a temperature sensor in COMPONENTS, needs to provide an async temperature() or tempHumid() coroutine 15 | REACTION_TIME: 900 # how often heater reacts to temperature changes 16 | HYSTERESIS_LOW: 0.25 # the theater will start heating below target temperature minus hysteresis 17 | HYSTERESIS_HIGH: 0.25 # the theater will stop heating above target temperature plus hysteresis 18 | SHUTDOWN_CYCLES: 2 # amount of cycles (in reaction time) after which the heater will shut down if target+hysteris_high reached 19 | START_CYCLES: 2 # amount of cycles (in reaction time) after which the heater will start heating if temp<(target-hysteresis_low); prevents short spikes from starting up the heater (opening a window) 20 | # FROST_TEMP: 16 # optional, defaults to 16C, will try to keep temperature above this temperature, no matter what mode/settings are used 21 | # SHUTDOWN_TEMP: 29 # optional, defaults to 29C, shuts heater down if that temperature is reached no matter what mode/settings are used 22 | # TARGET_TEMP: 22 # optional, defaults to 22C, target temperature for startup only, data published to TARGET_TEMP_TOPIC will be used afterwards 23 | # STATUS_TOPIC: None # optional, defaults to //heater/status, publishes current state of heater (running, error, ...) 24 | # POWER_TOPIC: None # optional, defaults to //heater/power, for requesting and publishing the current power level of the heater (if supported) 25 | # TARGET_TEMP_TOPIC: None # optional, defaults to //heater/temp, for changing the target temperature 26 | # MODE_TOPIC: None # optional, defaults to //heater/mode, for setting heater to internal mode, fully remotely controlled, etc 27 | } 28 | } 29 | inherits everything from Core and just adds the correct hardware (pin on/off) and remoteControl mode 30 | """ 31 | 32 | __updated__ = "2019-05-19" 33 | __version__ = "0.5" 34 | 35 | from .core import Heater as Core, log 36 | from .plugins.daynight import Daynight 37 | from .hardware.pin import pin 38 | 39 | 40 | async def Heater(PIN, **kwargs): 41 | heater = Core(**kwargs) 42 | await pin(heater, PIN, INVERTED=True) 43 | Daynight(HEATER=heater) 44 | await log.asyncLog("info", "JunkersZWR18-3 created, version {!s}".format(__version__)) 45 | return heater 46 | -------------------------------------------------------------------------------- /_deprecated/heater/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_deprecated/heater/__init__.py -------------------------------------------------------------------------------- /_deprecated/heater/hardware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_deprecated/heater/hardware/__init__.py -------------------------------------------------------------------------------- /_deprecated/heater/hardware/pin.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 2018-08-13 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | """ 8 | example config: 9 | { 10 | package: .devices.heater.hardware.pin 11 | component: pin 12 | constructor_args: { 13 | HEATER: heaterObject # name of heater object registered before this component 14 | PIN: D5 # pin that controls the heater 15 | } 16 | } 17 | """ 18 | 19 | __updated__ = "2018-10-05" 20 | __version__ = "0.4" 21 | 22 | from ..core import log 23 | from pysmartnode.components.machine.pin import Pin 24 | import machine 25 | 26 | _pin = None 27 | _inverted = False 28 | 29 | 30 | # not converting to a Component subclass as it doesn't use any mqtt. 31 | 32 | async def pin(HEATER, PIN, INVERTED=False): 33 | global _pin 34 | global _inverted 35 | _inverted = INVERTED 36 | _pin = Pin(PIN, machine.Pin.OUT) 37 | await log.asyncLog("info", "Heater hardware PIN version {!s}".format(__version__)) 38 | HEATER.registerHardware(_setHeaterPower) 39 | await _setHeaterPower(0) # initialize heater as shut down 40 | 41 | 42 | async def _setHeaterPower(power): 43 | """ Adapt this function to fit required hardware""" 44 | log.debug("setting power to {!s}".format(0 if power == 0 else 100), local_only=True) 45 | if power == 0: 46 | _pin.value(_inverted) 47 | else: 48 | _pin.value(not _inverted) 49 | return True 50 | -------------------------------------------------------------------------------- /_deprecated/heater/modes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_deprecated/heater/modes/__init__.py -------------------------------------------------------------------------------- /_deprecated/heater/modes/remoteControl.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 2018-08-13 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | """ 8 | example config: 9 | { 10 | package: .devices.heater.modes.remoteControl 11 | component: RemoteControl 12 | constructor_args: { 13 | # HEATER: heaterObject # optional, name of heater object registered before this component. defaults to the one registered 14 | # ACTIVE: False # optional, if mode should be activated immediately (or just be available if needed later) 15 | } 16 | } 17 | Fully remotely controlled heater mode. Will just set the power received by mqtt. 18 | Be aware that heater will switch to internal mode if temp < FROST or temp >SHUTDOWN_TEMP 19 | 20 | Does not support homeassistant discovery as homeassistant doesn't have a component for sending float values. 21 | """ 22 | 23 | __updated__ = "2019-06-04" 24 | __version__ = "0.4" 25 | 26 | from ..core import log, _mqtt, _heater, Heater 27 | from pysmartnode.utils.component import Component 28 | 29 | 30 | class RemoteControl(Component): 31 | def __init__(self, HEATER: Heater = None, ACTIVE=False): 32 | super().__init__() 33 | if _heater is None and HEATER is None: 34 | raise TypeError("No heater unit registered yet") 35 | self._heater = HEATER or _heater 36 | self._heater.addMode("REMOTE", self._remoteMode) 37 | self._subscribe(self._heater.getPowerTopic() + "/set", self._requestPower) 38 | if ACTIVE is True: 39 | self._heater.setMode("", "REMOTE", False) 40 | 41 | async def _init_network(self): 42 | await super()._init_network() 43 | await log.asyncLog("info", "Heater mode 'remoteControl' version {!s}".format(__version__)) 44 | 45 | async def _requestPower(self, topic, msg, retain): 46 | # if heater mode == "INTERNAL" this will have no effect because main loop does not check for it 47 | try: 48 | power = float(msg) 49 | except ValueError: 50 | log.error("Error converting requested power to float: {!r}".format(msg)) 51 | return None 52 | await self._heater.setTargetPower(power) 53 | log.debug("requestPower {!s}".format(power), local_only=True) 54 | if self._heater.getActiveMode() == "INTERNAL" and retain is False: 55 | # don't log if it's a retained value because of mc reset 56 | await log.asyncLog("debug", "heater in internal mode, requestPower does not work") 57 | else: 58 | if self._heater.hasStarted(): 59 | self._heater.setEvent() 60 | 61 | @staticmethod 62 | async def _remoteMode(heater: Heater, data): 63 | # remoteControl only sets the power received by mqtt, which is set by heater.requestPower() 64 | power = heater.getTargetPower() 65 | if await heater.setHeaterPower(power): 66 | await _mqtt.publish(heater.getPowerTopic()[:heater.getPowerTopic().find("/set")], power, qos=1, retain=True) 67 | if power == 0: 68 | await _mqtt.publish(heater.getStatusTopic(), "OFF", qos=1, retain=True) 69 | else: 70 | await _mqtt.publish(heater.getStatusTopic(), "ON", qos=1, retain=True) 71 | """ 72 | if heater._last_error=="SET_POWER": 73 | heater._last_error=None 74 | """ 75 | # Not needed as _watch will reset the last_error if it is the same 76 | else: 77 | log.error("Could not set heater power to {!s}%, shutting heater down".format(heater.getStatusTopic())) 78 | await heater.setHeaterPower(0) 79 | heater.setLastError("SET_POWER") 80 | await _mqtt.publish(heater.getPowerTopic()[:heater.getPowerTopic().find("/set")], power, qos=1, retain=True) 81 | await _mqtt.publish(heater.getStatusTopic(), "ERR: SET_POWER", qos=1, retain=True) 82 | -------------------------------------------------------------------------------- /_deprecated/heater/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_deprecated/heater/plugins/__init__.py -------------------------------------------------------------------------------- /_deprecated/heater/plugins/remoteTemperature.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 2018-09-25 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | """ 8 | example config: 9 | { 10 | package: .devices.heater.plugins.remoteTemperature 11 | component: RemoteTemperature 12 | constructor_args: { 13 | # HEATER: heaterObject # optional, name of heater object registered before this component 14 | TOPIC: sometopic # topic of the remote temperature, supports float or dict with "temperature":float 15 | } 16 | } 17 | Adds support for receiving temperature over mqtt and use it to control the heater power. 18 | This plugin works completely independent of other modules and modes and will overwrite the "current_temp" attribute 19 | 20 | Does not support homeassistant discovery as homeassistant doesn't have a component for sending a string. 21 | """ 22 | 23 | __updated__ = "2019-06-04" 24 | __version__ = "0.8" 25 | 26 | from ..core import log, _heater, Heater 27 | import time 28 | from pysmartnode.utils.component import Component 29 | 30 | 31 | class RemoteTemperature(Component): 32 | def __init__(self, TOPIC, HEATER: Heater = None): 33 | super().__init__() 34 | if HEATER is None and _heater is None: 35 | raise TypeError("No heater unit registered yet") 36 | self._heater = HEATER or _heater 37 | self._heater.registerPlugin(self._remoteTempControl, "remoteTemperature") 38 | self._subscribe(TOPIC, self._remoteTemp) 39 | log.info("Heater plugin 'remoteTemperature' version {!s}".format(__version__)) 40 | self.__time = None 41 | self.__temp = None 42 | 43 | async def _remoteTemp(self, topic, msg, retain): 44 | if retain: 45 | # a retained temperature value is of no use 46 | return 47 | if type(msg) == dict: 48 | if "temperature" in msg: 49 | msg = msg["temperature"] 50 | else: 51 | log.error("Dictionary has unsupported values") 52 | return 53 | try: 54 | msg = float(msg) 55 | except Exception as e: 56 | log.error("Can't convert remote temperature to float: {!s}".format(e)) 57 | return 58 | self.__time = time.ticks_ms() 59 | self.__temp = msg 60 | log.debug("Got remote temp {!s}°C".format(msg), local_only=True) 61 | 62 | async def _remoteTempControl(self, heater, data): 63 | if self.__temp is not None: 64 | if time.ticks_ms() - self.__time < heater.getInterval() * 1000: 65 | data["current_temp"] = self.__temp 66 | # overwriting current_temp, if no remote temp received, internal temp is used 67 | log.debug("Using remote temperature {!s}°C".format(self.__temp), local_only=True) 68 | data["remote_temp"] = self.__temp 69 | data["remote_temp_time"] = self.__time 70 | # just in case a future plugin would like to use this 71 | -------------------------------------------------------------------------------- /_deprecated/loadComponentsFile.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 2018-09-25 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | __version__ = "0.2" 8 | __updated__ = "2018-10-17" 9 | 10 | from pysmartnode.utils import sys_vars 11 | import ujson 12 | from sys import platform 13 | import gc 14 | import uasyncio as asyncio 15 | 16 | 17 | def _importComponents(_log): 18 | try: 19 | import components 20 | except ImportError: 21 | _log.critical("components.py does not exist") 22 | return False 23 | except Exception as e: 24 | _log.critical("components.py: {!s}".format(e)) 25 | return False 26 | if hasattr(components, "COMPONENTS"): 27 | _log.info("Trying to register COMPONENTS of components.py", local_only=True) 28 | return components.COMPONENTS 29 | else: 30 | _log.info("No COMPONENTS in components.py, maybe user code executed", local_only=True) 31 | return True 32 | 33 | 34 | async def loadComponentsFile(_log, registerComponent): 35 | if not sys_vars.hasFilesystem(): 36 | comps = _importComponents(_log) 37 | if comps is False: 38 | _log.critical("Can't load components file as filesystem is unavailable") 39 | return False 40 | return comps 41 | try: 42 | f = open("components.json", "r") 43 | components_found = True 44 | except OSError: 45 | components_found = False 46 | if components_found is False: 47 | try: 48 | f = open("_order.json", "r") 49 | except Exception as e: 50 | # if loading configuration jsons fails, try to import components.py 51 | comps = _importComponents(_log) 52 | if comps is False: 53 | _log.critical("_order.json does not exist, {!s}".format(e)) 54 | return False 55 | else: 56 | return comps 57 | order = ujson.loads(f.read()) 58 | f.close() 59 | gc.collect() 60 | for component in order: 61 | try: 62 | with open("components/{!s}.json".format(component), "r") as f: 63 | componentdata = ujson.load(f) 64 | registerComponent(component, componentdata) 65 | except Exception as e: 66 | _log.error("Error loading component file {!s}, {!s}".format(component, e)) 67 | gc.collect() 68 | if platform == "esp8266": 69 | await asyncio.sleep(1) 70 | # gives time to get retained topic and settle ram, important on esp8266 71 | else: 72 | await asyncio.sleep_ms(100) 73 | gc.collect() 74 | return True 75 | else: 76 | c = f.read() 77 | f.close() 78 | try: 79 | c = ujson.loads(c) 80 | gc.collect() 81 | return c 82 | except Exception as e: 83 | _log.critical("components.json parsing error {!s}".format(e)) 84 | return False 85 | -------------------------------------------------------------------------------- /_deprecated/mqtt_timeout.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2020 Released under the MIT license 3 | # Created on 2020-03-31 4 | 5 | __updated__ = "2020-04-01" 6 | __version__ = "0.2" 7 | 8 | from micropython_mqtt_as.mqtt_as import MQTTClient as _MQTTClient 9 | import uasyncio as asyncio 10 | import time 11 | 12 | 13 | # doesn't work with new version of uasyncio 14 | 15 | class MQTTClient(_MQTTClient): 16 | _ops_tasks = [None, None] # publish and (un)sub operations, can be concurrent 17 | 18 | async def _operationTimeout(self, coro, *args, i): 19 | try: 20 | await coro(*args) 21 | except asyncio.CancelledError: 22 | raise # in case the calling function need to handle Cancellation too 23 | finally: 24 | self._ops_tasks[i] = None 25 | 26 | async def _preprocessor(self, coroutine, *args, timeout=None, await_connection=True): 27 | task = None 28 | start = time.ticks_ms() 29 | i = 0 if len(args) == 4 else 1 # 0: publish, 1:(un)sub 30 | try: 31 | while timeout is None or time.ticks_diff(time.ticks_ms(), start) < timeout * 1000: 32 | if not await_connection and not self._isconnected: 33 | return False 34 | if self._ops_tasks[i] is task is None: 35 | task = asyncio.create_task(self._operationTimeout(coroutine, *args, i=i)) 36 | self._ops_tasks[i] = task 37 | elif task: 38 | if self._ops_tasks[i] is not task: 39 | return True # published 40 | await asyncio.sleep_ms(20) 41 | self.dprint("timeout on", "(un)sub" if i else "publish", args) 42 | raise asyncio.TimeoutError 43 | except asyncio.CancelledError: 44 | raise # the caller should be cancelled too 45 | finally: 46 | if task and self._ops_tasks[i] is task: 47 | async with self.lock: 48 | task.cancel() 49 | 50 | async def publish(self, topic, msg, retain=False, qos=0, timeout=None, await_connection=True): 51 | if not await_connection and not self._isconnected: 52 | return False 53 | return await self._preprocessor(super().publish, topic, msg, retain, qos, 54 | timeout=timeout, await_connection=await_connection) 55 | 56 | async def subscribe(self, topic, qos=0, timeout=None, await_connection=True): 57 | if not await_connection and not self._isconnected: 58 | return False 59 | return await self._preprocessor(super().unsubscribe, topic, timeout=timeout, 60 | await_connection=False) 61 | 62 | async def unsubscribe(self, topic, timeout=None, await_connection=True): 63 | if not await_connection and not self._isconnected: 64 | return False 65 | return await self._preprocessor(super().unsubscribe, topic, timeout=timeout, 66 | await_connection=await_connection) 67 | -------------------------------------------------------------------------------- /_deprecated/tempHumidWrapper.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_deprecated/tempHumidWrapper.py -------------------------------------------------------------------------------- /_deprecated/testing/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_deprecated/testing/utils/__init__.py -------------------------------------------------------------------------------- /_deprecated/testing/utils/subscribe_file.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 11.03.2018 3 | 4 | @author: Kevin K�ck 5 | ''' 6 | 7 | __version__ = "0.2" 8 | __updated__ = "2018-03-11" 9 | 10 | import gc 11 | from pysmartnode.utils.wrappers.timeit import timeit 12 | 13 | memory = gc.mem_free() 14 | gc.collect() 15 | 16 | 17 | def printMemory(info=""): 18 | global memory 19 | memory_new = gc.mem_free() 20 | print("[RAM] [{!s}] {!s}".format(info, memory_new - memory)) 21 | memory = memory_new 22 | 23 | 24 | def creating(): 25 | gc.collect() 26 | printMemory("Start") 27 | from _deprecated.subscriptionHandlers import SubscriptionHandler 28 | gc.collect() 29 | printMemory("After import") 30 | global handler 31 | handler = SubscriptionHandler() 32 | gc.collect() 33 | printMemory("After handler creation") 34 | 35 | 36 | @timeit 37 | def addObjects(): 38 | for j in range(0, 3): 39 | for i in range(0, 10): 40 | handler.addObject("home/235j094s4eg/device{!s}/htu{!s}".format(j, i), "func{!s}".format(i)) 41 | 42 | 43 | @timeit 44 | def getObject(): 45 | return handler.getFunctions("home/235j094s4eg/device2/htu9") 46 | 47 | 48 | @timeit 49 | def removeObject(handler): 50 | handler.removeObject("home/test2/htu") 51 | 52 | 53 | def speedtest(): 54 | creating() 55 | gc.collect() 56 | printMemory("after creation with no Objects") 57 | addObjects() 58 | gc.collect() 59 | printMemory("30 Objects") 60 | print(getObject()) 61 | gc.collect() 62 | printMemory("Subscription test done") 63 | 64 | 65 | speedtest() 66 | print("Functional test") 67 | 68 | 69 | def test(): 70 | from _deprecated.subscriptionHandlers import SubscriptionHandler 71 | handler = SubscriptionHandler() 72 | 73 | @timeit 74 | def create(): 75 | handler.addObject("home/test/htu", "func1") 76 | handler.addObject("home/test2/htu", "func2") 77 | handler.addObject("home/test3/htu2", "func3") 78 | create() 79 | 80 | @timeit 81 | def get(): 82 | print(handler.getFunctions("home/test/htu")) 83 | print(handler.getFunctions("home/test2/htu")) 84 | print(handler.getFunctions("home/test3/htu2")) 85 | get() 86 | handler.setFunctions("home/test3/htu2", "func_test") 87 | print(handler.getFunctions("home/test3/htu2")) 88 | try: 89 | print(handler.getFunctions("home/test5/htu2")) 90 | except Exception as e: 91 | print(e) 92 | removeObject(handler) 93 | try: 94 | print(handler.getFunctions("home/test2/htu")) 95 | except Exception as e: 96 | print(e) 97 | print(handler.getFunctions("home/test3/htu2")) 98 | handler.addObject("home/1325/ds18", "funcDS") 99 | handler.addObject("home/1325/#", "funcWildcard") 100 | print("ds19 (wildcard should trigger)", handler.getFunctions("home/1325/ds19")) 101 | print("ds19 (wildcard should trigger)", handler.getFunctions("home/1325/ds19/what")) 102 | print("Multiple subscriptions test") 103 | handler.addObject("home/1325/ds18", "funcDS2") 104 | print("ds18", handler.getFunctions("home/1325/ds18")) 105 | return handler 106 | 107 | 108 | test() 109 | 110 | print("Test finished") 111 | 112 | 113 | """ 114 | >>> from _testing.utils import subscribe_file 115 | [RAM] [Start] -224 116 | [RAM] [After import] -368 117 | [RAM] [After handler creation] -112 118 | [RAM] [after creation with no Objects] 0 119 | [Time][function] 9279007 120 | [RAM] [30 Objects] -784 121 | [Time][function] 392449 122 | func9 123 | [RAM] [Subscription test done] 0 124 | Functional test 125 | [Time][function] 386079 126 | func1 127 | func2 128 | func3 129 | [Time][function] 84170 130 | func_test 131 | Object home/test5/htu2 does not exist 132 | [Time][function] 417159 133 | Object home/test2/htu does not exist 134 | func_test 135 | ds19 (wildcard should trigger) funcWildcard 136 | ds19 (wildcard should trigger) funcWildcard 137 | Multiple subscriptions test 138 | ds18 ('funcDS', 'funcDS2') 139 | Test finished 140 | """ 141 | -------------------------------------------------------------------------------- /_deprecated/testing/utils/subscription.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 18.02.2018 3 | 4 | @author: Kevin K�ck 5 | ''' 6 | 7 | __version__ = "0.2" 8 | __updated__ = "2018-03-09" 9 | 10 | import gc 11 | from pysmartnode.utils.wrappers.timeit import timeit 12 | 13 | memory = gc.mem_free() 14 | gc.collect() 15 | 16 | 17 | def printMemory(info=""): 18 | global memory 19 | memory_new = gc.mem_free() 20 | print("[RAM] [{!s}] {!s}".format(info, memory_new - memory)) 21 | memory = memory_new 22 | 23 | 24 | def creating(): 25 | gc.collect() 26 | printMemory("Start") 27 | from _deprecated.subscriptionHandlers import SubscriptionHandler 28 | gc.collect() 29 | printMemory("After import") 30 | global handler 31 | handler = SubscriptionHandler() 32 | gc.collect() 33 | printMemory("After handler creation") 34 | 35 | 36 | @timeit 37 | def addObjects(): 38 | for j in range(0, 3): 39 | for i in range(0, 10): 40 | handler.addObject("home/235j094s4eg/device{!s}/htu{!s}".format(j, i), "func{!s}".format(i)) 41 | 42 | 43 | @timeit 44 | def getObject(): 45 | return handler.getFunctions("home/235j094s4eg/device2/htu9") 46 | 47 | 48 | @timeit 49 | def getObjectDirectly(): 50 | return handler.get("home/235j094s4eg/device2/htu9", 1) 51 | 52 | 53 | @timeit 54 | def addObjectsList(): 55 | for j in range(0, 3): 56 | for i in range(0, 10): 57 | a.append(("home/235j094s4eg/device{!s}/htu{!s}".format(j, i), "func{!s}".format(i))) 58 | 59 | 60 | @timeit 61 | def getObjectList(): 62 | for i in a: 63 | if i[0] == "home/235j094s4eg/device3/htu9": 64 | return i[1] 65 | 66 | 67 | def speedtest(): 68 | creating() 69 | gc.collect() 70 | printMemory("after creation with no Objects") 71 | addObjects() 72 | gc.collect() 73 | printMemory("30 Objects") 74 | print(getObject()) 75 | gc.collect() 76 | print(getObjectDirectly()) 77 | gc.collect() 78 | printMemory("Subscription test done") 79 | 80 | print("Comparison to list") 81 | global a 82 | a = [] 83 | 84 | gc.collect() 85 | printMemory("List created") 86 | addObjectsList() 87 | gc.collect() 88 | printMemory("Added 30 objects to list") 89 | print(getObjectList()) 90 | gc.collect() 91 | printMemory("List comparison done") 92 | 93 | 94 | speedtest() 95 | print("Functional test") 96 | 97 | 98 | def test(): 99 | from _deprecated.subscriptionHandlers import SubscriptionHandler 100 | handler = SubscriptionHandler() 101 | handler.addObject("home/test/htu", "func1") 102 | handler.addObject("home/test2/htu", "func2") 103 | handler.addObject("home/test3/htu2", "func3") 104 | print(handler.getFunctions("home/test/htu")) 105 | print(handler.getFunctions("home/test2/htu")) 106 | print(handler.getFunctions("home/test3/htu2")) 107 | handler.setFunctions("home/test3/htu2", "func_test") 108 | print(handler.getFunctions("home/test3/htu2")) 109 | try: 110 | print(handler.getFunctions("home/test5/htu2")) 111 | except Exception as e: 112 | print(e) 113 | handler.removeObject("home/test2/htu") 114 | try: 115 | print(handler.getFunctions("home/test2/htu")) 116 | except Exception as e: 117 | print(e) 118 | print(handler.getFunctions("home/test3/htu2")) 119 | handler.addObject("home/1325/ds18", "funcDS") 120 | print("Multiple subscriptions test") 121 | handler.addObject("home/1325/ds18", "funcDS2") 122 | print("ds18", handler.get("home/1325/ds18", 1)) 123 | return handler 124 | 125 | 126 | test() 127 | 128 | print("Test finished") 129 | 130 | """ 131 | >>> from _testing.utils import subscription 132 | [RAM] [Start] -336 133 | [RAM] [After import] -992 134 | [RAM] [After handler creation] -32 135 | [RAM] [after creation with no Objects] 0 136 | [Time] Function addObjects: 612.455ms 137 | [RAM] [30 Objects] -3552 138 | [Time] Function getObject: 5.920ms 139 | func9 140 | [Time] Function getObjectDirectly: 5.813ms 141 | func9 142 | [RAM] [Subscription test done] 0 143 | Comparison to list 144 | [RAM] [List created] -32 145 | [Time] Function addObjectsList: 496.705ms 146 | [RAM] [Added 30 objects to list] -2704 147 | [Time] Function getObjectList: 2.223ms 148 | None 149 | [RAM] [List comparison done] 0 150 | Functional test 151 | func1 152 | func2 153 | func3 154 | func_test 155 | Object home/test5/htu2 does not exist 156 | Object home/test2/htu does not exist 157 | func_test 158 | Multiple subscriptions test 159 | ds18 ['funcDS', 'funcDS2'] 160 | Test finished 161 | """ 162 | -------------------------------------------------------------------------------- /_deprecated/testing/utils/subscriptions_all.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 11.04.2018 3 | 4 | @author: Kevin 5 | ''' 6 | 7 | 8 | def wrapResult(handler, expected, result): 9 | equals = str(expected) == str(result) 10 | print(handler, "Success:", equals, "Expected result:", expected, "Result:", result) 11 | 12 | 13 | from _deprecated.subscriptionHandlers import Tree 14 | from _deprecated.subscriptionHandlers import SubscriptionHandler as SubscriptionHandler_File 15 | from _deprecated.subscriptionHandlers import SubscriptionHandler 16 | 17 | for handler in [Tree, SubscriptionHandler, SubscriptionHandler_File]: 18 | print(handler) 19 | if handler == Tree: 20 | t = Tree("home", ["Functions"]) 21 | elif handler == SubscriptionHandler: 22 | t = SubscriptionHandler() 23 | else: 24 | t = SubscriptionHandler_File() 25 | 26 | topic = "home/login/#" 27 | t.addObject(topic, "sendConfig") 28 | wrapResult(handler, "sendConfig", t.getFunctions("home/login/test")) 29 | wrapResult(handler, "sendConfig", t.getFunctions("home/login")) 30 | 31 | topic = "home/login" 32 | t.addObject(topic, "nothing") 33 | wrapResult(handler, "sendConfig", t.getFunctions("home/login/test")) 34 | wrapResult(handler, "nothing", t.getFunctions("home/login")) 35 | if handler == SubscriptionHandler: 36 | print("This test fails because the wildcard was subscribed first and will be found first") 37 | 38 | print("\nTests done\n") 39 | -------------------------------------------------------------------------------- /_deprecated/testing/utils/tree.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 21.02.2018 3 | 4 | @author: Kevin K�ck 5 | ''' 6 | 7 | __version__ = "0.3" 8 | __updated__ = "2018-02-24" 9 | 10 | import gc 11 | gc.collect() 12 | memory = gc.mem_free() 13 | 14 | 15 | def printMemory(info=""): 16 | global memory 17 | memory_new = gc.mem_free() 18 | print("[RAM] [{!s}] {!s}".format(info, memory_new - memory)) 19 | memory = memory_new 20 | 21 | 22 | def functional_testing(): 23 | print("Functioncal test") 24 | from _deprecated.subscriptionHandlers import Tree 25 | tree = Tree("home", ["Functions"]) 26 | tree.addObject("home/1325/htu21d/set", "func1") 27 | tree.addObject("home/1325/#", "funcWildcard") 28 | print("home:", tree.get("home", 0)) 29 | print("1325:", tree.get("home/1325", 0)) 30 | print("htu21d:", tree.get("home/1325/htu21d", 0)) 31 | print("set:", tree.get("home/1325/htu21d/set", 0)) 32 | 33 | try: 34 | tree.addObject("test/1325/htu21d/set", "func1") 35 | except Exception as e: 36 | print("Exception {!r} is expected".format(e)) 37 | 38 | tree.addObject("home/1325/htu21d/test", "funcTest") 39 | print("home/1325/htu21d/test", tree.get("home/1325/htu21d/test", 0)) 40 | tree.addObject("home/1325/htu21d", "func2") 41 | print("home/1325/htu21d", tree.get("home/1325/htu21d", 0)) 42 | tree.setFunctions("home/1325/htu21d", "func_new") 43 | print("home/1325/htu21d", tree.getFunctions("home/1325/htu21d")) 44 | tree.addObject("home/1325/ds18/set", "funcDSSET") 45 | tree.addObject("home/1325/ds18", "funcDS") 46 | print("ds18/set:", tree.get("home/1325/ds18/set", 0)) 47 | print("ds18", tree.get("home/1325/ds18", 0)) 48 | print("\n") 49 | tree.print() 50 | print("\n") 51 | print("ds18", tree.get("home/1325/ds18", 0)) 52 | print("ds19 (wildcard should trigger)", tree.get("home/1325/ds19", 0)) 53 | print("ds18/bla (wildcard should trigger)", tree.get("home/1325/ds18/bla", 0)) 54 | print("Multiple subscriptions test") 55 | tree.addObject("home/1325/ds18", "funcDS2") 56 | print("ds18", tree.get("home/1325/ds18", 0)) 57 | print("Removing home/1325") 58 | tree.removeObject("home/1325") 59 | try: 60 | print("home/1325", tree.getFunctions("home/1325")) 61 | except Exception as e: 62 | print("Exception {!r} is expected".format(e)) 63 | 64 | 65 | import gc 66 | from pysmartnode.utils.wrappers.timeit import timeit 67 | 68 | memory = gc.mem_free() 69 | gc.collect() 70 | 71 | 72 | def creating(): 73 | gc.collect() 74 | printMemory("Start") 75 | from _deprecated.subscriptionHandlers import Tree 76 | gc.collect() 77 | printMemory("After import") 78 | global handler 79 | handler = Tree("home", ["Function"]) 80 | gc.collect() 81 | printMemory("After handler creation") 82 | 83 | 84 | @timeit 85 | def addObjects(): 86 | for j in range(0, 3): 87 | for i in range(0, 10): 88 | handler.addObject("home/235j094s4eg/device{!s}/htu{!s}".format(j, i), "func{!s}".format(i)) 89 | 90 | 91 | @timeit 92 | def getObject(): 93 | return handler.getFunction("home/235j094s4eg/device2/htu9") 94 | 95 | 96 | @timeit 97 | def getObjectDirectly(): 98 | return handler.get("home/235j094s4eg/device2/htu9", 0) 99 | 100 | 101 | creating() 102 | gc.collect() 103 | printMemory("after creation with no Objects") 104 | addObjects() 105 | gc.collect() 106 | printMemory("30 Objects") 107 | print(getObject()) 108 | gc.collect() 109 | print(getObjectDirectly()) 110 | gc.collect() 111 | printMemory("Subscription test done") 112 | 113 | print("Comparison to list") 114 | a = [] 115 | 116 | 117 | @timeit 118 | def addObjectsList(): 119 | for j in range(0, 3): 120 | for i in range(0, 10): 121 | a.append(("home/235j094s4eg/device{!s}/htu{!s}".format(j, i), "func{!s}".format(i))) 122 | 123 | 124 | @timeit 125 | def getObjectList(): 126 | for i in a: 127 | if i[0] == "home/235j094s4eg/device2/htu9": 128 | return i[1] 129 | 130 | 131 | gc.collect() 132 | printMemory("List created") 133 | addObjectsList() 134 | gc.collect() 135 | printMemory("Added 30 objects to list") 136 | print(getObjectList()) 137 | gc.collect() 138 | printMemory("List comparison done") 139 | 140 | 141 | functional_testing() 142 | 143 | print("Test finished") 144 | 145 | 146 | """ 147 | >>> from _testing.utils import tree 148 | [RAM] [Start] -192 149 | [RAM] [After import] -720 150 | [RAM] [After handler creation] -384 151 | [RAM] [after creation with no Objects] -32 152 | [Time][addObjects] 957941 153 | [RAM] [30 Objects] -5088 154 | [Time][getObject] 2375 155 | func9 156 | [Time][getObjectDirectly] 2265 157 | func9 158 | [RAM] [Subscription test done] -48 159 | Comparison to list 160 | [RAM] [List created] -128 161 | [Time][addObjectsList] 890893 162 | [RAM] [Added 30 objects to list] -2688 163 | [Time][getObjectList] 1050 164 | func9 165 | [RAM] [List comparison done] 0 166 | Functioncal test 167 | home: None 168 | 1325: None 169 | htu21d: None 170 | set: func1 171 | Exception ValueError('Requested object has different root: test',) is expected 172 | home/1325/htu21d/test funcTest 173 | home/1325/htu21d func2 174 | home/1325/htu21d func_new 175 | ds18/set: funcDSSET 176 | ds18 funcDS 177 | 178 | 179 | Printing Tree: 180 | /home 181 | /home/1325 182 | /home/1325/htu21d 183 | /home/1325/htu21d/set 184 | /home/1325/htu21d/test 185 | /home/1325/# 186 | /home/1325/ds18 187 | /home/1325/ds18/set 188 | 189 | 190 | ds18 funcDS 191 | ds19 (wildcard should trigger) funcWildcard 192 | ds18/bla (wildcard should trigger) funcWildcard 193 | Multiple subscriptions test 194 | ds18 ['funcDS', 'funcDS2'] 195 | Removing home/1325 196 | Exception ValueError('Object home/1325 does not exist',) is expected 197 | Test finished 198 | """ 199 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/esp32_build.sh: -------------------------------------------------------------------------------- 1 | CLEAN="$1" 2 | cd ~/MicroPython_ESP32_psRAM_LoBo/MicroPython_BUILD/ 3 | if [ "$CLEAN" = "clean" ] 4 | then 5 | git pull 6 | ./BUILD.sh clean 7 | fi 8 | #./BUILD.sh menuconfig < mncfg_exit.txt 9 | ./BUILD.sh -v -j10 10 | #./BUILD -a 2048 flash 11 | #mpfshell -o ttyUSB0 --reset -n 12 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/esp32_flash.sh: -------------------------------------------------------------------------------- 1 | PORT="$1" 2 | cd ~/MicroPython_ESP32_psRAM_LoBo/MicroPython_BUILD/ 3 | #./BUILD.sh erase -p $PORT 4 | ./BUILD.sh -a 1536 flash -p $PORT 5 | #mpfshell -o ttyUSB0 --reset -n 6 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/esp32_initialize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "/home/kevin/ws_cloud/Programme Python/WNodePython/" 3 | mpfshell -o ttyS4 -n -c "put main.py" 4 | mpfshell -o ttyS4 -n -c "put config.py" 5 | mpfshell -o ttyS4 -n -c "put boot.py" -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/esp32_monitor.sh: -------------------------------------------------------------------------------- 1 | cd ~/MicroPython_ESP32_psRAM_LoBo/MicroPython_BUILD/ 2 | ./BUILD.sh monitor 3 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/esp32_renew.sh: -------------------------------------------------------------------------------- 1 | CLEAN="$1" 2 | cd esp32_lobo 3 | esp32_sync.sh 4 | esp32_build.sh #CLEAN 5 | esp32_flash.sh /dev/ttyS11 6 | #./esp32_initialize.sh 7 | #cd ~/MicroPython_ESP32_psRAM_LoBo/MicroPython_BUILD/ 8 | #./BUILD.sh monitor 9 | echo "Done" 10 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/esp32_renew_PSRAM.sh: -------------------------------------------------------------------------------- 1 | CLEAN="$1" 2 | cd esp32_lobo 3 | #./esp32_sync.sh 4 | esp32_build.sh #CLEAN 5 | esp32_flash.sh /dev/ttyS4 6 | #./esp32_initialize.sh 7 | #./esp32_initialize.sh 8 | #cd ~/MicroPython_ESP32_psRAM_LoBo/MicroPython_BUILD/ 9 | #./BUILD.sh monitor 10 | echo "Done" 11 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/esp32_sync.sh: -------------------------------------------------------------------------------- 1 | cd ~/MicroPython_ESP32_psRAM_LoBo/ 2 | rsync -av "/home/kevin/ws_cloud/Programme Python/WNodePython/pysmartnode/" ./MicroPython_BUILD/components/micropython/esp32/modules/pysmartnode/ --delete --exclude=__pycache__ --exclude=.git --exclude=.gitignore --exclude=.project --exclude=.pydevproject --exclude=*.mpy --exclude=*.md --exclude=*.bin 3 | rsync -av "/home/kevin/ws_cloud/Programme Python/WNodePython/_testing/" ./MicroPython_BUILD/components/micropython/esp32/modules/_testing/ --delete --exclude=__pycache__ --exclude=.git --exclude=.gitignore --exclude=.project --exclude=.pydevproject --exclude=*.mpy --exclude=*.md --exclude=*.bin 4 | rsync -av "/home/kevin/ws_cloud/Programme Python/WNodePython/external_modules/" ./MicroPython_BUILD/components/micropython/esp32/modules/ --exclude=*.egg-info --exclude=.git --exclude=.gitignore --exclude=.project --exclude=.pydevproject --exclude=*.mpy --exclude=*.md --exclude=*.bin 5 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/mncfg_exit.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /_deprecated/tools/esp32_lobo/set_port.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | new_port='CONFIG_ESPTOOLPY_PORT='\""$1"\" 3 | sed -i '/CONFIG_ESPTOOLPY_PORT=.*/c\'"$new_port" ~/MicroPython_ESP32_psRAM_LoBo/MicroPython_BUILD/sdkconfig 4 | -------------------------------------------------------------------------------- /_deprecated/tools/esp8266_remove_hints.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | pip3 install strip-hints 3 | cd ~/micropython/ports/esp8266/modules 4 | function foo { 5 | echo $1 6 | local res=$(/home/kevin/.local/bin/strip-hints --only-assigns-and-defs --only-test-for-changes $1) 7 | if [[ $res = "True" ]]; then 8 | echo "$1 stripped of hints" 9 | v=$(/home/kevin/.local/bin/strip-hints --only-assigns-and-defs $1) 10 | echo "$v" > $1 11 | #else 12 | # echo $1 $res 13 | fi 14 | } 15 | export -f foo 16 | find . -name \*.py -exec bash -c 'foo "$@"' bash {} \; -------------------------------------------------------------------------------- /_templates/button_template.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-10 4 | 5 | """ 6 | example config for remoteConfig module or as json in components.py: 7 | { 8 | package: 9 | component: Button 10 | constructor_args: {} 11 | } 12 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 13 | """ 14 | 15 | # A button is basically a switch with a single-shot action that deactivates itself afterwards. 16 | 17 | __updated__ = "2020-04-03" 18 | __version__ = "0.81" 19 | 20 | from pysmartnode import config 21 | from pysmartnode.utils.component.button import ComponentButton 22 | 23 | #################### 24 | # choose a component name that will be used for logging (not in leightweight_log), 25 | # the default mqtt topic that can be changed by received or local component configuration 26 | # as well as for the component name in homeassistant. 27 | COMPONENT_NAME = "Button" 28 | #################### 29 | 30 | _mqtt = config.getMQTT() 31 | _unit_index = -1 32 | 33 | 34 | class Button(ComponentButton): 35 | def __init__(self, **kwargs): 36 | # This makes it possible to use multiple instances of Button. 37 | # It is needed for every default value for mqtt. 38 | # Initialize before super()__init__(...) to not pass the wrong value. 39 | global _unit_index 40 | _unit_index += 1 41 | 42 | # set the initial state otherwise it will be "None" (unknown) and the first request 43 | # will set it accordingly which in case of a button will always be an activation. 44 | initial_state = False # A button will always be False as it is single-shot, 45 | # unless you have a device with a long single-shot action active during reboot. 46 | # You might be able to poll the current state of a device to set the inital state correctly 47 | 48 | super().__init__(COMPONENT_NAME, __version__, _unit_index, 49 | wait_for_lock=False, initial_state=initial_state, **kwargs) 50 | 51 | # If the device needs extra code, launch a new coroutine. 52 | 53 | ##################### 54 | # Change this method according to your device. 55 | ##################### 56 | async def _on(self) -> bool: 57 | """Turn device on.""" 58 | pass 59 | # no return needed because of single-shot action. 60 | # If turning device on fails, it should be handled inside this method 61 | 62 | ##################### 63 | -------------------------------------------------------------------------------- /_templates/components.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 01.06.2018 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | __version__ = "2.0" 8 | __updated__ = "2019-10-20" 9 | 10 | # Template for using components.py as a local component configuration 11 | 12 | # Adapt dictionary to your needs or use alternatives below, 13 | COMPONENTS = { 14 | "_order": ["i2c", "htu", "gpio"], 15 | "i2c": { 16 | "package": ".machine.i2c", 17 | "component": "I2C", 18 | "constructor_args": { 19 | "SCL": "D6", 20 | "SDA": "D5" 21 | } 22 | }, 23 | "htu": { 24 | "package": ".sensors.htu21d", 25 | "component": "HTU21D", 26 | "constructor_args": { 27 | "i2c": "i2c", 28 | "precision_temp": 2, 29 | "precision_humid": 1, 30 | "temp_offset": -2.0, 31 | "humid_offset": 10.0 32 | } 33 | }, 34 | "gpio": { 35 | "package": ".machine.easyGPIO", 36 | "component": "GPIO", 37 | "constructor_args": { 38 | "discover_pins": ["D0", "D1", "D2"] 39 | } 40 | } 41 | } 42 | 43 | # Alternatively or additionally you can register components manually, 44 | # which saves a lot of RAM as no dict doesn't get loaded into RAM. 45 | # This example provides the same configuration as the COMPONENT dict above: 46 | 47 | from pysmartnode import config 48 | import gc 49 | 50 | gc.collect() 51 | from pysmartnode.components.machine.i2c import I2C 52 | 53 | gc.collect() # It's important to call gc.collect() to keep the RAM fragmentation to a minimum 54 | from pysmartnode.components.sensors.htu21d import HTU21D 55 | 56 | gc.collect() 57 | from pysmartnode.components.machine.easyGPIO import GPIO 58 | 59 | gc.collect() 60 | 61 | i2c = I2C(SCL="D6", SDA="D5") 62 | config.addComponent("i2c", i2c) 63 | gc.collect() 64 | htu = HTU21D(i2c, precision_temp=2, precision_humid=1, temp_offset=-2.0, humid_offset=10.0) 65 | config.addComponent("htu", htu) 66 | gc.collect() 67 | gpio = GPIO(discover_pins=["D0", "D1", "D2"]) 68 | config.addComponent("gpio", gpio) # This is optional, it just puts 69 | # your component in the dictionary where all registered components are. 70 | -------------------------------------------------------------------------------- /_templates/sensor_template.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_templates/sensor_template.py -------------------------------------------------------------------------------- /_templates/switch_template.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-10-31 4 | 5 | """ 6 | example config: 7 | { 8 | package: 9 | component: Switch 10 | constructor_args: { 11 | # mqtt_topic: null # optional, defaults to //Switch<_unit_index>/set 12 | # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery 13 | } 14 | } 15 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 16 | """ 17 | 18 | __updated__ = "2020-04-03" 19 | __version__ = "1.91" 20 | 21 | from pysmartnode import config 22 | from pysmartnode.utils.component.switch import ComponentSwitch 23 | 24 | #################### 25 | # choose a component name that will be used for logging (not in leightweight_log), 26 | # the default mqtt topic that can be changed by received or local component configuration 27 | # as well as for the component name in homeassistant. 28 | COMPONENT_NAME = "Switch" 29 | #################### 30 | 31 | _mqtt = config.getMQTT() 32 | _unit_index = -1 33 | 34 | 35 | class Switch(ComponentSwitch): 36 | def __init__(self, mqtt_topic=None, friendly_name=None, **kwargs): 37 | # This makes it possible to use multiple instances of Button. 38 | # It is needed for every default value for mqtt. 39 | # Initialize before super()__init__(...) to not pass the wrong value. 40 | global _unit_index 41 | _unit_index += 1 42 | 43 | # mqtt_topic and friendly_name can be removed from the constructor if not initialized 44 | # differently by this module if not given by the user. If removed from the constructor, 45 | # also remove it from super().__init__(...)! 46 | # example: 47 | friendly_name = friendly_name or "mySwitch_name_friendly_{!s}".format(_unit_index) 48 | 49 | ### 50 | # set the initial state otherwise it will be "None" (unknown) and the first request 51 | # will set it accordingly. 52 | initial_state = None 53 | # should be False/True if can read your devices state on startup or know the state because 54 | # you initialize a pin in a certain state. 55 | ### 56 | 57 | # mqtt_topic can be adapted otherwise a default mqtt_topic will be generated if not 58 | # provided by user configuration. 59 | # friendly_name can be adapted, otherwise it will be unconfigured (which is ok too). 60 | super().__init__(COMPONENT_NAME, __version__, _unit_index, 61 | friendly_name=friendly_name, mqtt_topic=mqtt_topic, 62 | # remove friendly_name and mqtt_topic if removed from constructor 63 | wait_for_lock=True, initial_state=initial_state, **kwargs) 64 | 65 | # If the device needs extra code, launch a new coroutine. 66 | 67 | ##################### 68 | # Change these methods according to your device. 69 | ##################### 70 | async def _on(self) -> bool: 71 | """Turn device on.""" 72 | return True # return True when turning device on was successful. 73 | 74 | async def _off(self) -> bool: 75 | """Turn device off. """ 76 | return True # return True when turning device off was successful. 77 | ##################### 78 | -------------------------------------------------------------------------------- /_testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/_testing/__init__.py -------------------------------------------------------------------------------- /_testing/sensor.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2020 Released under the MIT license 3 | # Created on 2020-04-02 4 | 5 | __updated__ = "2020-04-02" 6 | __version__ = "0.2" 7 | 8 | from pysmartnode import config 9 | from pysmartnode import logging 10 | from pysmartnode.utils.component.sensor import ComponentSensor, SENSOR_TEMPERATURE, SENSOR_HUMIDITY 11 | import gc 12 | import time 13 | 14 | #################### 15 | # choose a component name that will be used for logging (not in leightweight_log) and 16 | # a default mqtt topic that can be changed by received or local component configuration 17 | COMPONENT_NAME = "TestSensor" 18 | # define (homeassistant) value templates for all sensor readings 19 | _VAL_T_TEMPERATURE = "{{ value_json.temperature }}" 20 | _VAL_T_HUMIDITY = "{{ value_json.humidity }}" 21 | #################### 22 | 23 | _log = logging.getLogger(COMPONENT_NAME) 24 | _mqtt = config.getMQTT() 25 | gc.collect() 26 | 27 | _unit_index = -1 28 | 29 | 30 | class Sensor(ComponentSensor): 31 | def __init__(self, interval_reading=0.05, interval_publish=5, publish_old_values=True, 32 | discover=False, **kwargs): 33 | global _unit_index 34 | _unit_index += 1 35 | super().__init__(COMPONENT_NAME, __version__, _unit_index, logger=_log, discover=discover, 36 | interval_reading=interval_reading, interval_publish=interval_publish, 37 | publish_old_values=publish_old_values, **kwargs) 38 | # discover: boolean, if this component should publish its mqtt discovery. 39 | # This can be used to prevent combined Components from exposing underlying 40 | # hardware components like a power switch 41 | 42 | self._addSensorType(SENSOR_TEMPERATURE, 0, 0, _VAL_T_TEMPERATURE, "°C") 43 | self._addSensorType(SENSOR_HUMIDITY, 0, 0, _VAL_T_HUMIDITY, "%") 44 | 45 | ############################## 46 | gc.collect() 47 | self._i = 0 48 | 49 | async def _read(self): 50 | t = self._i 51 | h = self._i 52 | _log.debug(time.ticks_ms(), self._i, local_only=True) 53 | self._i += 1 54 | await self._setValue(SENSOR_TEMPERATURE, t) 55 | await self._setValue(SENSOR_HUMIDITY, h) 56 | -------------------------------------------------------------------------------- /_testing/switch.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-09 4 | 5 | __updated__ = "2019-11-11" 6 | __version__ = "0.1" 7 | 8 | from pysmartnode.utils.component.switch import ComponentSwitch as _Switch 9 | from pysmartnode import config 10 | from pysmartnode import logging 11 | 12 | mqtt = config.getMQTT() 13 | log = logging.getLogger("Switch") 14 | 15 | 16 | class Switch(_Switch): 17 | def __init__(self): 18 | super().__init__("testswitch", __version__, 0, 19 | mqtt_topic=mqtt.getDeviceTopic("switch", is_request=True), 20 | friendly_name="Testswitch", initial_state=None, discover=False) 21 | log.info("State: {!s}".format(self._state), local_only=True) 22 | 23 | async def _on(self): 24 | log.info("State: {!s}".format(True if self._state is False else False), local_only=True) 25 | return True 26 | 27 | async def _off(self): 28 | log.info("State: {!s}".format(False if self._state is True else True), local_only=True) 29 | return True 30 | -------------------------------------------------------------------------------- /boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | # import esp 3 | # esp.osdebug(0) 4 | import gc 5 | 6 | # import webrepl 7 | # webrepl.start() 8 | gc.collect() 9 | 10 | 11 | def reload(mod): 12 | import sys 13 | mod_name = mod.__name__ 14 | del sys.modules[mod_name] 15 | return __import__(mod_name) 16 | 17 | 18 | import uos 19 | import machine 20 | 21 | uos.dupterm(machine.UART(0, 115200), 1) 22 | -------------------------------------------------------------------------------- /config_example.py: -------------------------------------------------------------------------------- 1 | from sys import platform 2 | 3 | ### 4 | # Uncomment optional configurations to change their default value. 5 | # See pysmartnode/config_base.py for more configuration options. 6 | ### 7 | 8 | 9 | # Required custom configuration 10 | WIFI_SSID = "SSID" # optional on esp8266 if it has been connected once 11 | WIFI_PASSPHRASE = "PASSPHRASE" # optional on esp8266 if it has been connected once 12 | MQTT_HOST = "BROKER-IP" 13 | MQTT_PORT = 1883 14 | MQTT_USER = "" # optional if no authentication needed 15 | MQTT_PASSWORD = "" # optional if no authentication needed 16 | 17 | ### 18 | # Optional configuration 19 | ### 20 | # MQTT_KEEPALIVE = const(120) 21 | # MQTT_HOME = "home" 22 | # MQTT_AVAILABILITY_SUBTOPIC = "available" # will be generated to MQTT_HOME//MQTT_AVAILABILITY_SUBTOPIC 23 | # MQTT_DISCOVERY_PREFIX = "homeassistant" 24 | # MQTT_DISCOVERY_ENABLED = True 25 | # MQTT_RECEIVE_CONFIG = True 26 | ### 27 | # RECEIVE_CONFIG: Only use if you run the "SmartServer" in your environment which 28 | # sends the configuration of a device over mqtt 29 | # If you do not run it, you have to configure the components locally on each microcontroller 30 | ### 31 | 32 | # WIFI_LED = None # set a pin number to have the wifi state displayed by a blinking led. Useful for devices like sonoff 33 | # WIFI_LED_ACTIVE_HIGH = True # if led is on when output is low, change to False 34 | 35 | # WEBREPL_ACTIVE = False # If you want to have the webrepl active. Configures and starts it automatically. 36 | # WEBREPL_PASSWORD = "" # Be aware that webrepl needs 1.4kB of RAM! Also password can't be more than 9 characters! 37 | 38 | RTC_TIMEZONE_OFFSET = 1 # offset from GMT timezone as ntptime on esp8266 does not support timezones 39 | RTC_DAYLIGHT_SAVINGS = False # True will add +1 hour to timezone during summer time. 40 | 41 | # 10min, Interval sensors send a new value if not specified by specific configuration 42 | # INTERVAL_SEND_SENSOR = const(600) 43 | 44 | ### 45 | # Name of the device 46 | ## 47 | # DEVICE_NAME = None 48 | ### 49 | # set to a unique device name otherwise the id will be used. 50 | # This is relevant for homeassistant mqtt autodiscovery so the device gets 51 | # recognized by its device_name instead of the id. 52 | # It is also used with the unix port instead of the unique chip id (which is not available 53 | # on the unix port) and it therefore has to be UNIQUE in your network or 54 | # it will result in problems. 55 | ### 56 | -------------------------------------------------------------------------------- /dev/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/dev/__init__.py -------------------------------------------------------------------------------- /dev/custom_components/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/dev/custom_components/__init__.py -------------------------------------------------------------------------------- /dev/custom_components/unix/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/dev/custom_components/unix/__init__.py -------------------------------------------------------------------------------- /dev/displays/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/dev/displays/__init__.py -------------------------------------------------------------------------------- /dev/displays/ssd1306.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/dev/displays/ssd1306.py -------------------------------------------------------------------------------- /dev/gpio_rpi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = '0.1.0' 3 | 4 | import os 5 | 6 | 7 | class PinState(object): 8 | """An ultra simple pin-state object. 9 | Keeps track data related to each pin. 10 | Args: 11 | value: the file pointer to set/read value of pin. 12 | direction: the file pointer to set/read direction of the pin. 13 | """ 14 | 15 | def __init__(self, value, direction): 16 | self.value = value 17 | self.direction = direction 18 | 19 | 20 | path = os.path 21 | pjoin = os.path.join 22 | 23 | gpio_root = '/sys/class/gpio' 24 | gpiopath = lambda pin: os.path.join(gpio_root, 'gpio{0}'.format(pin)) 25 | 26 | _open = dict() 27 | FMODE = 'w+' 28 | 29 | IN, OUT = "in", "out" 30 | LOW, HIGH = 0, 1 31 | 32 | 33 | def _write(f, v): 34 | print("writing: {}: {}".format(f, v)) 35 | f.write(str(v)) 36 | f.flush() 37 | 38 | 39 | def _read(f): 40 | print("Reading: {}".format(f)) 41 | f.seek(0) 42 | return f.read().strip() 43 | 44 | 45 | def _verify(function): 46 | """decorator to ensure pin is properly set up""" 47 | 48 | def wrapped(pin, *args, **kwargs): 49 | pin = int(pin) 50 | if pin not in _open: 51 | ppath = gpiopath(pin) 52 | if not os.path.exists(ppath): 53 | print("Creating Pin {0}".format(pin)) 54 | with open(pjoin(gpio_root, 'export'), 'w') as f: 55 | _write(f, pin) 56 | value = open(pjoin(ppath, 'value'), FMODE) 57 | direction = open(pjoin(ppath, 'direction'), FMODE) 58 | _open[pin] = PinState(value=value, direction=direction) 59 | return function(pin, *args, **kwargs) 60 | 61 | return wrapped 62 | 63 | 64 | def cleanup(pin=None, assert_exists=False): 65 | """Cleanup the pin by closing and unexporting it. 66 | Args: 67 | pin (int, optional): either the pin to clean up or None (default). 68 | If None, clean up all pins. 69 | assert_exists: if True, raise a ValueError if the pin was not 70 | setup. Otherwise, this function is a NOOP. 71 | """ 72 | if pin is None: 73 | # Take a list of keys because we will be deleting from _open 74 | for pin in list(_open): 75 | cleanup(pin) 76 | return 77 | if not isinstance(pin, int): 78 | raise TypeError("pin must be an int, got: {}".format(pin)) 79 | 80 | state = _open.get(pin) 81 | if state is None: 82 | if assert_exists: 83 | raise ValueError("pin {} was not setup".format(pin)) 84 | return 85 | state.value.close() 86 | state.direction.close() 87 | if os.path.exists(gpiopath(pin)): 88 | print("Unexporting pin {0}".format(pin)) 89 | with open(pjoin(gpio_root, 'unexport'), 'w') as f: 90 | _write(f, pin) 91 | 92 | del _open[pin] 93 | 94 | 95 | @_verify 96 | def setup(pin, mode, pullup=None, initial=False): 97 | '''Setup pin with mode IN or OUT. 98 | Args: 99 | pin (int): 100 | mode (str): use either gpio.OUT or gpio.IN 101 | pullup (None): rpio compatibility. If anything but None, raises 102 | value Error 103 | pullup (bool, optional): Initial pin value. Default is False 104 | ''' 105 | if pullup is not None: 106 | raise ValueError("sysfs does not support pullups") 107 | 108 | if mode not in (IN, OUT): 109 | raise ValueError(mode) 110 | 111 | print("Setup {}: {}".format(pin, mode)) 112 | f = _open[pin].direction 113 | _write(f, mode) 114 | if mode == OUT: 115 | if initial: 116 | set(pin, 1) 117 | else: 118 | set(pin, 0) 119 | 120 | 121 | # @_verify 122 | def mode(pin): 123 | '''get the pin mode 124 | Returns: 125 | str: "in" or "out" 126 | ''' 127 | f = _open[pin].direction 128 | return _read(f) 129 | 130 | 131 | # @_verify 132 | def read(pin): 133 | '''read the pin value 134 | Returns: 135 | bool: 0 or 1 136 | ''' 137 | f = _open[pin].value 138 | out = int(_read(f)) 139 | print("Read {}: {}".format(pin, out)) 140 | return out 141 | 142 | 143 | # @_verify 144 | def set(pin, value): 145 | '''set the pin value to 0 or 1''' 146 | print("Write {}: {}".format(pin, value)) 147 | f = _open[pin].value 148 | _write(f, value) 149 | 150 | 151 | input = read 152 | output = set 153 | 154 | 155 | def setwarnings(value): 156 | '''exists for rpio compatibility''' 157 | pass 158 | 159 | 160 | def setmode(value): 161 | '''exists for rpio compatibility''' 162 | pass 163 | 164 | 165 | BCM = None # rpio compatibility 166 | -------------------------------------------------------------------------------- /dev/unix/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/dev/unix/__init__.py -------------------------------------------------------------------------------- /dev/unix/popen_base.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-07-02 4 | 5 | __updated__ = "2019-07-11" 6 | __version__ = "0.3" 7 | 8 | import os 9 | import uasyncio as asyncio 10 | from pysmartnode.utils.wrappers.timeit import timeitAsync 11 | 12 | 13 | class Popen: 14 | def __init__(self, command, expected_return=None, execution_time=0, iterations=1, iter_delay=10): 15 | self._com = command 16 | self._expected_return = expected_return 17 | self._iterations = iterations 18 | self._iter_delay = iter_delay 19 | self._exec_time = execution_time 20 | 21 | @timeitAsync 22 | async def _execute(self): 23 | """ 24 | Execute the stored function. 25 | :return: 26 | """ 27 | f = os.popen(self._com) 28 | if self._exec_time > 0: 29 | await asyncio.sleep_ms(self._exec_time) 30 | try: 31 | r = f.read() 32 | print(r) 33 | if self._expected_return is not None: 34 | if r == self._expected_return: 35 | return True 36 | return r 37 | else: 38 | return r 39 | except Exception as e: 40 | raise e 41 | finally: 42 | f.close() 43 | 44 | async def execute(self): 45 | """ 46 | Executes the command self._iterations times and returns True if at least one return value 47 | equals the expected return otherwise it will return the returned value. 48 | :return: 49 | """ 50 | eq = False 51 | for i in range(self._iterations): 52 | r = await self._execute() 53 | if r == self._expected_return: 54 | eq = True 55 | await asyncio.sleep_ms(self._iter_delay) 56 | return eq if eq is True else r 57 | -------------------------------------------------------------------------------- /dev/unix/rf433switch.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-07-03 4 | 5 | """ 6 | example config: 7 | { 8 | package: .unix.rf433switch 9 | component: RF433 10 | constructor_args: { 11 | unit_code: "10001" 12 | unit: "1" 13 | # expected_execution_time_on: 500 # optional, estimated execution time; allows other coroutines to run during that time 14 | # expected_execution_time_off: 500 # optional, estimated execution time; allows other coroutines to run during that time 15 | # iterations: 1 # optional, number of times the command will be executed 16 | # iter_delay: 20 # optional, delay in ms between iterations 17 | # mqtt_topic: null #optional, defaults to //RF433/set 18 | # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery 19 | } 20 | } 21 | """ 22 | 23 | __updated__ = "2019-09-29" 24 | __version__ = "0.4" 25 | 26 | import gc 27 | from pysmartnode import config 28 | from pysmartnode import logging 29 | from pysmartnode.utils.component import Switch, DISCOVERY_SWITCH 30 | from .popen_base import Popen 31 | 32 | #################### 33 | COMPONENT_NAME = "RF433Switch" 34 | # define the type of the component according to the homeassistant specifications 35 | _COMPONENT_TYPE = "switch" 36 | #################### 37 | 38 | _mqtt = config.getMQTT() 39 | _log = logging.getLogger(COMPONENT_NAME) 40 | 41 | gc.collect() 42 | 43 | _unit_index = -1 44 | 45 | COMMAND_ON = "~/raspberry-remote/send {!s} {!s} 1" 46 | COMMAND_OFF = "~/raspberry-remote/send {!s} {!s} 0" 47 | EXPECTED_RETURN_ON = 'using pin 0\nsending systemCode[{!s}] unitCode[{!s}] command[1]\n' 48 | EXPECTED_RETURN_OFF = 'using pin 0\nsending systemCode[{!s}] unitCode[{!s}] command[0]\n' 49 | 50 | 51 | class RF433(Switch): 52 | lock = config.Lock() # only one method can have control over the RF433 device 53 | 54 | def __init__(self, unit_code, unit, expected_execution_time_on=500, expected_execution_time_off=500, 55 | iterations=1, iter_delay=10, mqtt_topic=None, friendly_name=None): 56 | super().__init__() 57 | self._log = _log 58 | 59 | # This makes it possible to use multiple instances of Switch 60 | global _unit_index 61 | self._count = _count 62 | _unit_index += 1 63 | self._topic = mqtt_topic or _mqtt.getDeviceTopic("{!s}{!s}".format(COMPONENT_NAME, self._count), 64 | is_request=True) 65 | self._subscribe(self._topic, self.on_message) 66 | self._frn = friendly_name 67 | gc.collect() 68 | self.unit_lock = config.Lock() 69 | self._c_on = Popen(COMMAND_ON.format(unit_code, unit), EXPECTED_RETURN_ON.format(unit_code, unit), 70 | expected_execution_time_on, iterations, iter_delay) 71 | self._c_off = Popen(COMMAND_OFF.format(unit_code, unit), EXPECTED_RETURN_OFF.format(unit_code, unit), 72 | expected_execution_time_off, iterations, iter_delay) 73 | 74 | async def on_message(self, topic, msg, retain): 75 | if self.unit_lock.locked(): 76 | return False 77 | async with self.lock: 78 | async with self.unit_lock: 79 | if msg in _mqtt.payload_on: 80 | r = await self._c_on.execute() 81 | if r is True: 82 | await _mqtt.publish(self._topic[:-4], "ON", qos=1, retain=True) # makes it easier to subclass 83 | return True 84 | else: 85 | await self._log.asyncLog("warn", "Got unexpected return: {!s}".format(r)) 86 | return False 87 | elif msg in _mqtt.payload_off: 88 | r = await self._c_off.execute() 89 | if r is True: 90 | await _mqtt.publish(self._topic[:-4], "OFF", qos=1, retain=True) 91 | return True 92 | else: 93 | await self._log.asyncLog("warn", "Got unexpected return: {!s}".format(r)) 94 | return False 95 | 96 | async def _discovery(self): 97 | name = "{!s}{!s}".format(COMPONENT_NAME, self._count) 98 | await self._publishDiscovery(_COMPONENT_TYPE, self._topic[:-4], name, DISCOVERY_SWITCH, self._frn) 99 | # note that _publishDiscovery does expect the state topic but we have the command topic stored. 100 | -------------------------------------------------------------------------------- /dev/unix/switch.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-07-02 4 | 5 | """ 6 | example config: 7 | { 8 | package: .unix.switch 9 | component: Switch 10 | constructor_args: { 11 | command_on: "something" # command to execute when switch gets switched off 12 | expected_return_on: "True" # optional, expected output of command_on. If not provided, output is being published 13 | expected_execution_time_on: 50 # optional, estimated execution time; allows other coroutines to run during that time 14 | command_off: "something" # command to execute when switch gets switched off 15 | expected_return_off: "True" # optional, expected output of command_on. If not provided, output is being published 16 | expected_execution_time_off: 50 # optional, estimated execution time; allows other coroutines to run during that time 17 | iterations: 1 # optional, number of times the command will be executed 18 | iter_delay: 20 # optional, delay in ms between iterations 19 | # mqtt_topic: null #optional, defaults to //UnixSwitch/set 20 | # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery 21 | } 22 | } 23 | """ 24 | 25 | __updated__ = "2019-09-08" 26 | __version__ = "0.4" 27 | 28 | import gc 29 | from pysmartnode import config 30 | from pysmartnode import logging 31 | from pysmartnode.utils.component import Switch as _Switch, DISCOVERY_SWITCH 32 | from .popen_base import Popen 33 | 34 | #################### 35 | COMPONENT_NAME = "UnixSwitch" 36 | # define the type of the component according to the homeassistant specifications 37 | _COMPONENT_TYPE = "switch" 38 | #################### 39 | 40 | _mqtt = config.getMQTT() 41 | _log = logging.getLogger(COMPONENT_NAME) 42 | 43 | gc.collect() 44 | 45 | _unit_index = -1 46 | 47 | 48 | class Switch(_Switch): 49 | def __init__(self, command_on, command_off, expected_return_on=None, expected_execution_time_on=0, 50 | expected_return_off=None, expected_execution_time_off=0, 51 | iterations=1, iter_delay=10, mqtt_topic=None, friendly_name=None): 52 | super().__init__() 53 | 54 | # This makes it possible to use multiple instances of Switch 55 | global _unit_index 56 | self._count = _count 57 | _unit_index += 1 58 | self._topic = mqtt_topic or _mqtt.getDeviceTopic("{!s}/{!s}".format(COMPONENT_NAME, self._count), 59 | is_request=True) 60 | self._subscribe(self._topic, self.on_message) 61 | self._frn = friendly_name 62 | gc.collect() 63 | self.lock = config.Lock() # in case switch activates a device that will need a while to finish 64 | self._c_on = Popen(command_on, expected_return_on, expected_execution_time_on, iterations, iter_delay) 65 | self._c_off = Popen(command_off, expected_return_off, expected_execution_time_off, iterations, iter_delay) 66 | 67 | async def on_message(self, topic, msg, retain): 68 | if self.lock.locked(): 69 | return False 70 | async with self.lock: 71 | if msg in _mqtt.payload_on: 72 | r = await self._c_on.execute() 73 | if r is True: 74 | return True 75 | else: 76 | await _log.asyncLog("warn", "Got unexpected return: {!s}".format(r)) 77 | return False 78 | elif msg in _mqtt.payload_off: 79 | r = await self._c_off.execute() 80 | if r is True: 81 | return True 82 | else: 83 | await _log.asyncLog("warn", "Got unexpected return: {!s}".format(r)) 84 | return False 85 | return False # will not publish the requested state to mqtt 86 | 87 | async def _discovery(self): 88 | name = "{!s}{!s}".format(COMPONENT_NAME, self._count) 89 | await self._publishDiscovery(_COMPONENT_TYPE, self._topic[:-4], name, DISCOVERY_SWITCH, self._frn) 90 | # note that _publishDiscovery does expect the state topic but we have the command topic stored. 91 | -------------------------------------------------------------------------------- /external_modules/micropython_stat.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: micropython-stat 3 | Version: 0.5.1 4 | Summary: CPython stat module ported to MicroPython 5 | Home-page: https://github.com/micropython/micropython-lib 6 | Author: MicroPython Developers 7 | Author-email: micro-python@googlegroups.com 8 | License: Python 9 | Description: This is a module ported from CPython standard library to be compatible with 10 | MicroPython interpreter. Usually, this means applying small patches for 11 | features not supported (yet, or at all) in MicroPython. Sometimes, heavier 12 | changes are required. Note that CPython modules are written with availability 13 | of vast resources in mind, and may not work for MicroPython ports with 14 | limited heap. If you are affected by such a case, please help reimplement 15 | the module from scratch. 16 | Platform: UNKNOWN 17 | -------------------------------------------------------------------------------- /external_modules/stat.py: -------------------------------------------------------------------------------- 1 | """Constants/functions for interpreting results of os.stat() and os.lstat(). 2 | 3 | Suggested usage: from stat import * 4 | """ 5 | 6 | # Indices for stat struct members in the tuple returned by os.stat() 7 | 8 | ST_MODE = 0 9 | ST_INO = 1 10 | ST_DEV = 2 11 | ST_NLINK = 3 12 | ST_UID = 4 13 | ST_GID = 5 14 | ST_SIZE = 6 15 | ST_ATIME = 7 16 | ST_MTIME = 8 17 | ST_CTIME = 9 18 | 19 | # Extract bits from the mode 20 | 21 | def S_IMODE(mode): 22 | """Return the portion of the file's mode that can be set by 23 | os.chmod(). 24 | """ 25 | return mode & 0o7777 26 | 27 | def S_IFMT(mode): 28 | """Return the portion of the file's mode that describes the 29 | file type. 30 | """ 31 | return mode & 0o170000 32 | 33 | # Constants used as S_IFMT() for various file types 34 | # (not all are implemented on all systems) 35 | 36 | S_IFDIR = 0o040000 # directory 37 | S_IFCHR = 0o020000 # character device 38 | S_IFBLK = 0o060000 # block device 39 | S_IFREG = 0o100000 # regular file 40 | S_IFIFO = 0o010000 # fifo (named pipe) 41 | S_IFLNK = 0o120000 # symbolic link 42 | S_IFSOCK = 0o140000 # socket file 43 | 44 | # Functions to test for each file type 45 | 46 | def S_ISDIR(mode): 47 | """Return True if mode is from a directory.""" 48 | return S_IFMT(mode) == S_IFDIR 49 | 50 | def S_ISCHR(mode): 51 | """Return True if mode is from a character special device file.""" 52 | return S_IFMT(mode) == S_IFCHR 53 | 54 | def S_ISBLK(mode): 55 | """Return True if mode is from a block special device file.""" 56 | return S_IFMT(mode) == S_IFBLK 57 | 58 | def S_ISREG(mode): 59 | """Return True if mode is from a regular file.""" 60 | return S_IFMT(mode) == S_IFREG 61 | 62 | def S_ISFIFO(mode): 63 | """Return True if mode is from a FIFO (named pipe).""" 64 | return S_IFMT(mode) == S_IFIFO 65 | 66 | def S_ISLNK(mode): 67 | """Return True if mode is from a symbolic link.""" 68 | return S_IFMT(mode) == S_IFLNK 69 | 70 | def S_ISSOCK(mode): 71 | """Return True if mode is from a socket.""" 72 | return S_IFMT(mode) == S_IFSOCK 73 | 74 | # Names for permission bits 75 | 76 | S_ISUID = 0o4000 # set UID bit 77 | S_ISGID = 0o2000 # set GID bit 78 | S_ENFMT = S_ISGID # file locking enforcement 79 | S_ISVTX = 0o1000 # sticky bit 80 | S_IREAD = 0o0400 # Unix V7 synonym for S_IRUSR 81 | S_IWRITE = 0o0200 # Unix V7 synonym for S_IWUSR 82 | S_IEXEC = 0o0100 # Unix V7 synonym for S_IXUSR 83 | S_IRWXU = 0o0700 # mask for owner permissions 84 | S_IRUSR = 0o0400 # read by owner 85 | S_IWUSR = 0o0200 # write by owner 86 | S_IXUSR = 0o0100 # execute by owner 87 | S_IRWXG = 0o0070 # mask for group permissions 88 | S_IRGRP = 0o0040 # read by group 89 | S_IWGRP = 0o0020 # write by group 90 | S_IXGRP = 0o0010 # execute by group 91 | S_IRWXO = 0o0007 # mask for others (not in group) permissions 92 | S_IROTH = 0o0004 # read by others 93 | S_IWOTH = 0o0002 # write by others 94 | S_IXOTH = 0o0001 # execute by others 95 | 96 | # Names for file flags 97 | 98 | UF_NODUMP = 0x00000001 # do not dump file 99 | UF_IMMUTABLE = 0x00000002 # file may not be changed 100 | UF_APPEND = 0x00000004 # file may only be appended to 101 | UF_OPAQUE = 0x00000008 # directory is opaque when viewed through a union stack 102 | UF_NOUNLINK = 0x00000010 # file may not be renamed or deleted 103 | UF_COMPRESSED = 0x00000020 # OS X: file is hfs-compressed 104 | UF_HIDDEN = 0x00008000 # OS X: file should not be displayed 105 | SF_ARCHIVED = 0x00010000 # file may be archived 106 | SF_IMMUTABLE = 0x00020000 # file may not be changed 107 | SF_APPEND = 0x00040000 # file may only be appended to 108 | SF_NOUNLINK = 0x00100000 # file may not be renamed or deleted 109 | SF_SNAPSHOT = 0x00200000 # file is a snapshot file 110 | 111 | 112 | _filemode_table = ( 113 | ((S_IFLNK, "l"), 114 | (S_IFREG, "-"), 115 | (S_IFBLK, "b"), 116 | (S_IFDIR, "d"), 117 | (S_IFCHR, "c"), 118 | (S_IFIFO, "p")), 119 | 120 | ((S_IRUSR, "r"),), 121 | ((S_IWUSR, "w"),), 122 | ((S_IXUSR|S_ISUID, "s"), 123 | (S_ISUID, "S"), 124 | (S_IXUSR, "x")), 125 | 126 | ((S_IRGRP, "r"),), 127 | ((S_IWGRP, "w"),), 128 | ((S_IXGRP|S_ISGID, "s"), 129 | (S_ISGID, "S"), 130 | (S_IXGRP, "x")), 131 | 132 | ((S_IROTH, "r"),), 133 | ((S_IWOTH, "w"),), 134 | ((S_IXOTH|S_ISVTX, "t"), 135 | (S_ISVTX, "T"), 136 | (S_IXOTH, "x")) 137 | ) 138 | 139 | def filemode(mode): 140 | """Convert a file's mode to a string of the form '-rwxrwxrwx'.""" 141 | perm = [] 142 | for table in _filemode_table: 143 | for bit, char in table: 144 | if mode & bit == bit: 145 | perm.append(char) 146 | break 147 | else: 148 | perm.append("-") 149 | return "".join(perm) 150 | -------------------------------------------------------------------------------- /file_flowchart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/file_flowchart.jpg -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/main.py -------------------------------------------------------------------------------- /pysmartnode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pysmartnode/components/devices/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/components/devices/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/devices/arduinoGPIO/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/components/devices/arduinoGPIO/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/devices/arduinoGPIO/arduino.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-04-08 4 | 5 | __updated__ = "2019-04-08" 6 | __version__ = "0.0" 7 | 8 | """ 9 | Arduino Instance 10 | { 11 | package: .devices.arduinoGPIO.arduino 12 | component: Arduino 13 | constructor_args: { 14 | arduinoControl: "ardControlName" # ArduinoControl instance 15 | rom: "ArduinoROM" # Arduino device ROM 16 | } 17 | } 18 | 19 | Arduino Pin instance (could also use ArduinoControl Pin) 20 | { 21 | package: .devices.arduinoGPIO.arduino 22 | component: Pin 23 | constructor_args: { 24 | arduino: "arduinoName" # Arduino instance 25 | pin: 4 # Pin number 26 | mode: 1 # Arduino pin mode, ArduinoInteger 27 | value: 0 # Starting value of the pin 28 | } 29 | } 30 | 31 | Arduino ADC instance (could also use ArduinoControl ADC) 32 | { 33 | package: .devices.arduinoGPIO.arduino 34 | component: ADC 35 | constructor_args: { 36 | arduino: "arduinoName" # Arduino instance 37 | pin: 0 # Pin number 38 | # vcc: 5 # Arduino VCC voltage 39 | } 40 | } 41 | """ 42 | 43 | from pysmartnode.libraries.arduinoGPIO.arduinoGPIO.arduino import Arduino, Pin, ADC 44 | -------------------------------------------------------------------------------- /pysmartnode/components/devices/arduinoGPIO/arduinoControl.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-03-31 4 | 5 | __updated__ = "2019-11-02" 6 | __version__ = "0.2" 7 | 8 | """ 9 | ArduinoControl Instance 10 | { 11 | package: .devices.arduinoGPIO.arduinoControl 12 | component: ArduinoControl 13 | constructor_args: { 14 | pin: 2 # pin number or object 15 | # expected_devices: ["ROM","ROM"] # list of ROMs or amount of devices, optional 16 | } 17 | } 18 | 19 | ArduinoControl Pin instance 20 | { 21 | package: .devices.arduinoGPIO.arduinoControl 22 | component: Pin 23 | constructor_args: { 24 | arduinoControl: "arduinoControlName" # ArduinoControl instance 25 | rom: "ArduinoROM" # Arduino device ROM 26 | pin: 4 # Pin number 27 | mode: 1 # Arduino pin mode, ArduinoInteger 28 | value: 0 # Starting value of the pin 29 | } 30 | } 31 | 32 | ArduinoControl ADC instance 33 | { 34 | package: .devices.arduinoGPIO.arduinoControl 35 | component: ADC 36 | constructor_args: { 37 | arduinoControl: "arduinoControlName" # ArduinoControl instance 38 | rom: "ArduinoROM" # Arduino device ROM 39 | pin: 0 # Pin number 40 | # vcc: 5 # Arduino VCC voltage 41 | } 42 | } 43 | """ 44 | 45 | from pysmartnode.libraries.arduinoGPIO.arduinoGPIO.arduinoControl import \ 46 | ArduinoControl as _ArduinoControl 47 | from pysmartnode.components.machine.pin import Pin as PyPin 48 | from pysmartnode import logging 49 | 50 | log = logging.getLogger("Arduino") 51 | 52 | 53 | class ArduinoControl(_ArduinoControl): 54 | def __init__(self, pin: any, expected_devices=None): 55 | """ 56 | Class to remotely control an Arduino 57 | :param pin: Pin number/name/object of the onewire connection 58 | :param expected_devices: used to warn if devices go missing (filters non-arduino devices) 59 | """ 60 | pin = PyPin(pin) 61 | if type(expected_devices) == list: 62 | for i, d in enumerate(expected_devices): 63 | if type(d) == str: 64 | expected_devices[i] = self.str2rom(d) 65 | super().__init__(pin, expected_devices) 66 | 67 | def _error(self, message): 68 | log.error(message) 69 | 70 | 71 | def Pin(arduinoControl: ArduinoControl, rom: bytearray, pin: int, *args, **kwargs): 72 | if type(rom) == str: 73 | rom = arduinoControl.str2rom(rom) 74 | return arduinoControl.Pin(rom, pin, *args, **kwargs) 75 | 76 | 77 | def ADC(arduinoControl: ArduinoControl, rom: bytearray, pin: int, vcc: int = 5): 78 | if type(rom) == str: 79 | rom = arduinoControl.str2rom(rom) 80 | return arduinoControl.ADC(rom, pin, vcc) 81 | -------------------------------------------------------------------------------- /pysmartnode/components/devices/climate/definitions.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-10-12 4 | 5 | __updated__ = "2020-08-31" 6 | __version__ = "0.4" 7 | 8 | CLIMATE_DISCOVERY = '{{' \ 9 | '"~":"{!s}/state",' \ 10 | '"name":"{!s}",' \ 11 | '{!s}' \ 12 | '"uniq_id":"{!s}_{!s}",' \ 13 | '"curr_temp_t":"{!s}",' \ 14 | '"curr_temp_tpl":"{!s}",' \ 15 | '"mode_stat_t":"~",' \ 16 | '"mode_stat_tpl":"{{{{ value_json.mode }}}}",' \ 17 | '"mode_cmd_t":"~m/set",' \ 18 | '"act_t":"~",' \ 19 | '"act_tpl":"{{{{ value_json.action }}}}",' \ 20 | '"temp_step":{!s},' \ 21 | '"min_temp":{!s},' \ 22 | '"max_temp":{!s},' \ 23 | '"modes":{!s},' \ 24 | '"away_mode_cmd_t":"~aw/set",' \ 25 | '"away_mode_stat_t":"~",' \ 26 | '"away_mode_stat_tpl":"{{{{ value_json.away }}}}",' \ 27 | '{!s}' \ 28 | '"dev":{!s}' \ 29 | '}}' 30 | 31 | CLIMATE_DISCOVERY_HILOW = '"temp_lo_cmd_t":"~tl/set",' \ 32 | '"temp_lo_stat_t":"~",' \ 33 | '"temp_lo_stat_tpl":"{{ value_json.c_temp_l }}",' \ 34 | '"temp_hi_cmd_t":"~th/set",' \ 35 | '"temp_hi_stat_t":"~",' \ 36 | '"temp_hi_stat_tpl":"{{ value_json.c_temp_h }}",' 37 | 38 | CLIMATE_DISCOVERY_STEMP = '"temp_cmd_t":"~t/set",' \ 39 | '"temp_stat_t":"~",' \ 40 | '"temp_stat_tpl":"{{ value_json.c_temp }}",' 41 | 42 | # IMPLEMENTED MODES 43 | MODE_OFF = "off" 44 | MODE_HEAT = "heat" 45 | MODES_SUPPORTED = [MODE_OFF, MODE_HEAT] 46 | 47 | # ACTION STATES 48 | ACTION_OFF = "off" 49 | ACTION_HEATING = "heating" 50 | ACTION_COOLING = "cooling" 51 | ACTION_DRYING = "drying" 52 | ACTION_IDLE = "idle" 53 | ACTION_FAN = "fan" 54 | ACTIONS = [ACTION_OFF, ACTION_HEATING, ACTION_COOLING, ACTION_DRYING, ACTION_IDLE, ACTION_FAN] 55 | 56 | # AWAY MODES 57 | AWAY_OFF = "OFF" 58 | AWAY_ON = "ON" 59 | 60 | # CLIMATE STATE ATTRIBUTES 61 | # when changing values make sure to change the discovery message values accordingly 62 | CURRENT_TEMPERATURE_LOW = "c_temp_l" 63 | CURRENT_TEMPERATURE_HIGH = "c_temp_h" 64 | CURRENT_TEMPERATURE_SINGLE = "c_temp" 65 | AWAY_MODE_STATE = "away" 66 | STORAGE_AWAY_TEMPERATURE_LOW = "_a_temp_l" 67 | STORAGE_AWAY_TEMPERATURE_HIGH = "_a_temp_h" 68 | STORAGE_TEMPERATURE_LOW = "_temp_l" 69 | STORAGE_TEMPERATURE_HIGH = "_temp_h" 70 | CURRENT_MODE = "mode" 71 | CURRENT_ACTION = "action" 72 | -------------------------------------------------------------------------------- /pysmartnode/components/devices/climate/heat.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-10-12 4 | 5 | __updated__ = "2019-10-31" 6 | __version__ = "0.4" 7 | 8 | from pysmartnode.components.devices.climate import BaseMode, Climate 9 | from .definitions import ACTION_HEATING, ACTION_IDLE, MODE_HEAT, CURRENT_ACTION, \ 10 | CURRENT_TEMPERATURE_HIGH, CURRENT_TEMPERATURE_LOW 11 | 12 | 13 | class heat(BaseMode): 14 | def __init__(self, climate): 15 | super().__init__(climate) 16 | self._last_state = False 17 | 18 | async def trigger(self, climate: Climate, current_temp: float) -> bool: 19 | """Triggered whenever the situation is evaluated again""" 20 | if current_temp is None: 21 | await climate.log.asyncLog("warn", "No temperature", timeout=2, await_connection=False) 22 | # keep heater state is no temp is available. 23 | # Makes it possible to use external button to switch heater state manually 24 | # in case temperature sensor is broken or network unavailable. 25 | # Will be overwritten on next trigger though if temperature is available. 26 | return True 27 | if current_temp > climate.state[CURRENT_TEMPERATURE_HIGH] and self._last_state is True: 28 | # target temperature reached 29 | if await climate.heating_unit.off(): 30 | climate.state[CURRENT_ACTION] = ACTION_IDLE 31 | self._last_state = False 32 | return True 33 | else: 34 | climate.log.error("Couldn't deactivate heater") 35 | return False 36 | elif current_temp < climate.state[CURRENT_TEMPERATURE_LOW] and self._last_state is False: 37 | # start heating 38 | if await climate.heating_unit.on(): 39 | climate.state[CURRENT_ACTION] = ACTION_HEATING 40 | self._last_state = True 41 | return True 42 | else: 43 | climate.log.error("Couldn't activate heater") 44 | return False 45 | else: 46 | # temperature between target temperatures high and low 47 | # set action and state in case the state is retained or changed manually by button 48 | if climate.heating_unit.state() is True and \ 49 | climate.state[CURRENT_ACTION] != ACTION_HEATING: 50 | climate.state[CURRENT_ACTION] = ACTION_HEATING 51 | self._last_state = True 52 | elif climate.heating_unit.state() is False and \ 53 | climate.state[CURRENT_ACTION] != ACTION_IDLE: 54 | climate.state[CURRENT_ACTION] = ACTION_IDLE 55 | self._last_state = False 56 | 57 | async def activate(self, climate: Climate) -> bool: 58 | """Triggered whenever the mode changes and this mode has been activated""" 59 | self._last_state = climate.heating_unit.state() 60 | return True 61 | 62 | async def deactivate(self, climate: Climate) -> bool: 63 | """Triggered whenever the mode changes and this mode has been deactivated""" 64 | return True # no deinit needed 65 | 66 | def __str__(self): 67 | """Name of the mode, has to be the same as the classname/module""" 68 | return MODE_HEAT 69 | -------------------------------------------------------------------------------- /pysmartnode/components/devices/climate/off.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-10-12 4 | 5 | __updated__ = "2019-10-31" 6 | __version__ = "0.2" 7 | 8 | from pysmartnode.components.devices.climate import BaseMode, Climate 9 | from .definitions import ACTION_OFF, MODE_OFF, CURRENT_ACTION 10 | 11 | 12 | class off(BaseMode): 13 | # def __init__(self, climate): 14 | 15 | async def trigger(self, climate: Climate, current_temp: float) -> bool: 16 | """Triggered whenever the situation is evaluated again""" 17 | if climate.heating_unit.state() is False and climate.state[CURRENT_ACTION] == ACTION_OFF: 18 | return True 19 | if await climate.heating_unit.off(): 20 | climate.state[CURRENT_ACTION] = ACTION_OFF 21 | return True 22 | return False 23 | 24 | async def activate(self, climate: Climate) -> bool: 25 | """Triggered whenever the mode changes and this mode has been activated""" 26 | return True # no init needed 27 | 28 | async def deactivate(self, climate: Climate) -> bool: 29 | """Triggered whenever the mode changes and this mode has been deactivated""" 30 | return True # no deinit needed 31 | 32 | def __str__(self): 33 | """Name of the mode, has to be the same as the classname/module""" 34 | return MODE_OFF 35 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/components/machine/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/machine/deepsleep.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2018-2020 Released under the MIT license 3 | # Created on 2018-07-16 4 | 5 | """ 6 | example config: 7 | { 8 | package: .machine.deepsleep 9 | component: deepsleep 10 | constructor_args: { 11 | sleeping_time: 10 # sleeping time in seconds (float value ok) 12 | wait_before_sleep: null # optional, wait this many seconds before going to deepsleep 13 | # event: null # optional, asyncio event to wait for before going to sleep 14 | } 15 | } 16 | """ 17 | 18 | __version__ = "0.3" 19 | __updated__ = "2020-04-09" 20 | 21 | import machine 22 | import uasyncio as asyncio 23 | from sys import platform 24 | 25 | 26 | async def deepsleep(sleeping_time, wait_before_sleep=None, event=None): 27 | if wait_before_sleep is not None: 28 | await asyncio.sleep(wait_before_sleep) 29 | if event is not None: 30 | await event.wait() 31 | rtc = machine.RTC() 32 | rtc.irq(trigger=rtc.ALARM0, wake=machine.DEEPSLEEP) 33 | rtc.alarm(rtc.ALARM0, int(sleeping_time * 1000)) 34 | machine.deepsleep() 35 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/easyGPIO.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-10-30 4 | 5 | """ 6 | example config: 7 | { 8 | package: .machine.easyGPIO 9 | component: GPIO 10 | constructor_args: { 11 | # mqtt_topic: null #optional, topic needs to have /easyGPIO/# at the end; to change a value publish to /easyGPIO//set 12 | # discover_pins: [1,2,3] # optional, discover all pins of the list. Otherwise no pins are discovered. 13 | } 14 | } 15 | Makes esp8266 listen to requested gpio changes or return pin.value() if message is published without payload. 16 | This component is just a generic interface to device pins, it does not offer ComponentSwitch features. 17 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 18 | """ 19 | 20 | __updated__ = "2020-04-03" 21 | __version__ = "1.91" 22 | 23 | import gc 24 | import machine 25 | from pysmartnode.components.machine.pin import Pin 26 | from pysmartnode import config 27 | from pysmartnode import logging 28 | from pysmartnode.utils.component import ComponentBase, DISCOVERY_SWITCH 29 | 30 | _mqtt = config.getMQTT() 31 | 32 | COMPONENT_NAME = "easyGPIO" 33 | _COMPONENT_TYPE = "switch" 34 | 35 | _log = logging.getLogger("easyGPIO") 36 | 37 | gc.collect() 38 | 39 | 40 | class GPIO(ComponentBase): 41 | def __init__(self, mqtt_topic=None, discover_pins=None, **kwargs): 42 | super().__init__(COMPONENT_NAME, __version__, unit_index=0, logger=_log, **kwargs) 43 | self._topic = mqtt_topic or _mqtt.getDeviceTopic("easyGPIO/+/set") 44 | _mqtt.subscribeSync(self._topic, self.on_message, self, check_retained_state=True) 45 | self._d = discover_pins or [] 46 | 47 | async def _discovery(self, register=True): 48 | for pin in self._d: 49 | name = "{!s}_{!s}".format(COMPONENT_NAME, pin) 50 | if register: 51 | await self._publishDiscovery(_COMPONENT_TYPE, "{}{}".format(self._topic[:-5], pin), 52 | name, DISCOVERY_SWITCH) 53 | else: 54 | await self._deleteDiscovery(_COMPONENT_TYPE, name) 55 | 56 | @staticmethod 57 | async def on_message(topic, msg, retain): 58 | if topic.endswith("/set") is False: 59 | if retain: 60 | pin = topic[topic.rfind("easyGPIO/") + 9:] 61 | else: 62 | # topic without /set ignored if not retained 63 | return False 64 | else: 65 | pin = topic[topic.rfind("easyGPIO/") + 9:-4] 66 | print("__gpio pin", pin, msg, retain) 67 | try: 68 | _p = Pin(pin) 69 | except Exception as e: 70 | await _log.asyncLog("pin {!r} does not exist: {!s}".format(pin, e)) 71 | return False 72 | if msg != "": 73 | if msg in _mqtt.payload_on: 74 | value = 1 75 | elif msg in _mqtt.payload_off: 76 | value = 0 77 | else: 78 | try: 79 | value = int(msg) 80 | except ValueError: 81 | value = None 82 | if value is None: 83 | await _log.logAsync("error", 84 | "pin {!r} got no supported value {!r}".format(pin, msg)) 85 | return False 86 | Pin(pin, machine.Pin.OUT).value(value) 87 | return True 88 | else: 89 | return Pin(pin, machine.Pin.IN).value() 90 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/i2c.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-10-28 4 | 5 | COMPONENT_NAME = "I2C" 6 | 7 | """ 8 | example config: 9 | { 10 | package: .machine.i2c 11 | component: I2C 12 | constructor_args: { 13 | SCL: D4 14 | SDA: 4 15 | #FREQ: 100000 #optional, defaults to 100000 16 | } 17 | } 18 | 19 | """ 20 | 21 | __updated__ = "2018-08-18" 22 | __version__ = "0.4" 23 | 24 | import gc 25 | 26 | """ 27 | Easy I2C-creation 28 | """ 29 | 30 | 31 | def I2C(SCL, SDA, FREQ=100000): 32 | from machine import I2C 33 | from pysmartnode.components.machine.pin import Pin 34 | i2c = I2C(scl=Pin(SCL), sda=Pin(SDA), freq=FREQ) 35 | gc.collect() 36 | return i2c 37 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/pin.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2018-2020 Released under the MIT license 3 | # Created on 2018-08-17 4 | 5 | """ 6 | Unified Pin creation utility. Accepts pin number, name (for esp8266 NodeMCU) and Pin-Object. 7 | This enables developers to use this object instead of checking if they received a pin number, string or object 8 | and even set some default parameters like pull_up as these won't get used if a Pin-Object is received. 9 | But as the machine.Pin can't be subclassed, pull_up has to be used like this: 10 | from pysmartnode.utils.pin import Pin 11 | import machine 12 | 13 | p=Pin("D0", machine.Pin.OUT, machine.Pin.PULL_UP) 14 | """ 15 | 16 | __updated__ = "2019-04-04" 17 | __version__ = "0.3" 18 | 19 | import machine 20 | from sys import platform 21 | import gc 22 | 23 | 24 | def Pin(pin, *args, **kwargs): 25 | if type(pin) == machine.Pin: 26 | return pin 27 | if type(pin) == str: 28 | try: 29 | pin = int(pin) 30 | # just in case it gets a string that should be an integer 31 | except ValueError: 32 | pass 33 | if type(pin) == str: 34 | if platform == "esp8266": 35 | # generate dictionary on request so no RAM gets reserved all the time 36 | pins = {"D%i" % p: v for p, v in enumerate((16, 5, 4, 0, 2, 14, 12, 13, 15, 3, 1))} 37 | else: 38 | raise TypeError( 39 | "Platform {!s} does not support string pin names like {!s}".format(platform, pin)) 40 | if pin in pins: 41 | pin = pins[pin] 42 | else: 43 | raise TypeError( 44 | "Pin type {!s}, name {!r} not found in dictionary".format(type(pin), pin)) 45 | gc.collect() 46 | elif type(pin) != int: 47 | # assuming pin object 48 | # TODO: implement instance system like with ADC 49 | return pin 50 | args = list(args) 51 | args.insert(0, pin) 52 | return machine.Pin(*args, **kwargs) 53 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/remoteConfig.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-15 4 | 5 | __updated__ = "2020-08-11" 6 | __version__ = "0.93" 7 | 8 | from pysmartnode.utils.component import ComponentBase 9 | from pysmartnode import config 10 | from pysmartnode import logging 11 | import uasyncio as asyncio 12 | from sys import platform 13 | import gc 14 | import os 15 | 16 | COMPONENT_NAME = "remoteConfig" 17 | 18 | _mqtt = config.getMQTT() 19 | _log = logging.getLogger(COMPONENT_NAME) 20 | 21 | # SPIRAM is very slow when importing modules 22 | WAIT = 1.5 if platform == "esp8266" else ( 23 | 0.5 if os.uname() == "posix" or "(spiram)" not in os.uname().machine else 3) 24 | 25 | 26 | class RemoteConfig(ComponentBase): 27 | def __init__(self, **kwargs): 28 | super().__init__(COMPONENT_NAME, __version__, unit_index=0, logger=_log, **kwargs) 29 | self._topic = "{!s}/login/{!s}/#".format(_mqtt.mqtt_home, _mqtt.client_id) 30 | self._icomp = None 31 | self._rcomp = [] 32 | self._done = False 33 | self._watcher_task = asyncio.create_task(self._watcher()) 34 | 35 | def done(self): 36 | return self._done 37 | 38 | async def _watcher(self): 39 | mqtt = _mqtt 40 | mqtt.subscribeSync(self._topic, self.on_message, self) 41 | try: 42 | while True: 43 | while mqtt.isconnected() is False: 44 | await asyncio.sleep(1) 45 | if await mqtt.awaitSubscriptionsDone(await_connection=False): 46 | _log.debug("waiting for config", local_only=True) 47 | await _mqtt.publish( 48 | "{!s}/login/{!s}/set".format(mqtt.mqtt_home, mqtt.client_id), 49 | [config.VERSION, platform, WAIT]) 50 | gc.collect() 51 | else: 52 | await asyncio.sleep(20) 53 | continue 54 | for _ in range(120): 55 | if mqtt.isconnected() is False: 56 | break 57 | await asyncio.sleep(1) 58 | # so that it can be cancelled properly every second 59 | except asyncio.CancelledError: 60 | if config.DEBUG is True: 61 | _log.debug("_watcher cancelled", local_only=True) 62 | except Exception as e: 63 | await _log.asyncLog("error", "Error watching remoteConfig:", e) 64 | finally: 65 | await mqtt.unsubscribe(self._topic, self) 66 | self._done = True 67 | 68 | def _saveComponent(self, name, data): 69 | pass 70 | # save if save is enabled 71 | 72 | async def on_message(self, topic, msg, retain): 73 | if retain is True: 74 | return False 75 | m = memoryview(topic) 76 | if m[-4:] == b"/set": 77 | return False 78 | if m == memoryview(self._topic)[:-2]: 79 | print("received amount", msg) 80 | self._icomp = int(msg) 81 | # no return so it can end if 0 components are expected 82 | elif self._icomp is None: 83 | await _log.asyncLog("error", "Need amount of components first") 84 | return False 85 | else: 86 | if type(msg) != dict: 87 | await _log.asyncLog("error", "Received config is no dict") 88 | return False 89 | name = topic[topic.rfind("/") + 1:] 90 | del topic 91 | gc.collect() 92 | _log.info("received config for component", name, ":", msg, local_only=True) 93 | if name in self._rcomp: 94 | # received config already, typically happens if process was 95 | # interrupted by network error 96 | return False 97 | self._rcomp.append(name) 98 | self._saveComponent(name, msg) 99 | await config.registerComponent(name, msg) 100 | if len(self._rcomp) == self._icomp: # received all components 101 | self._watcher_task.cancel() 102 | return False 103 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/stats.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-04-28 4 | 5 | # This component will be started automatically to provide basic device statistics. 6 | # You don't need to configure it to be active. 7 | 8 | __updated__ = "2020-04-03" 9 | __version__ = "1.71" 10 | 11 | import gc 12 | 13 | from pysmartnode import config 14 | import uasyncio as asyncio 15 | from pysmartnode.utils.component import ComponentBase 16 | import time 17 | from sys import platform 18 | from pysmartnode.utils import sys_vars 19 | 20 | try: 21 | import os 22 | except: 23 | import uos as os # unix port compatibility because of missing weaklinks 24 | 25 | if platform != "linux": 26 | import network 27 | 28 | gc.collect() 29 | 30 | #################### 31 | # choose a component name that will be used for logging (not in leightweight_log) and 32 | # a default mqtt topic that can be changed by received or local component configuration 33 | COMPONENT_NAME = "STATS" 34 | #################### 35 | 36 | _mqtt = config.getMQTT() 37 | 38 | STATE_TYPE = '"json_attributes_topic":"~",' \ 39 | '"unit_of_meas":"dBm",' \ 40 | '"val_tpl":"{{value_json.RSSI|int}}",' \ 41 | '"ic":"mdi:information-outline",' 42 | 43 | 44 | class STATS(ComponentBase): 45 | def __init__(self, **kwargs): 46 | super().__init__(COMPONENT_NAME, __version__, unit_index=0, **kwargs) 47 | self._interval = config.INTERVAL_SENSOR_PUBLISH 48 | self._last_boot = None 49 | 50 | async def _init_network(self): 51 | await super()._init_network() 52 | await self._publish() 53 | asyncio.create_task(self._loop()) 54 | # start loop once network is completely set up because it doesn't have 55 | # any use otherwise because component only publishes stats. 56 | 57 | async def _publish(self): 58 | val = {} 59 | if platform != "linux": 60 | sta = network.WLAN(network.STA_IF) 61 | else: 62 | sta = None 63 | val["Pysmartnode version"] = config.VERSION 64 | if config.RTC_SYNC_ACTIVE is True: 65 | if self._last_boot is None: 66 | for _ in range(5): 67 | if time.localtime()[0] == 2000: # not synced 68 | await asyncio.sleep(1) 69 | t = time.time() # polling earlier might not have synced. 70 | s = round(time.ticks_ms() / 1000) 71 | self._last_boot = time.localtime(t - s) # last real boot/reset, not soft reset 72 | t = self._last_boot 73 | val["Last boot"] = "{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(t[0], t[1], t[2], 74 | t[3], t[4], t[5]) 75 | s = int(time.time() - time.mktime(t)) 76 | else: 77 | s = int(time.ticks_ms() / 1000 + 0.5) 78 | # approximate uptime depending on accuracy of ticks_ms() 79 | m, s = divmod(s, 60) 80 | h, m = divmod(m, 60) 81 | d, h = divmod(h, 24) 82 | val["Uptime"] = '{:d}T{:02d}:{:02d}:{:02d}'.format(d, h, m, s) 83 | self._log.info(gc.mem_free(), local_only=True) 84 | val["RAM free (bytes)"] = gc.mem_free() 85 | if sta is not None: 86 | try: 87 | val["RSSI"] = sta.status("rssi") 88 | except Exception as e: 89 | val["RSSI"] = 0 # platform doesn't support reading rssi 90 | print(e) 91 | try: 92 | val["IPAddress"] = sta.ifconfig()[0] 93 | except Exception as e: 94 | print(e) 95 | pass 96 | else: 97 | val["RSSI"] = 0 # can't read rssi on unix port, might not even be connected by WLAN 98 | try: 99 | val["Micropython version"] = os.uname().version 100 | except: 101 | pass 102 | s = int(_mqtt.getDowntime()) 103 | m, s = divmod(s, 60) 104 | h, m = divmod(m, 60) 105 | d, h = divmod(h, 24) 106 | val["MQTT Downtime"] = '{:d}T{:02d}:{:02d}:{:02d}'.format(d, h, m, s) 107 | val["MQTT Reconnects"] = _mqtt.getReconnects() 108 | val["MQTT Subscriptions"] = _mqtt.getLenSubscribtions() 109 | if config.DEBUG: 110 | # only needed for debugging and could be understood wrongly otherwise 111 | val["MQTT TimedOutOps"] = _mqtt.getTimedOutOperations() 112 | val["MQTT Repubs"] = _mqtt.REPUB_COUNT 113 | await _mqtt.publish(_mqtt.getDeviceTopic("status"), val, qos=1, retain=False, timeout=5) 114 | del val 115 | gc.collect() 116 | if config.DEBUG: 117 | # DEBUG to check RAM/Heap fragmentation 118 | import micropython 119 | micropython.mem_info(1) 120 | 121 | async def _loop(self): 122 | await asyncio.sleep(20) 123 | while True: 124 | gc.collect() 125 | await self._publish() 126 | await asyncio.sleep(self._interval) 127 | 128 | async def _discovery(self, register=True): 129 | topic = _mqtt.getRealTopic(_mqtt.getDeviceTopic("status")) 130 | if register: 131 | await self._publishDiscovery("sensor", topic, "status", STATE_TYPE, 132 | "Status {!s}".format( 133 | config.DEVICE_NAME or sys_vars.getDeviceID())) 134 | else: 135 | await self._deleteDiscovery("sensor", "status") 136 | gc.collect() 137 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/watchdog.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2018-2020 Released under the MIT license 3 | # Created on 2018-05-29 4 | 5 | """ 6 | example config: 7 | { 8 | package: .machine.watchdog 9 | component: WDT 10 | constructor_args: { 11 | id: 0 #optional, number of the timer to be used 12 | timeout: 120 #optional, defaults to 120s, resets machine after this 13 | } 14 | } 15 | """ 16 | 17 | __updated__ = "2020-04-03" 18 | __version__ = "1.3" 19 | 20 | import gc 21 | import uasyncio as asyncio 22 | import machine 23 | from sys import platform 24 | 25 | gc.collect() 26 | 27 | 28 | class WDT: 29 | def __init__(self, id=0, timeout=120): 30 | self._timeout = timeout / 10 31 | self._counter = 0 32 | self._timer = machine.Timer(id) 33 | self.init() 34 | asyncio.create_task(self._resetCounter()) 35 | """ Done in pysmartnode.main 36 | try: 37 | with open("reset_reason.txt", "r") as f: 38 | if f.read() == "True": 39 | logging.getLogger("WDT").warn("Reset reason: Watchdog") 40 | except Exception as e: 41 | print(e) # file probably just does not exist 42 | try: 43 | os.remove("reset_reason.txt") 44 | except Exception as e: 45 | logging.getLogger("WDT").error("Error saving to file: {!s}".format(e)) 46 | """ 47 | 48 | def _wdt(self, t): 49 | self._counter += self._timeout 50 | if self._counter >= self._timeout * 10: 51 | try: 52 | with open("reset_reason.txt", "w") as f: 53 | f.write("WDT reset") 54 | except Exception as e: 55 | print("Error saving to file: {!s}".format(e)) 56 | machine.reset() 57 | 58 | def feed(self): 59 | self._counter = 0 60 | 61 | def init(self, timeout=None): 62 | timeout = timeout or self._timeout 63 | self._timeout = timeout / 10 64 | self._timer.init(period=int(self._timeout * 1000), mode=machine.Timer.PERIODIC, 65 | callback=self._wdt) 66 | 67 | def deinit(self): # will not stop coroutine 68 | self._timer.deinit() 69 | 70 | async def _resetCounter(self): 71 | while True: 72 | await asyncio.sleep(self._timeout) 73 | self.feed() 74 | -------------------------------------------------------------------------------- /pysmartnode/components/machine/wifi_led.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-07 4 | 5 | """ 6 | Does not provide discovery as no sensor or switch, just a state/activity output of wifi status. 7 | Best to be activated in config.py so it can display the status before receving/loading any additional config. 8 | Therefore no example configuration given. 9 | """ 10 | 11 | __updated__ = "2020-03-29" 12 | __version__ = "1.5" 13 | 14 | import gc 15 | import machine 16 | from pysmartnode.components.machine.pin import Pin 17 | from pysmartnode.utils.component import ComponentBase 18 | import network 19 | import uasyncio as asyncio 20 | import time 21 | from pysmartnode import config 22 | 23 | gc.collect() 24 | 25 | COMPONENT_NAME = "WifiLED" 26 | 27 | 28 | # TODO: add option for heartbeat or always-on mode 29 | 30 | 31 | class WIFILED(ComponentBase): 32 | def __init__(self, pin, active_high=True, **kwargs): 33 | super().__init__(COMPONENT_NAME, __version__, discover=False, unit_index=0, **kwargs) 34 | self.pin = Pin(pin, machine.Pin.OUT, value=0 if active_high else 1) 35 | self._active_high = active_high 36 | self._next = [] 37 | asyncio.create_task(self._loop()) 38 | 39 | async def _loop(self): 40 | mqtt = config.getMQTT() 41 | mqtt.registerWifiCallback(self._wifiChanged) 42 | mqtt.registerConnectedCallback(self._reconnected) 43 | await self._flash(500, 1) 44 | sta = network.WLAN(network.STA_IF) 45 | st = time.ticks_ms() 46 | while True: 47 | while self._next: 48 | await self._flash(*self._next.pop(0)) 49 | await asyncio.sleep(1) 50 | if time.ticks_diff(time.ticks_ms(), st) > 60000: # heartbeat 51 | st = time.ticks_ms() 52 | if sta.isconnected(): 53 | await self._flash(20, 1) 54 | await asyncio.sleep_ms(250) 55 | await self._flash(20, 1) 56 | else: 57 | await self._flash(500, 3) 58 | await asyncio.sleep_ms(500) 59 | 60 | async def _flash(self, duration, iters): 61 | for _ in range(iters): 62 | self.pin.value(1 if self._active_high else 0) 63 | await asyncio.sleep_ms(duration) 64 | self.pin.value(0 if self._active_high else 1) 65 | await asyncio.sleep_ms(duration) 66 | 67 | async def _wifiChanged(self, state): 68 | if state is True: 69 | self._next.append((50, 2)) 70 | else: 71 | self._next.append((500, 3)) 72 | 73 | async def _reconnected(self, client): 74 | self._next.append((100, 5)) 75 | -------------------------------------------------------------------------------- /pysmartnode/components/multiplexer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/components/multiplexer/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/multiplexer/amux.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-08-09 4 | 5 | """ 6 | example config: 7 | { 8 | package: .multiplexer.amux, #analog multiplexer 9 | component: Amux 10 | constructor_args: { 11 | s0: D5 # muxPin object can be used if an amux is connected to a mux, then mux can be None 12 | s1: D2 # if all pins are connected to a mux then pin number can be used and mux object has to be given 13 | s2: D4 14 | #s3: null #optional, if not given a 8-bit amux is used 15 | mux: null #optional, if defined then all pins are interpreted as mux pins and have to be integer 16 | sig: 0 #not needed on esp8266 as it has only one adc anyway, can be an amux pin object (multiple amux) 17 | return_voltages: false #optional, bool, set true if standard read() should return voltage or false if raw value 18 | } 19 | } 20 | """ 21 | 22 | __updated__ = "2020-04-09" 23 | __version__ = "3.4" 24 | 25 | # Version 2.0 should support an Amux connected to an Amux, not tested though, only have one amux 26 | 27 | from pysmartnode.components.machine.adc import ADC as _ADC, pyADC as _pyADC 28 | import machine 29 | from pysmartnode.components.machine.pin import Pin 30 | import gc 31 | 32 | gc.collect() 33 | 34 | 35 | class Amux: 36 | def __init__(self, s0, s1, s2, s3=None, mux=None, adc=None, return_voltages=False): 37 | """ It is possibile to initialize with: 38 | - pin numbers (or string on esp8266) 39 | - mux object and pin numbers (of mux pins) 40 | - Pin objects (either from machine or mux Pin objects [no mux object needed], or Arduino) 41 | :type return_voltages: bool, True returns voltages on .read() else raw adc value 42 | :type mux: Mux object if a multiplexer is used 43 | :type adc: ADC pin number (esp32) or None (esp8266) or Arduino ADC object or any ADC object 44 | Amux uses default return values of ADC in .read() 45 | s3 is optional, only needed if 16 pins are used, 8 pins possible with s0-s2. 46 | Amux can be read like a list: value=amux[2] 47 | """ 48 | if mux: 49 | # MUX pin numbers, not pin objects 50 | self._s0 = s0 51 | self._s1 = s1 52 | self._s2 = s2 53 | self._s3 = s3 54 | self._mux = mux 55 | else: 56 | # Pin will take care of returning the correct object 57 | self._s0 = Pin(s0, machine.Pin.OUT) 58 | self._s1 = Pin(s1, machine.Pin.OUT) 59 | self._s2 = Pin(s2, machine.Pin.OUT) 60 | if s3: 61 | self._s3 = Pin(s3, machine.Pin.OUT) 62 | if s3: 63 | self.__size = 16 64 | else: 65 | self.__size = 8 66 | self._return_voltages = return_voltages 67 | self._adc = _ADC( 68 | adc) # no matter what adc is, _ADC will return an object with the unified ADC API 69 | 70 | def setReturnVoltages(self, vol): 71 | self._return_voltages = vol 72 | 73 | def __getitem__(self, a): 74 | return self.read(a) 75 | 76 | def getSize(self): 77 | """ Get number of pins""" 78 | return self.__size 79 | 80 | def read(self, a, return_voltage=None): 81 | if a >= self.__size: 82 | raise ValueError("Maximum Port number is {!s}".format(self.__size - 1)) 83 | if type(self._s0) == int: # mux pins 84 | if self.__size == 16: 85 | self._mux[self._s3] = (1 if a & 8 else 0) 86 | self._mux[self._s2] = (1 if a & 4 else 0) 87 | self._mux[self._s1] = (1 if a & 2 else 0) 88 | self._mux[self._s0] = (1 if a & 1 else 0) 89 | self._mux.write() 90 | else: 91 | if self.__size == 16: 92 | self._s3.value(1 if a & 8 else 0) 93 | self._s2.value(1 if a & 4 else 0) 94 | self._s1.value(1 if a & 2 else 0) 95 | self._s0.value(1 if a & 1 else 0) 96 | if return_voltage is True or return_voltage is None and self._return_voltages is True: 97 | return self._adc.readVoltage() 98 | else: 99 | return self._adc.read() 100 | 101 | def readVoltage(self, a): 102 | return self.read(a, return_voltage=True) 103 | 104 | def ADC(self, i, *args, **kwargs): 105 | """ compatible to machine.ADC, returns an ADC object""" 106 | return ADC(self, i) 107 | 108 | def atten(self, *args, **kwargs): 109 | self._adc.atten(*args, **kwargs) 110 | 111 | def width(self, *args, **kwargs): 112 | self._adc.width(*args, **kwargs) 113 | 114 | 115 | class ADC(_pyADC): 116 | def __init__(self, amux: Amux, pin): 117 | super().__init__() 118 | self.__amux = amux 119 | self.__pin = pin 120 | 121 | def read(self): 122 | return self.__amux.read(self.__pin) 123 | 124 | def readVoltage(self): 125 | return self.__amux.readVoltage(self.__pin) 126 | 127 | def __str__(self): 128 | return "amuxADC({!s})".format(self.__pin) 129 | 130 | # Careful using the methods below as they change values for all readings of the Amux 131 | 132 | def atten(self, *args, **kwargs): 133 | self.__amux.atten(*args, **kwargs) 134 | 135 | def width(self, *args, **kwargs): 136 | self.__amux.width(*args, **kwargs) 137 | -------------------------------------------------------------------------------- /pysmartnode/components/multiplexer/mux.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-08-09 4 | 5 | """ 6 | example config: 7 | { 8 | package: .multiplexer.mux, 9 | component: Mux, 10 | constructor_args: { 11 | shift_pin: D5 12 | store_pin: D2 13 | data_pin: D4 14 | #number_multiplexer: 2 #optional, defaults to 1, no limit 15 | } 16 | } 17 | """ 18 | 19 | __updated__ = "2019-11-02" 20 | __version__ = "0.8" 21 | 22 | import machine 23 | from pysmartnode.components.machine.pin import Pin as PyPin 24 | import gc 25 | 26 | gc.collect() 27 | 28 | 29 | class Mux: 30 | def __init__(self, shift_pin, store_pin, data_pin, number_multiplexer=1): 31 | self.shcp = PyPin(shift_pin, machine.Pin.OUT) 32 | self.stcp = PyPin(store_pin, machine.Pin.OUT) 33 | self.ds = PyPin(data_pin, machine.Pin.OUT) 34 | self.__data = bytearray(8 * number_multiplexer) 35 | self.__size = number_multiplexer 36 | self.write() 37 | 38 | def write(self): 39 | self.stcp.value(0) 40 | for i in range((8 * self.__size) - 1, -1, -1): 41 | self.shcp.value(0) 42 | self.ds.value(self.__data[i]) 43 | self.shcp.value(1) 44 | self.stcp.value(1) 45 | 46 | def __setitem__(self, a, b): 47 | if b != 1 and b != 0: 48 | raise ValueError("Value must be 1 or 0") 49 | self.__data[a] = b 50 | 51 | def __getitem__(self, a): 52 | return self.__data[a] 53 | 54 | def __delitem__(self, a): 55 | self.__data[a] = 0 56 | 57 | def set(self, i): 58 | self.__data[i] = 1 59 | 60 | def clear(self, i): 61 | self.__data[i] = 0 62 | 63 | def getSize(self): 64 | """ Get number of pins""" 65 | return self.__size * 8 66 | 67 | def Pin(self, i, *args, **kwargs): 68 | return Pin(self, i) 69 | 70 | 71 | class Pin: 72 | def __init__(self, mux, pin): 73 | self.__mux = mux 74 | self.__pin = pin 75 | 76 | def value(self, inp=None, wait=False): 77 | if inp is not None: 78 | if inp == 1: 79 | self.__mux.set(self.__pin) 80 | if not wait: 81 | self.__mux.write() 82 | elif inp > 1: 83 | raise ValueError("Value must be 1 or 0") 84 | else: 85 | self.__mux.clear(self.__pin) 86 | if not wait: 87 | self.__mux.write() 88 | else: 89 | return self.__mux[self.__pin] 90 | 91 | def __str__(self): 92 | return "muxPin({!s})".format(self.__pin) 93 | 94 | def on(self): 95 | self.__mux.value(self.__pin, 1) 96 | 97 | def off(self): 98 | self.__mux.value(self.__pin, 0) 99 | 100 | def __call__(self, x=None): 101 | return self.value(x) 102 | -------------------------------------------------------------------------------- /pysmartnode/components/sensors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/components/sensors/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/sensors/bell/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/components/sensors/bell/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/sensors/dht22.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2018-2020 Released under the MIT license 3 | # Created on 2018-06-25 4 | 5 | """ 6 | example config: 7 | { 8 | package: .sensors.dht22 9 | component: DHT22 10 | constructor_args: { 11 | pin: 4 #pin number or label (on NodeMCU) 12 | precision_temp: 2 #precision of the temperature value published 13 | precision_humid: 1 #precision of the humid value published 14 | offset_temp: 0 #offset for temperature to compensate bad sensor reading offsets 15 | offset_humid: 0 #... 16 | # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery 17 | } 18 | } 19 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 20 | """ 21 | 22 | __updated__ = "2020-03-29" 23 | __version__ = "1.2" 24 | 25 | from pysmartnode import config 26 | from pysmartnode import logging 27 | import uasyncio as asyncio 28 | from pysmartnode.components.machine.pin import Pin 29 | from pysmartnode.utils.component.sensor import ComponentSensor, SENSOR_TEMPERATURE, SENSOR_HUMIDITY 30 | import gc 31 | 32 | #################### 33 | # import your library here 34 | from dht import DHT22 as Sensor 35 | 36 | # choose a component name that will be used for logging (not in leightweight_log) and 37 | # a default mqtt topic that can be changed by received or local component configuration 38 | COMPONENT_NAME = "DHT22" 39 | # define (homeassistant) value templates for all sensor readings 40 | _VAL_T_TEMPERATURE = "{{ value_json.temperature }}" 41 | _VAL_T_HUMIDITY = "{{ value_json.humidity }}" 42 | #################### 43 | 44 | _log = logging.getLogger(COMPONENT_NAME) 45 | _mqtt = config.getMQTT() 46 | gc.collect() 47 | 48 | _unit_index = -1 49 | 50 | 51 | class DHT22(ComponentSensor): 52 | def __init__(self, pin, precision_temp=2, precision_humid=1, 53 | offset_temp=0, offset_humid=0, friendly_name_temp=None, friendly_name_humid=None, 54 | **kwargs): 55 | # This makes it possible to use multiple instances of MySensor and have unique identifier 56 | global _unit_index 57 | _unit_index += 1 58 | super().__init__(COMPONENT_NAME, __version__, _unit_index, logger=_log, **kwargs) 59 | self._addSensorType(SENSOR_TEMPERATURE, precision_temp, offset_temp, _VAL_T_TEMPERATURE, 60 | "°C", friendly_name_temp) 61 | self._addSensorType(SENSOR_HUMIDITY, precision_humid, offset_humid, _VAL_T_HUMIDITY, "%", 62 | friendly_name_humid) 63 | ############################## 64 | # create sensor object 65 | self.sensor = Sensor(Pin(pin)) # add neccessary constructor arguments here 66 | ############################## 67 | gc.collect() 68 | 69 | async def _read(self): 70 | try: 71 | self.sensor.measure() 72 | await asyncio.sleep(1) 73 | self.sensor.measure() 74 | except Exception as e: 75 | await _log.asyncLog("error", "DHT22 is not working,", e, timeout=10) 76 | return None, None 77 | await asyncio.sleep_ms(100) # give other tasks some time as measure() is slow and blocking 78 | try: 79 | temp = self.sensor.temperature() 80 | humid = self.sensor.humidity() 81 | except Exception as e: 82 | await _log.asyncLog("error", "Error reading DHT22:", e, timeout=10) 83 | else: 84 | await self._setValue(SENSOR_TEMPERATURE, temp) 85 | await self._setValue(SENSOR_HUMIDITY, humid) 86 | -------------------------------------------------------------------------------- /pysmartnode/components/sensors/htu21d.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-10-28 4 | 5 | """ 6 | example config: 7 | { 8 | package: .sensors.htu21d 9 | component: HTU21D 10 | constructor_args: { 11 | i2c: i2c # i2c object created before 12 | precision_temp: 2 # precision of the temperature value published 13 | precision_humid: 1 # precision of the humid value published 14 | temp_offset: 0 # offset for temperature to compensate bad sensor reading offsets 15 | humid_offset: 0 # ... 16 | # friendly_name_temp: null # optional, friendly name shown in homeassistant gui with mqtt discovery 17 | # friendly_name_humid: null # optional, friendly name shown in homeassistant gui with mqtt discovery 18 | } 19 | } 20 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 21 | """ 22 | 23 | __updated__ = "2020-03-29" 24 | __version__ = "3.2" 25 | 26 | import gc 27 | import uasyncio as asyncio 28 | from pysmartnode import config 29 | from pysmartnode import logging 30 | from pysmartnode.utils.component.sensor import ComponentSensor, SENSOR_TEMPERATURE, SENSOR_HUMIDITY 31 | 32 | #################### 33 | # choose a component name that will be used for logging (not in leightweight_log) and 34 | # a default mqtt topic that can be changed by received or local component configuration 35 | COMPONENT_NAME = "HTU" 36 | # define (homeassistant) value templates for all sensor readings 37 | _VAL_T_TEMPERATURE = "{{ value_json.temperature }}" 38 | _VAL_T_HUMIDITY = "{{ value_json.humidity }}" 39 | #################### 40 | 41 | _mqtt = config.getMQTT() 42 | _log = logging.getLogger(COMPONENT_NAME) 43 | gc.collect() 44 | 45 | _unit_index = -1 46 | _ADDRESS = 0x40 47 | _ISSUE_TEMP_ADDRESS = 0xE3 48 | _ISSUE_HU_ADDRESS = 0xE5 49 | 50 | 51 | class HTU21D(ComponentSensor): 52 | def __init__(self, i2c, precision_temp: int = 2, precision_humid: int = 2, 53 | temp_offset: float = 0, humid_offset: float = 0, 54 | friendly_name_temp=None, friendly_name_humid=None, **kwargs): 55 | # This makes it possible to use multiple instances of MySensor and have unique identifier 56 | global _unit_index 57 | _unit_index += 1 58 | super().__init__(COMPONENT_NAME, __version__, _unit_index, logger=_log, **kwargs) 59 | # discover: boolean, if this component should publish its mqtt discovery. 60 | # This can be used to prevent combined Components from exposing underlying 61 | # hardware components like a power switch 62 | self.i2c = i2c 63 | self._addSensorType(SENSOR_TEMPERATURE, precision_temp, temp_offset, _VAL_T_TEMPERATURE, 64 | "°C", friendly_name_temp) 65 | self._addSensorType(SENSOR_HUMIDITY, precision_humid, humid_offset, _VAL_T_HUMIDITY, "%", 66 | friendly_name_humid) 67 | 68 | gc.collect() 69 | ############################## 70 | 71 | async def _read(self): 72 | raw = await self._issue_measurement_async(_ISSUE_TEMP_ADDRESS) 73 | if raw is not None: 74 | await self._setValue(SENSOR_TEMPERATURE, -46.85 + (175.72 * raw / 65536)) 75 | raw = await self._issue_measurement_async(_ISSUE_HU_ADDRESS) 76 | if raw is not None: 77 | await self._setValue(SENSOR_HUMIDITY, -6 + (125.0 * raw / 65536)) 78 | 79 | async def _issue_measurement_async(self, write_address): 80 | try: 81 | # self.i2c.start() 82 | self.i2c.writeto_mem(int(_ADDRESS), int(write_address), '') 83 | # self.i2c.stop() 84 | data = bytearray(3) 85 | except Exception as e: 86 | await self._log.asyncLog("error", "Error reading sensor:", e, timeout=10) 87 | return None 88 | await asyncio.sleep_ms(50) 89 | try: 90 | self.i2c.readfrom_into(_ADDRESS, data) 91 | remainder = ((data[0] << 8) + data[1]) << 8 92 | remainder |= data[2] 93 | divsor = 0x988000 94 | for i in range(0, 16): 95 | if remainder & 1 << (23 - i): 96 | remainder ^= divsor 97 | divsor >>= 1 98 | if remainder: 99 | await self._log.asyncLog("error", "Checksum error", timeout=10) 100 | return None 101 | raw = (data[0] << 8) + data[1] 102 | raw &= 0xFFFC 103 | return raw 104 | except Exception as e: 105 | await self._log.asyncLog("error", "Error reading sensor:", e, timeout=10) 106 | return None 107 | -------------------------------------------------------------------------------- /pysmartnode/components/sensors/waterSensor.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-04-10 4 | 5 | """ 6 | Simple water sensor using 2 wires in water. As soon as some conductivity is possible, the sensor will hit. 7 | 8 | { 9 | package: .sensors.waterSensor 10 | component: WaterSensor 11 | constructor_args: { 12 | adc: 33 13 | power_pin: 5 # optional if connected to permanent power 14 | # interval_reading: 1 # optional, interval in seconds that the sensor gets polled 15 | # cutoff_voltage: 3.3 # optional, defaults to ADC maxVoltage (on ESP 3.3V). Above this voltage means dry 16 | # mqtt_topic: "sometopic" # optional, defaults to home//waterSensor/ 17 | # friendly_name: null # optional, friendly name for the homeassistant gui 18 | # discover: true # optional, if false no discovery message for homeassistant will be sent. 19 | # expose_intervals: Expose intervals to mqtt so they can be changed remotely 20 | # intervals_topic: if expose_intervals then use this topic to change intervals. Defaults to //<_unit_index>/interval/set. Send a dictionary with keys "reading" and/or "publish" to change either/both intervals. 21 | } 22 | } 23 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 24 | Will publish on any state change. State changes are detected in the interval_reading. 25 | Only the polling interval of the first initialized sensor is used. 26 | The publish interval is unique to each sensor. 27 | This is to use only one uasyncio task for all sensors (because old uasyncio would overflow). 28 | 29 | ** How to connect: 30 | Put a Resistor (~10kR) between the power pin (or permanent power) and the adc pin. 31 | Connect the wires to the adc pin and gnd. 32 | """ 33 | 34 | __updated__ = "2020-04-03" 35 | __version__ = "1.8" 36 | 37 | from pysmartnode import config 38 | from pysmartnode import logging 39 | from pysmartnode.components.machine.adc import ADC 40 | from pysmartnode.components.machine.pin import Pin 41 | import uasyncio as asyncio 42 | import gc 43 | import machine 44 | import time 45 | from pysmartnode.utils.component.sensor import ComponentSensor, SENSOR_BINARY_MOISTURE, \ 46 | VALUE_TEMPLATE 47 | 48 | COMPONENT_NAME = "WaterSensor" 49 | _unit_index = -1 50 | 51 | _log = logging.getLogger(COMPONENT_NAME) 52 | _mqtt = config.getMQTT() 53 | gc.collect() 54 | 55 | 56 | class WaterSensor(ComponentSensor): 57 | DEBUG = False 58 | 59 | def __init__(self, adc, power_pin=None, cutoff_voltage=None, 60 | interval_reading=1, friendly_name=None, **kwargs): 61 | global _unit_index 62 | _unit_index += 1 63 | super().__init__(COMPONENT_NAME, __version__, _unit_index, logger=_log, 64 | interval_reading=interval_reading, interval_publish=-1, 65 | **kwargs) 66 | self._adc = ADC(adc) 67 | self._ppin = Pin(power_pin, machine.Pin.OUT) if power_pin is not None else None 68 | self._cv = cutoff_voltage or self._adc.maxVoltage() 69 | self._lv = None 70 | self._addSensorType(SENSOR_BINARY_MOISTURE, 0, 0, VALUE_TEMPLATE, "", friendly_name, 71 | self._topic, None, True) 72 | self._pub_task = None 73 | 74 | async def _read(self): 75 | a = time.ticks_us() 76 | p = self._ppin 77 | if p is not None: 78 | p.value(1) 79 | vol = self._adc.readVoltage() 80 | if self.DEBUG is True: 81 | print("#{!s}, V".format(self.getTopic(SENSOR_BINARY_MOISTURE)[-1]), vol) 82 | if p is not None: 83 | p.value(0) 84 | if vol >= self._cv: 85 | state = False 86 | if self._lv != state: 87 | # dry 88 | if self._pub_task is not None: 89 | self._pub_task.cancel() 90 | self._pub_task = asyncio.create_task( 91 | _mqtt.publish(self.getTopic(SENSOR_BINARY_MOISTURE), "OFF", qos=1, 92 | retain=True, timeout=None, await_connection=True)) 93 | 94 | self._lv = state 95 | else: 96 | state = True 97 | if self._lv != state: 98 | # wet 99 | if self._pub_task is not None: 100 | self._pub_task.cancel() 101 | self._pub_task = asyncio.create_task( 102 | _mqtt.publish(self.getTopic(SENSOR_BINARY_MOISTURE), "ON", qos=1, 103 | retain=True, timeout=None, await_connection=True)) 104 | self._lv = state 105 | b = time.ticks_us() 106 | if WaterSensor.DEBUG: 107 | print("Water measurement took", (b - a) / 1000, "ms") 108 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/components/switches/__init__.py -------------------------------------------------------------------------------- /pysmartnode/components/switches/buzzer.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-10-31 4 | 5 | """ 6 | example config: 7 | { 8 | package: .switches.buzzer, 9 | component: Buzzer, 10 | constructor_args: { 11 | pin: D5, 12 | pwm_values: [512,819,1020,786] #list of pwm dutys, on esp32 use percentage, max is 1024 on esp8266 13 | # on_time: 500 #optional, defaults to 500ms, time buzzer stays at one pwm duty 14 | # iters: 1 #optional, iterations done, defaults to 1 15 | # freq: 1000 #optional, defaults to 1000 16 | } 17 | } 18 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 19 | """ 20 | 21 | __updated__ = "2020-04-03" 22 | __version__ = "3.31" 23 | 24 | import gc 25 | from machine import Pin, PWM 26 | from pysmartnode.components.machine.pin import Pin as PyPin 27 | from pysmartnode import config 28 | import uasyncio as asyncio 29 | from pysmartnode.utils.component.button import ComponentButton 30 | 31 | _mqtt = config.getMQTT() 32 | 33 | #################### 34 | # choose a component name that will be used for logging (not in leightweight_log), 35 | # a default mqtt topic that can be changed by received or local component configuration 36 | # as well as for the component name in homeassistant. 37 | COMPONENT_NAME = "Buzzer" 38 | #################### 39 | 40 | 41 | gc.collect() 42 | 43 | _unit_index = -1 44 | 45 | 46 | class Buzzer(ComponentButton): 47 | def __init__(self, pin, pwm_values, on_time=500, iters=1, freq=1000, **kwargs): 48 | self.pin = PyPin(pin, Pin.OUT) 49 | self.on_time = on_time 50 | self.values = pwm_values 51 | self.iters = iters 52 | self.pin = PWM(self.pin, freq=freq) 53 | self.pin.duty(0) 54 | # This makes it possible to use multiple instances of Buzzer 55 | global _unit_index 56 | _unit_index += 1 57 | super().__init__(COMPONENT_NAME, __version__, _unit_index, **kwargs) 58 | gc.collect() 59 | 60 | async def _on(self): 61 | for _ in range(self.iters): 62 | for duty in self.values: 63 | self.pin.duty(duty) 64 | await asyncio.sleep_ms(self.on_time) 65 | self.pin.duty(0) 66 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/generic_switch.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2020 Released under the MIT license 3 | # Created on 2020-03-04 4 | 5 | """ 6 | example config: 7 | { 8 | package: .switches.generic_switch 9 | component: GenSwitch 10 | constructor_args: {} 11 | } 12 | This generic switch does absolutely nothing except publishing its state and receiving state changes. 13 | Can be used to represent (for example) the long-press state of a physical button. 14 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 15 | """ 16 | 17 | __updated__ = "2020-04-03" 18 | __version__ = "1.11" 19 | 20 | from pysmartnode import config 21 | from pysmartnode.utils.component.switch import ComponentSwitch 22 | 23 | COMPONENT_NAME = "GenericSwitch" 24 | 25 | _mqtt = config.getMQTT() 26 | _unit_index = -1 27 | 28 | 29 | class GenSwitch(ComponentSwitch): 30 | def __init__(self, **kwargs): 31 | global _unit_index 32 | _unit_index += 1 33 | initial_state = False 34 | super().__init__(COMPONENT_NAME, __version__, _unit_index, wait_for_lock=True, 35 | initial_state=initial_state, **kwargs) 36 | 37 | @staticmethod 38 | async def _on(): 39 | """Turn device on.""" 40 | return True # return True when turning device on was successful. 41 | 42 | @staticmethod 43 | async def _off(): 44 | """Turn device off. """ 45 | return True # return True when turning device off was successful. 46 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/gpio.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-10-30 4 | 5 | """ 6 | example config: 7 | { 8 | package: .switches.gpio 9 | component: GPIO 10 | constructor_args: { 11 | pin: D5 12 | active_high: true #optional, defaults to active high 13 | # mqtt_topic: sometopic #optional, topic needs to have /set at the end, defaults to //GPIO/ 14 | # instance_name: name #optional, name of the gpio instance, will be generated automatically 15 | } 16 | } 17 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 18 | """ 19 | 20 | __updated__ = "2020-04-03" 21 | __version__ = "1.11" 22 | 23 | import gc 24 | import machine 25 | from pysmartnode.components.machine.pin import Pin 26 | from pysmartnode import config 27 | from pysmartnode.utils.component.switch import ComponentSwitch 28 | 29 | _mqtt = config.getMQTT() 30 | 31 | COMPONENT_NAME = "GPIO" 32 | _COMPONENT_TYPE = "switch" 33 | _unit_index = -1 34 | 35 | gc.collect() 36 | 37 | 38 | class GPIO(ComponentSwitch): 39 | def __init__(self, pin, active_high=True, mqtt_topic=None, instance_name=None, **kwargs): 40 | mqtt_topic = mqtt_topic or _mqtt.getDeviceTopic( 41 | "{!s}/{!s}".format(COMPONENT_NAME, str(pin)), is_request=True) 42 | global _unit_index 43 | _unit_index += 1 44 | super().__init__(COMPONENT_NAME, __version__, _unit_index, mqtt_topic=mqtt_topic, 45 | instance_name=instance_name or "{!s}_{!s}".format(COMPONENT_NAME, pin), 46 | **kwargs) 47 | self.pin = Pin(pin, machine.Pin.OUT, value=0 if active_high else 1) 48 | self._state = not active_high 49 | self._active_high = active_high 50 | 51 | async def _on(self): 52 | self.pin.value(1 if self._active_high else 0) 53 | return True 54 | 55 | async def _off(self): 56 | self.pin.value(0 if self._active_high else 1) 57 | return True 58 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/led.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-10-28 4 | 5 | """ 6 | example config: 7 | { 8 | package: .switches.led 9 | component: LEDNotification 10 | constructor_args: { 11 | pin: D5 12 | #on_time: 50 #optional, time led is on, defaults to 50ms 13 | #off_time: 50 #optional, time led is off, defaults to 50ms 14 | #iters: 20 #optional, iterations done, defaults to 20 15 | } 16 | } 17 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 18 | """ 19 | 20 | __updated__ = "2020-04-03" 21 | __version__ = "3.31" 22 | 23 | import gc 24 | 25 | import machine 26 | from pysmartnode.components.machine.pin import Pin 27 | from pysmartnode import config 28 | import uasyncio as asyncio 29 | from pysmartnode.utils.component.button import ComponentButton 30 | 31 | _mqtt = config.getMQTT() 32 | 33 | #################### 34 | # choose a component name that will be used for logging (not in leightweight_log), 35 | # a default mqtt topic that can be changed by received or local component configuration 36 | # as well as for the component name in homeassistant. 37 | COMPONENT_NAME = "LEDNotification" 38 | #################### 39 | 40 | gc.collect() 41 | 42 | _unit_index = -1 43 | 44 | 45 | class LEDNotification(ComponentButton): 46 | def __init__(self, pin, on_time=50, off_time=50, iters=20, **kwargs): 47 | self.pin = Pin(pin, machine.Pin.OUT, value=0) 48 | self.on_time = on_time 49 | self.off_time = off_time 50 | self.iters = iters 51 | # This makes it possible to use multiple instances of LED 52 | global _unit_index 53 | _unit_index += 1 54 | super().__init__(COMPONENT_NAME, __version__, _unit_index, **kwargs) 55 | gc.collect() 56 | 57 | async def _on(self): 58 | for _ in range(self.iters): 59 | self.pin.value(1) 60 | await asyncio.sleep_ms(self.on_time) 61 | self.pin.value(0) 62 | await asyncio.sleep_ms(self.off_time) 63 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/remote433mhz.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2020 Released under the MIT license 3 | # Created on 2020-03-31 4 | 5 | """ 6 | example config: 7 | { 8 | package: .switches.433mhz 9 | component: Switch433Mhz 10 | constructor_args: { 11 | pin: 21 # pin number or object 12 | file: "filename" # filename where the captured sequences are stored. Has to be uploaded manually! 13 | name_on: "on_a" # name of the sequence for turning the device on 14 | name_off: "off_a" # name of the sequence for turning the device off 15 | # reps: 5 # optional, amount of times a frame is being sent 16 | } 17 | } 18 | Control 433Mhz devices (e.g. power sockets) with a cheap 433Mhz transmitter. 19 | Uses the excellent library from Peter Hinch: https://github.com/peterhinch/micropython_remote 20 | For this to work you need to have sequences captured and stores on the device. 21 | How to do that is described in his repository. 22 | Note: This component only works on the devices supported by Peter Hinch's library! 23 | (esp32, pyboards but not esp8266). 24 | Be careful with "reps", the amount of repitions as this currently uses a lot of RAM. 25 | 26 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 27 | """ 28 | 29 | __updated__ = "2020-04-03" 30 | __version__ = "0.2" 31 | 32 | from pysmartnode import config 33 | from pysmartnode.utils.component.switch import ComponentSwitch 34 | from pysmartnode.libraries.micropython_remote.tx import TX 35 | from pysmartnode.components.machine.pin import Pin 36 | import json 37 | import uasyncio as asyncio 38 | import machine 39 | 40 | #################### 41 | COMPONENT_NAME = "433MhzRemote" 42 | #################### 43 | 44 | _mqtt = config.getMQTT() 45 | _unit_index = -1 46 | _tx: TX = None 47 | _remotes = {} 48 | _lock = asyncio.Lock() 49 | 50 | 51 | class Switch433Mhz(ComponentSwitch): 52 | def __init__(self, pin, file: str, name_on: str, name_off: str, reps: int = 5, **kwargs): 53 | global _unit_index 54 | _unit_index += 1 55 | global _tx 56 | if file not in _remotes and _tx is None: 57 | pin = Pin(pin, machine.Pin.OUT) 58 | _tx = TX(pin, file, reps) 59 | _remotes[file] = _tx._data 60 | elif file not in _remotes: 61 | with open(file, 'r') as f: 62 | rem = json.load(f) 63 | # exceptions are forwarded to the caller 64 | _remotes[file] = rem 65 | if name_on not in _remotes[file]: 66 | raise AttributeError("name_on {!r} not in file {!s}".format(name_on, file)) 67 | if name_off not in _remotes[file]: 68 | raise AttributeError("name_off {!r} not in file {!s}".format(name_off, file)) 69 | 70 | super().__init__(COMPONENT_NAME, __version__, _unit_index, wait_for_lock=True, 71 | initial_state=None, **kwargs) 72 | # Unknown initial state. Should be sorted by retained state topic 73 | 74 | self._reps = reps 75 | self._file = file 76 | self._len_on = int(sum(_remotes[self._file][name_on]) * 1.1 / 1000) 77 | self._len_off = int(sum(_remotes[self._file][name_off]) * 1.1 / 1000) 78 | self._name_on = name_on 79 | self._name_off = name_off 80 | 81 | # one lock for all switches, overrides lock created by the base class 82 | self._lock = _lock 83 | 84 | ##################### 85 | # Change these methods according to your device. 86 | ##################### 87 | async def _on(self): 88 | """Turn device on.""" 89 | _tx._data = _remotes[self._file] 90 | reps = _tx._reps 91 | _tx._reps = self._reps 92 | _tx(self._name_on) 93 | await asyncio.sleep_ms(self._len_on * self._reps) 94 | _tx._reps = reps 95 | # wait until transmission is done so lock only gets released afterwards because 96 | # only one transmission can occur at a time. 97 | return True 98 | 99 | async def _off(self): 100 | """Turn device off. """ 101 | _tx._data = _remotes[self._file] 102 | reps = _tx._reps 103 | _tx._reps = self._reps 104 | _tx(self._name_off) 105 | await asyncio.sleep_ms(self._len_off * self._reps) 106 | _tx._reps = reps 107 | # wait until transmission is done so lock only gets released afterwards because 108 | # only one transmission can occur at a time. 109 | return True 110 | ##################### 111 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/remoteSwitch.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-10-11 4 | 5 | """ 6 | example config: 7 | { 8 | package: .sensors.remoteSensors.switch 9 | component: RemoteSwitch 10 | constructor_args: { 11 | command_topic: sometopic # command topic of the remote sensor 12 | state_topic: sometopic # state topic of the remote sensor 13 | # timeout: 10 # optional, defaults to 10s, timeout for receiving an answer 14 | } 15 | } 16 | NOTE: additional constructor arguments are available from base classes, check COMPONENTS.md! 17 | """ 18 | 19 | # TODO: implement possibility to set sensor topics through mqtt, similar to RemoteSensor implementation 20 | # TODO: make a real ComponentSwitch class so type checks won't fail 21 | 22 | __updated__ = "2020-03-29" 23 | __version__ = "0.3" 24 | 25 | COMPONENT_NAME = "RemoteSwitch" 26 | 27 | from pysmartnode.utils.component import ComponentBase 28 | from pysmartnode import config 29 | import uasyncio as asyncio 30 | import time 31 | from micropython import const 32 | 33 | _mqtt = config.getMQTT() 34 | _TIMEOUT = const(10) # wait for a single reconnect but should be short enough if not connected 35 | _unit_index = -1 36 | 37 | 38 | class RemoteSwitch(ComponentBase): 39 | """ 40 | Generic Switch class. 41 | Use it according to the template. 42 | """ 43 | 44 | def __init__(self, command_topic, state_topic, timeout=_TIMEOUT, **kwargs): 45 | global _unit_index 46 | _unit_index += 1 47 | super().__init__(COMPONENT_NAME, __version__, _unit_index, discover=False, **kwargs) 48 | self._state = False 49 | self._topic = command_topic 50 | self._state_topic = state_topic 51 | self.lock = asyncio.Lock() 52 | # in case switch activates a device that will need a while to finish 53 | self._state_time = 0 54 | self._timeout = timeout 55 | _mqtt.subscribeSync(self._state_topic, self.on_message, self) 56 | 57 | async def on_message(self, topic, msg, retain): 58 | """ 59 | Standard callback to change the device state from mqtt. 60 | Can be subclassed if extended functionality is needed. 61 | """ 62 | if msg in _mqtt.payload_on: 63 | self._state = True 64 | self._state_time = time.ticks_ms() 65 | elif msg in _mqtt.payload_off: 66 | self._state = False 67 | self._state_time = time.ticks_ms() 68 | else: 69 | raise TypeError("Payload {!s} not supported".format(msg)) 70 | return False # will not publish the requested state to mqtt as already done by on()/off() 71 | 72 | async def on(self): 73 | """Turn switch on. Can be used by other components to control this component""" 74 | async with self.lock: 75 | t = time.ticks_ms() 76 | await _mqtt.publish(self._topic, "ON", qos=1, timeout=self._timeout) 77 | while time.ticks_diff(time.ticks_ms(), t) < self._timeout * 1000: 78 | if t < self._state_time: # received new state 79 | return self._state 80 | return False # timeout reached 81 | 82 | async def off(self): 83 | """Turn switch off. Can be used by other components to control this component""" 84 | async with self.lock: 85 | t = time.ticks_ms() 86 | await _mqtt.publish(self._topic, "OFF", qos=1, timeout=self._timeout) 87 | while time.ticks_diff(time.ticks_ms(), t) < self._timeout * 1000: 88 | if t < self._state_time: # received new state 89 | return True if self._state is False else False 90 | return False # timeout reached 91 | 92 | async def toggle(self): 93 | """Toggle device state. Can be used by other component to control this component""" 94 | if self._state is True: 95 | return await self.off() 96 | else: 97 | return await self.on() 98 | 99 | def state(self): 100 | return self._state 101 | 102 | def topic(self): 103 | return self._topic 104 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/switch_extension/repeating.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-28 4 | 5 | __updated__ = "2019-11-15" 6 | __version__ = "0.4" 7 | 8 | from pysmartnode.components.switches.switch_extension import Switch, ComponentSwitch, _mqtt, \ 9 | COMPONENT_NAME, BaseMode 10 | import uasyncio as asyncio 11 | import time 12 | 13 | 14 | class repeating(BaseMode): 15 | """ 16 | Shut down device after configured amount of time 17 | """ 18 | 19 | def __init__(self, extended_switch: Switch, component: ComponentSwitch, component_on, 20 | component_off): 21 | self._on_time = 30 # default value to be adapted by mqtt 22 | self._off_time = 30 # default value to be adapted by mqtt 23 | count = component._count if hasattr(component, "_count") else "" 24 | _name = component._name if hasattr(component, "_name") else "{!s}{!s}".format( 25 | COMPONENT_NAME, count) 26 | topic = _mqtt.getDeviceTopic("{!s}/repeating/on_time".format(_name), is_request=True) 27 | _mqtt.subscribeSync(topic, self._changeOnTime, extended_switch, check_retained_state=True) 28 | topic2 = _mqtt.getDeviceTopic("{!s}/repeating/off_time".format(_name), is_request=True) 29 | _mqtt.subscribeSync(topic2, self._changeOffTime, extended_switch, 30 | check_retained_state=True) 31 | self._task = None 32 | 33 | async def _changeOnTime(self, topic, msg, retain): 34 | self._on_time = float(msg) 35 | return True 36 | 37 | async def _changeOffTime(self, topic, msg, retain): 38 | self._off_time = float(msg) 39 | return True 40 | 41 | async def _repeating(self, component_on, component_off): 42 | print("repeating started") 43 | try: 44 | while True: 45 | st = time.ticks_ms() 46 | await component_on() 47 | while time.ticks_diff(time.ticks_ms(), st) < self._on_time * 1000: 48 | await asyncio.sleep(0.2) 49 | await component_off() 50 | st = time.ticks_ms() 51 | while time.ticks_diff(time.ticks_ms(), st) < self._off_time * 1000: 52 | await asyncio.sleep(0.2) 53 | except asyncio.CancelledError: 54 | print("repeating canceled") 55 | finally: 56 | await component_off() 57 | self._task = None 58 | print("repeating exited") 59 | 60 | async def activate(self, extended_switch, component, component_on, component_off): 61 | """Triggered whenever the mode changes and this mode has been activated""" 62 | if self._task is not None: 63 | print("Task already active") 64 | self._task.cancel() 65 | self._task = asyncio.create_task(self._repeating(component_on, component_off)) 66 | return True 67 | 68 | async def deactivate(self, extended_switch, component, component_on, component_off): 69 | """Triggered whenever the mode changes and this mode has been deactivated""" 70 | self._task.cancel() 71 | return True 72 | 73 | def __str__(self): 74 | """Name of the mode, has to be the same as the classname/module""" 75 | return "repeating" 76 | -------------------------------------------------------------------------------- /pysmartnode/components/switches/switch_extension/safety_off.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-28 4 | 5 | __updated__ = "2019-11-15" 6 | __version__ = "0.3" 7 | 8 | from pysmartnode.components.switches.switch_extension import Switch, ComponentSwitch, _mqtt, \ 9 | COMPONENT_NAME, BaseMode 10 | import uasyncio as asyncio 11 | import time 12 | 13 | 14 | class safety_off(BaseMode): 15 | """ 16 | Shut down device after configured amount of time 17 | """ 18 | 19 | def __init__(self, extended_switch: Switch, component: ComponentSwitch, component_on, 20 | component_off): 21 | self._on_time = 30 # default value to be adapted by mqtt 22 | count = component._count if hasattr(component, "_count") else "" 23 | _name = component._name if hasattr(component, "_name") else "{!s}{!s}".format( 24 | COMPONENT_NAME, count) 25 | topic = _mqtt.getDeviceTopic("{!s}/safety_off/on_time".format(_name), is_request=True) 26 | _mqtt.subscribeSync(topic, self._changeOnTime, extended_switch, check_retained_state=True) 27 | self._task = None 28 | self.topic = topic 29 | 30 | async def _changeOnTime(self, topic, msg, retain): 31 | self._on_time = int(msg) 32 | return True 33 | 34 | async def on(self, extended_switch, component, component_on, component_off): 35 | """Turn device on""" 36 | if component.state() is True and self._task is not None: 37 | return True 38 | if self._task is None: 39 | if await component_on() is True: 40 | self._task = asyncio.create_task(self._wait_off(component_off)) 41 | return True 42 | else: 43 | return False 44 | else: 45 | raise TypeError("Activated too quickly after deactivation") 46 | # can actually happen if on() immediately after requesting off() because 47 | # coro will not have exited by then. 48 | 49 | async def _wait_off(self, component_off): 50 | print("wait_off started") 51 | st = time.ticks_ms() 52 | try: 53 | while time.ticks_diff(time.ticks_ms(), st) < self._on_time * 1000: 54 | await asyncio.sleep(0.2) 55 | except asyncio.CancelledError: 56 | print("wait_off canceled") 57 | finally: 58 | self._task = None # prevents cancelling the cancelled coro 59 | await component_off() 60 | print("wait_off exited") 61 | 62 | async def off(self, extended_switch, component, component_on, component_off): 63 | """Turn device off""" 64 | if self._task is not None: 65 | self._task.cancel() 66 | else: 67 | await component_off() 68 | return True 69 | 70 | async def activate(self, extended_switch, component, component_on, component_off): 71 | """Triggered whenever the mode changes and this mode has been activated""" 72 | if component.state() is True: 73 | return await self.on(extended_switch, component, component_on, component_off) 74 | return True 75 | 76 | async def deactivate(self, extended_switch, component, component_on, component_off): 77 | """Triggered whenever the mode changes and this mode has been deactivated""" 78 | return await self.off(extended_switch, component, component_on, component_off) 79 | 80 | def __str__(self): 81 | """Name of the mode, has to be the same as the classname/module""" 82 | return "safety_off" 83 | -------------------------------------------------------------------------------- /pysmartnode/config.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2018-2020 Released under the MIT license 3 | # Created on 2018-03-09 4 | 5 | ## 6 | # Configuration management file 7 | ## 8 | 9 | __updated__ = "2020-04-02" 10 | 11 | from .config_base import * 12 | from sys import platform 13 | 14 | if platform == "linux" and DEVICE_NAME is None: 15 | raise TypeError("DEVICE_NAME has to be set on unix port") 16 | 17 | # General 18 | VERSION = const(612) 19 | print("PySmartNode version {!s} started".format(VERSION)) 20 | 21 | import gc 22 | import sys 23 | 24 | if DEBUG: 25 | def __printRAM(start, info=""): 26 | print(info, "Mem free", gc.mem_free(), "diff:", gc.mem_free() - start) 27 | else: 28 | __printRAM = lambda *_: None 29 | 30 | _mem = gc.mem_free() 31 | 32 | from pysmartnode.utils import sys_vars 33 | 34 | gc.collect() 35 | __printRAM(_mem, "Imported .sys_vars") 36 | 37 | import uasyncio as asyncio 38 | 39 | gc.collect() 40 | __printRAM(_mem, "Imported uasyncio") 41 | 42 | gc.collect() 43 | __printRAM(_mem, "Imported os") 44 | from pysmartnode import logging 45 | 46 | gc.collect() 47 | _log = logging.getLogger("config") 48 | 49 | gc.collect() 50 | __printRAM(_mem, "Imported logging") 51 | 52 | from pysmartnode.networking.mqtt import MQTTHandler 53 | 54 | gc.collect() 55 | __printRAM(_mem, "Imported MQTTHandler") 56 | 57 | COMPONENTS = {} # dictionary of all configured components 58 | _mqtt = MQTTHandler() 59 | gc.collect() 60 | __printRAM(_mem, "Created MQTT") 61 | 62 | 63 | async def registerComponent(name, data=None): 64 | """ 65 | Can be used to register a component with name and data dict. 66 | Also possible to register multiple components when passing dictionary as name arg 67 | :param name: str if data is dict, else dict containing multiple components 68 | :param data: dict if name given, else None 69 | :return: bool 70 | """ 71 | _log.debug("RAM before import registerComponents:", gc.mem_free(), local_only=True) 72 | import pysmartnode.utils.registerComponents 73 | gc.collect() 74 | _log.debug("RAM after import registerComponents:", gc.mem_free(), local_only=True) 75 | if data is None: 76 | res = await pysmartnode.utils.registerComponents.registerComponentsAsync(name, _log) 77 | else: 78 | res = pysmartnode.utils.registerComponents.registerComponent(name, data, _log) 79 | _log.debug("RAM before deleting registerComponents:", gc.mem_free(), local_only=True) 80 | del pysmartnode.utils.registerComponents 81 | del sys.modules["pysmartnode.utils.registerComponents"] 82 | gc.collect() 83 | _log.debug("RAM after deleting registerComponents:", gc.mem_free(), local_only=True) 84 | return res 85 | 86 | 87 | def getComponent(name): 88 | if name in COMPONENTS: 89 | return COMPONENTS[name] 90 | else: 91 | return None 92 | 93 | 94 | def getComponentName(component): 95 | for comp in COMPONENTS: 96 | if COMPONENTS[comp] == component: 97 | return comp 98 | return None 99 | 100 | 101 | def addComponent(name, obj): 102 | """ 103 | Add a named component to the list of accessible components. 104 | These are used to register components using remote configuration or local configuration files. 105 | """ 106 | if name in COMPONENTS: 107 | raise ValueError("Component {!s} already registered, can't add".format(name)) 108 | COMPONENTS[name] = obj 109 | 110 | 111 | def getMQTT(): 112 | return _mqtt 113 | 114 | 115 | from pysmartnode.utils.component import ComponentBase 116 | 117 | __printRAM(_mem, "Imported Component base class") 118 | 119 | from pysmartnode.components.machine.stats import STATS 120 | 121 | __printRAM(_mem, "Imported .machine.stats") 122 | COMPONENTS["STATS"] = STATS() 123 | __printRAM(_mem, "Created .machine.stats.STATS") 124 | -------------------------------------------------------------------------------- /pysmartnode/config_base.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-10-22 4 | 5 | __updated__ = "2020-08-18" 6 | 7 | from sys import platform 8 | from micropython import const 9 | 10 | # Required custom configuration 11 | WIFI_SSID = "" 12 | WIFI_PASSPHRASE = "" 13 | MQTT_HOST = "" 14 | MQTT_PORT = const(1883) 15 | MQTT_USER = "" 16 | MQTT_PASSWORD = "" 17 | 18 | # Optional configuration 19 | MQTT_KEEPALIVE = const(120) 20 | MQTT_HOME = "home" 21 | MQTT_AVAILABILITY_SUBTOPIC = "available" # will be generated to MQTT_HOME//MQTT_AVAILABILITY_SUBTOPIC 22 | MQTT_DISCOVERY_PREFIX = "homeassistant" 23 | MQTT_DISCOVERY_ENABLED = True 24 | MQTT_RECEIVE_CONFIG = False 25 | # RECEIVE_CONFIG: Only use if you run the "SmartServer" in your environment which 26 | # sends the configuration of a device over mqtt 27 | # If you do not run it, you have to configure the components locally on each microcontroller 28 | # using a components.py file 29 | MQTT_TYPE = const(1) # 1: direct, 0: IOT implementation (not working at the moment, use direct) 30 | MQTT_MAX_CONCURRENT_EXECUTIONS = -1 31 | # MAX_CONCURRENT_EXECUTIONS: Can be used to restrict the amount of concurrently executed mqtt 32 | # messages to prevent message spam to crash the device. 33 | # However there is no safety against crashing the device with very long messages. 34 | 35 | WIFI_LED = None # set a pin number to have the wifi state displayed by a blinking led. Useful for devices like sonoff 36 | WIFI_LED_ACTIVE_HIGH = True # if led is on when output is low, change to False 37 | 38 | WEBREPL_ACTIVE = False # If you want to have the webrepl active. Configures and starts it automatically. 39 | WEBREPL_PASSWORD = "" 40 | 41 | RTC_SYNC_ACTIVE = True # uses ~600B additional RAM on esp8266 42 | RTC_TIMEZONE_OFFSET = 1 # offset from GMT timezone as ntptime does not support timezones 43 | RTC_DAYLIGHT_SAVINGS = False # will add +1 hour to timezone during summer time. 44 | 45 | if platform == "esp32": 46 | FTP_ACTIVE = True 47 | elif platform == "pyboard": 48 | FTP_ACTIVE = True 49 | elif platform == "esp8266": 50 | LIGTWEIGHT_LOG = False # uses a smaller class for logging on esp8266 omitting module names, saves ~500Bytes 51 | USE_SOFTWARE_WATCHDOG = True # uses ~700B of RAM, started with timeout=2xMQTT_KEEPALIVE, use if you experience outages 52 | WIFI_SLEEP_MODE = 0 # WIFI_NONE_SLEEP = 0, WIFI_LIGHT_SLEEP = 1, WIFI_MODEM_SLEEP = 2; changed to 0 for increased stability. Standard is 2. Integrated into mqtt_as. 53 | elif platform == "linux": 54 | RTC_SYNC_ACTIVE = True # This should always be True unless your system doesn't have access to the internet or sync the time 55 | 56 | INTERVAL_SENSOR_PUBLISH = const(600) # publish sensor readings every 10 minutes by default 57 | INTERVAL_SENSOR_READ = const(120) # read sensor every 2 minutes by default 58 | 59 | # Device specific configurations: 60 | # 61 | # Name of the device 62 | DEVICE_NAME = None 63 | # set to a unique device name otherwise the id will be used. 64 | # This is relevant for homeassistant mqtt autodiscovery so the device gets 65 | # recognized by its device_name instead of the id. 66 | # It is also used with the unix port instead of the unique chip id (which is not available 67 | # on the unix port) and it therefore has to be UNIQUE in your network or 68 | # it will result in problems. 69 | 70 | # Does not need to be changed normally 71 | DEBUG = False 72 | DEBUG_STOP_AFTER_EXCEPTION = False 73 | 74 | from config import * 75 | # config.py will overwrite configuration values defined here. 76 | # This way only changes need to be put into config.py and the default values will 77 | # remain in config_base which is frozen bytecode and won't take up RAM. 78 | # Also makes adding new options easier as their default will be part of the firmware. 79 | -------------------------------------------------------------------------------- /pysmartnode/logging/__init__.py: -------------------------------------------------------------------------------- 1 | from sys import platform 2 | from pysmartnode import config 3 | import gc 4 | 5 | if hasattr(config, "LIGTWEIGHT_LOG") and config.LIGTWEIGHT_LOG is True or \ 6 | platform == "esp8266" and hasattr(config, "LIGTWEIGHT_LOG") is False: 7 | from pysmartnode.logging.logging_light import getLogger 8 | else: 9 | from pysmartnode.logging.logging_full import getLogger 10 | 11 | gc.collect() 12 | -------------------------------------------------------------------------------- /pysmartnode/logging/logging_full.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2017-2020 Released under the MIT license 3 | # Created on 2017-07-19 4 | 5 | __updated__ = "2019-11-02" 6 | __version__ = "2.9" 7 | 8 | # TODO: Add possibility to use real logging module on esp32 and save logs locally or to sdcard 9 | 10 | import gc 11 | from pysmartnode.utils import sys_vars 12 | from pysmartnode import config 13 | import uasyncio as asyncio 14 | 15 | gc.collect() 16 | import time 17 | 18 | 19 | async def asyncLog(name, level, *message, timeout=None, await_connection=True): 20 | if level == "debug" and not config.DEBUG: # ignore debug messages if debug is disabled 21 | return 22 | if config.getMQTT(): 23 | base_topic = "{!s}/log/{!s}/{!s}".format(config.MQTT_HOME, "{!s}", sys_vars.getDeviceID()) 24 | # if level is before id other clients can subscribe to e.g. all critical logs 25 | message = (b"{} " * (len(message) + 1)).format("[{}]".format(name), *message) 26 | gc.collect() 27 | await config.getMQTT().publish(base_topic.format(level), message, qos=1, timeout=timeout, 28 | await_connection=await_connection) 29 | # format message as bytes so there's no need to encode it later. 30 | 31 | 32 | def log(name, level, *message, local_only=False, return_only=False, timeout=None): 33 | if level == "debug" and not config.DEBUG: # ignore debug messages if debug is disabled 34 | return 35 | if hasattr(config, "RTC_SYNC_ACTIVE") and config.RTC_SYNC_ACTIVE: 36 | if hasattr(time, "strftime"): 37 | print("[{}]".format(time.strftime("%Y-%m-%d %H:%M:%S")), "[{}]".format(name), 38 | "[{}]".format(level), *message) 39 | else: 40 | t = time.localtime() 41 | print("[{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}]".format(*t), "[{}]".format(name), 42 | "[{}]".format(level), *message) 43 | else: 44 | print("[{!s}] [{!s}]".format(name, level), *message) 45 | if return_only: 46 | return 47 | if not local_only: 48 | asyncio.create_task(asyncLog(name, level, *message, timeout=timeout, 49 | await_connection=True)) 50 | 51 | 52 | class Logger: 53 | def __init__(self, name): 54 | self.name = name 55 | 56 | def critical(self, *message, local_only=False): 57 | log(self.name, "critical", *message, local_only=local_only, timeout=None) 58 | 59 | def error(self, *message, local_only=False): 60 | log(self.name, "error", *message, local_only=local_only, timeout=None) 61 | 62 | def warn(self, *message, local_only=False): 63 | log(self.name, "warn", *message, local_only=local_only, timeout=None) 64 | 65 | def info(self, *message, local_only=False): 66 | log(self.name, "info", *message, local_only=local_only, timeout=20) 67 | 68 | def debug(self, *message, local_only=False): 69 | log(self.name, "debug", *message, local_only=local_only, timeout=5) 70 | 71 | async def asyncLog(self, level, *message, timeout=None, await_connection=True): 72 | log(self.name, level, *message, return_only=True) 73 | if timeout == 0: 74 | return 75 | await asyncLog(self.name, level, *message, timeout=timeout, 76 | await_connection=await_connection) 77 | 78 | 79 | def getLogger(name): 80 | return Logger(name) 81 | -------------------------------------------------------------------------------- /pysmartnode/logging/logging_light.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2018-2020 Released under the MIT license 3 | # Created on 2018-03-10 4 | 5 | __updated__ = "2019-11-02" 6 | __version__ = "2.6" 7 | 8 | import gc 9 | from pysmartnode.utils import sys_vars 10 | from pysmartnode import config 11 | import uasyncio as asyncio 12 | 13 | gc.collect() 14 | 15 | 16 | class Logging: 17 | def __init__(self): 18 | self.id = sys_vars.getDeviceID() 19 | self.base_topic = "{!s}/log/{!s}/{!s}".format(config.MQTT_HOME, "{!s}", 20 | sys_vars.getDeviceID()) 21 | # if level is before id other clients can subscribe to e.g. all critical logs 22 | 23 | def _log(self, level, *message, local_only=False, timeout=None): 24 | print("[{!s}]".format(level), *message) 25 | if timeout == 0: 26 | return 27 | if config.getMQTT() and not local_only: 28 | message = (b"{} " * len(message)).format(*message) 29 | asyncio.create_task( 30 | config.getMQTT().publish(self.base_topic.format(level), message, qos=1, 31 | timeout=timeout, await_connection=True)) 32 | # format message as bytes so there's no need to encode it later. 33 | 34 | def critical(self, *message, local_only=False): 35 | self._log("critical", *message, local_only=local_only, timeout=None) 36 | 37 | def error(self, *message, local_only=False): 38 | self._log("error", *message, local_only=local_only, timeout=None) 39 | 40 | def warn(self, *message, local_only=False): 41 | self._log("warn", *message, local_only=local_only, timeout=None) 42 | 43 | def info(self, *message, local_only=False): 44 | self._log("info", *message, local_only=local_only, timeout=20) 45 | 46 | def debug(self, *message, local_only=False): 47 | if config.DEBUG: # ignore debug messages if debug is disabled 48 | self._log("debug", *message, local_only=local_only, timeout=5) 49 | 50 | async def asyncLog(self, level, *message, timeout=None, await_connection=True): 51 | if level == "debug" and not config.DEBUG: # ignore debug messages if debug is disabled 52 | return 53 | print("[{!s}]".format(level), *message) 54 | if timeout == 0: 55 | return 56 | if config.getMQTT() is not None: 57 | await config.getMQTT().publish(self.base_topic.format(level), 58 | b"{}".format(message if len(message) > 1 else 59 | message[0]), 60 | qos=1, timeout=timeout, 61 | await_connection=await_connection) 62 | 63 | 64 | log = Logging() 65 | 66 | 67 | def getLogger(name): 68 | return log 69 | -------------------------------------------------------------------------------- /pysmartnode/networking/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/networking/__init__.py -------------------------------------------------------------------------------- /pysmartnode/networking/ntp.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2020 Released under the MIT license 3 | # Created on 2020-08-19 4 | 5 | __updated__ = "2020-08-20" 6 | __version__ = "0.2" 7 | 8 | import ntptime 9 | import time 10 | import machine 11 | from pysmartnode import logging 12 | import uasyncio as asyncio 13 | from pysmartnode import config 14 | import gc 15 | from sys import platform 16 | 17 | 18 | async def sync(): 19 | s = 1 20 | while True: 21 | 22 | print("Synchronize time from NTP server ...") 23 | try: 24 | ntptime.settime() 25 | gc.collect() 26 | tm = time.localtime() 27 | offset = 0 28 | if config.RTC_DAYLIGHT_SAVINGS: 29 | # snippet for daylight savings adapted from forum post of user "JumpZero" 30 | year = time.localtime()[0] # get current year 31 | HHMarch = time.mktime((year, 3, (31 - (int(5 * year / 4 + 4)) % 7), 1, 0, 0, 0, 0, 32 | 0)) # Time of March change to CEST 33 | HHOctober = time.mktime((year, 10, (31 - (int(5 * year / 4 + 1)) % 7), 1, 0, 0, 0, 34 | 0, 0)) # Time of October change to CET 35 | now = time.time() 36 | if now < HHMarch: # we are before last sunday of march 37 | offset = 0 # only timezone change 38 | elif now < HHOctober: # we are before last sunday of october 39 | offset = 1 40 | else: # we are after last sunday of october 41 | offset = 0 # only timezone change 42 | day = tm[2] 43 | hour = tm[3] + config.RTC_TIMEZONE_OFFSET + offset 44 | if hour > 24: 45 | hour -= 24 46 | day += 1 47 | elif hour < 0: 48 | hour += 24 49 | day -= 1 50 | tm = tm[0:2] + (day,) + (0,) + (hour,) + tm[4:6] + (0,) 51 | machine.RTC().datetime(tm) 52 | print("Set time to", time.localtime()) 53 | s = 1 54 | await asyncio.sleep(18000 if platform != "esp8266" else 7200) # every 5h, 2h esp8266 55 | except Exception as e: 56 | await logging.getLogger("wifi").asyncLog("error", 57 | "Error syncing time: {!s}, retry in {!s}s".format( 58 | e, s), timeout=10) 59 | await asyncio.sleep(s) 60 | s += 5 61 | # should prevent crashes because previous request was not finished and 62 | # sockets still open (Errno 98 EADDRINUSE). Got killed by WDT after a few minutes. 63 | -------------------------------------------------------------------------------- /pysmartnode/networking/wifi_esp32.py: -------------------------------------------------------------------------------- 1 | from pysmartnode import config 2 | import gc 3 | import uasyncio as asyncio 4 | from pysmartnode.utils.sys_vars import getDeviceID 5 | import network 6 | 7 | __updated__ = "2020-08-19" 8 | 9 | try: 10 | s = network.WLAN(network.STA_IF) 11 | s.config(dhcp_hostname="{}{}".format("ESP32_", getDeviceID())) 12 | except Exception as e: 13 | print(e) # not important enough to do anything about it 14 | 15 | if config.RTC_SYNC_ACTIVE: 16 | from .ntp import sync 17 | 18 | asyncio.create_task(sync()) 19 | gc.collect() 20 | 21 | if hasattr(config, "FTP_ACTIVE") and config.FTP_ACTIVE is True: 22 | if config.WEBREPL_ACTIVE is True: 23 | try: 24 | import _thread 25 | except: 26 | config._log.critical("ftpserver background can't be used with webrepl") 27 | else: 28 | print("FTP-Server active") 29 | import pysmartnode.libraries.ftpserver.ftp_thread 30 | else: 31 | print("FTP-Server active") 32 | import pysmartnode.libraries.ftpserver.ftp_thread 33 | -------------------------------------------------------------------------------- /pysmartnode/networking/wifi_esp8266.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 26.05.2018 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | __updated__ = "2020-03-08" 8 | 9 | from pysmartnode import config 10 | import gc 11 | import uasyncio as asyncio 12 | from pysmartnode.utils.sys_vars import getDeviceID 13 | import network 14 | 15 | try: 16 | s = network.WLAN(network.STA_IF) 17 | s.config(dhcp_hostname="{}{}".format("ESP8266_", getDeviceID())) 18 | except Exception as e: 19 | print(e) # not important enough to do anything about it 20 | 21 | if config.WIFI_SLEEP_MODE is not None: 22 | import esp 23 | 24 | esp.sleep_type(config.WIFI_SLEEP_MODE) 25 | 26 | if config.RTC_SYNC_ACTIVE: 27 | from .ntp import sync 28 | 29 | asyncio.create_task(sync()) 30 | gc.collect() 31 | -------------------------------------------------------------------------------- /pysmartnode/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/utils/__init__.py -------------------------------------------------------------------------------- /pysmartnode/utils/abutton.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Based on Peter Hinch's aswitch.py. Useable as a drop-in replacement. 4 | # Queue overflow issue in Peter Hinch's aswitch fixed by now so this code 5 | # only provides an alternative with less RAM usage. 6 | # Created on 2019-10-19 7 | 8 | __updated__ = "2019-10-19" 9 | __version__ = "0.1" 10 | 11 | import uasyncio as asyncio 12 | import time 13 | 14 | type_gen = type((lambda: (yield))()) # Generator type 15 | 16 | 17 | # If a callback is passed, run it and return. 18 | # If a coro is passed initiate it and return. 19 | # coros are passed by name i.e. not using function call syntax. 20 | def launch(func, tup_args): 21 | res = func(*tup_args) 22 | if isinstance(res, type_gen): 23 | loop = asyncio.get_event_loop() 24 | loop.create_task(res) 25 | 26 | 27 | class Pushbutton: 28 | debounce_ms = 50 29 | long_press_ms = 1000 30 | double_click_ms = 400 31 | 32 | def __init__(self, pin, suppress=False): 33 | self.pin = pin 34 | self._supp = suppress # don't call release func after long press 35 | self._tf = None # pressed function 36 | self._ff = None # released function 37 | self._df = None # double pressed function 38 | self._lf = None # long pressed function 39 | self.sense = pin.value() # Convert from electrical to logical value 40 | self.state = self.rawstate() # Initial state 41 | loop = asyncio.get_event_loop() 42 | loop.create_task(self.buttoncheck()) # Thread runs forever 43 | 44 | def press_func(self, func, args=()): 45 | self._tf = func 46 | self._ta = args 47 | 48 | def release_func(self, func, args=()): 49 | self._ff = func 50 | self._fa = args 51 | 52 | def double_func(self, func, args=()): 53 | self._df = func 54 | self._da = args 55 | 56 | def long_func(self, func, args=()): 57 | self._lf = func 58 | self._la = args 59 | 60 | # Current non-debounced logical button state: True == pressed 61 | def rawstate(self): 62 | return bool(self.pin.value() ^ self.sense) 63 | 64 | # Current debounced state of button (True == pressed) 65 | def __call__(self): 66 | return self.state 67 | 68 | async def buttoncheck(self): 69 | t_change = None 70 | supp = False 71 | clicks = 0 72 | lpr = False # long press ran 73 | #### 74 | # local functions for performance improvements 75 | deb = self.debounce_ms 76 | dcms = self.double_click_ms 77 | lpms = self.long_press_ms 78 | raw = self.rawstate 79 | ticks_diff = time.ticks_diff 80 | ticks_ms = time.ticks_ms 81 | # 82 | while True: 83 | state = raw() 84 | if state is False and self.state is False and self._supp and \ 85 | ticks_diff(ticks_ms(), t_change) > dcms and clicks > 0 and self._ff: 86 | clicks = 0 87 | launch(self._ff, self._fa) 88 | elif state is True and self.state is True: 89 | if clicks > 0 and ticks_diff(ticks_ms(), t_change) > dcms: 90 | # double click timeout 91 | clicks = 0 92 | if self._lf and lpr is False: # check long press 93 | if ticks_diff(ticks_ms(), t_change) >= lpms: 94 | lpr = True 95 | clicks = 0 96 | if self._supp is True: 97 | supp = True 98 | launch(self._lf, self._la) 99 | elif state != self.state: # state changed 100 | lpr = False 101 | self.state = state 102 | if state is True: # Button pressed: launch pressed func 103 | if ticks_diff(ticks_ms(), t_change) > dcms: 104 | clicks = 0 105 | if self._df: 106 | clicks += 1 107 | if clicks == 2: # double click 108 | clicks = 0 109 | if self._supp is True: 110 | supp = True 111 | launch(self._df, self._da) 112 | elif self._tf: 113 | launch(self._tf, self._ta) 114 | else: # Button released. launch release func 115 | if supp is True: 116 | supp = False 117 | elif clicks and self._supp > 0: 118 | pass 119 | elif self._ff: # not after a long press with suppress 120 | launch(self._ff, self._fa) 121 | t_change = ticks_ms() 122 | # Ignore state changes until switch has settled 123 | await asyncio.sleep_ms(deb) 124 | -------------------------------------------------------------------------------- /pysmartnode/utils/aswitch.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Stripped down version of Peter Hinch's aswitch.py 3 | # Created on 2019-10-19 4 | 5 | __updated__ = "2019-10-19" 6 | __version__ = "0.1" 7 | 8 | import uasyncio as asyncio 9 | 10 | type_gen = type((lambda: (yield))()) # Generator type 11 | 12 | 13 | # If a callback is passed, run it and return. 14 | # If a coro is passed initiate it and return. 15 | # coros are passed by name i.e. not using function call syntax. 16 | def launch(func, tup_args): 17 | res = func(*tup_args) 18 | if isinstance(res, type_gen): 19 | loop = asyncio.get_event_loop() 20 | loop.create_task(res) 21 | 22 | 23 | class Switch: 24 | debounce_ms = 50 25 | 26 | def __init__(self, pin): 27 | self.pin = pin # Should be initialised for input with pullup 28 | self._open_func = False 29 | self._close_func = False 30 | self.switchstate = self.pin.value() # Get initial state 31 | loop = asyncio.get_event_loop() 32 | loop.create_task(self.switchcheck()) # Thread runs forever 33 | 34 | def open_func(self, func, args=()): 35 | self._open_func = func 36 | self._open_args = args 37 | 38 | def close_func(self, func, args=()): 39 | self._close_func = func 40 | self._close_args = args 41 | 42 | # Return current state of switch (0 = pressed) 43 | def __call__(self): 44 | return self.switchstate 45 | 46 | async def switchcheck(self): 47 | while True: 48 | state = self.pin.value() 49 | if state != self.switchstate: 50 | # State has changed: act on it now. 51 | self.switchstate = state 52 | if state == 0 and self._close_func: 53 | launch(self._close_func, self._close_args) 54 | elif state == 1 and self._open_func: 55 | launch(self._open_func, self._open_args) 56 | # Ignore further state changes until switch has settled 57 | await asyncio.sleep_ms(Switch.debounce_ms) 58 | -------------------------------------------------------------------------------- /pysmartnode/utils/component/button.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-10 4 | 5 | __updated__ = "2020-04-02" 6 | __version__ = "0.91" 7 | 8 | from .switch import ComponentSwitch 9 | from pysmartnode import config 10 | import uasyncio as asyncio 11 | 12 | _mqtt = config.getMQTT() 13 | 14 | 15 | class ComponentButton(ComponentSwitch): 16 | """ 17 | Generic Button class. 18 | Use it according to the template. 19 | It will activate a "single-shot" device that will be off after activation again. 20 | Otherwise it would be a Switch. 21 | """ 22 | 23 | def __init__(self, component_name, version, unit_index: int, wait_for_lock=False, 24 | initial_state=False, **kwargs): 25 | """ 26 | :param component_name: name of the component that is subclassing this switch (used for discovery and topics) 27 | :param version: version of the component module. will be logged over mqtt 28 | :param unit_index: counter of the registerd unit of this sensor_type (used for default topics) 29 | :param mqtt_topic: command_topic of subclass which controls the switch state. optional. 30 | :param instance_name: name of the instance. If not provided will get composed of component_name 31 | :param wait_for_lock: if True then every request waits for the lock to become available, 32 | meaning the previous device request has to finish before the new one is started. 33 | Otherwise the new one will get ignored. 34 | With a single-shot action it usually doesn't make sense to wait for the lock. 35 | :param friendly_name: friendly name for homeassistant gui 36 | :param initial_state: the initial state of the button, typically False ("OFF") for Pushbutton 37 | """ 38 | super().__init__(component_name, version, unit_index, wait_for_lock=wait_for_lock, 39 | restore_state=False, initial_state=initial_state, **kwargs) 40 | 41 | async def on(self): 42 | """Turn Button on. Can be used by other components to control this component""" 43 | if self._lock.locked() is True and self._wfl is False: 44 | return False 45 | async with self._lock: 46 | if self._pub_task: 47 | self._pub_task.cancel() # cancel if not finished, e.g. if activated quickly again 48 | self._pub_task = asyncio.create_task(self._publish("ON")) 49 | # so device gets activated as quickly as possible 50 | self._state = True 51 | await self._on() 52 | self._state = False 53 | if self._pub_task: 54 | self._pub_task.cancel() # cancel if not finished, e.g. if _on() is very fast 55 | self._pub_task = asyncio.create_task(self._publish("OFF")) 56 | return True 57 | 58 | async def off(self): 59 | """Only for compatibility as single-shot action has no off()""" 60 | return True 61 | 62 | async def toggle(self): 63 | """Just for compatibility reasons, will always activate single-shot action""" 64 | return await self.on() 65 | -------------------------------------------------------------------------------- /pysmartnode/utils/component/definitions.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-09-10 4 | 5 | __updated__ = "2020-08-13" 6 | __version__ = "0.5" 7 | 8 | # The discovery base should be a json string to keep the RAM requirement low and only need 9 | # to use format to enter the dynamic values so that the string is only loaded into RAM once 10 | # during the discovery method call. 11 | # Defining every base sensor in this module instead of in every custom component reduces RAM 12 | # requirements of modules that are not frozen to firmware. 13 | # For more variables see: https://www.home-assistant.io/docs/mqtt/discovery/ 14 | DISCOVERY_BASE = '{{' \ 15 | '"~":"{!s}",' \ 16 | '"name":"{!s}",' \ 17 | '"stat_t":"~",' \ 18 | '"uniq_id":"{!s}_{!s}",' \ 19 | '{!s}' \ 20 | '{!s}' \ 21 | '"dev":{!s}' \ 22 | '}}' 23 | 24 | DISCOVERY_AVAILABILITY = '"avty_t":"{!s}/{!s}/{!s}",' 25 | # '"pl_avail":"online",' \ 26 | # '"pl_not_avail":"offline",' \ 27 | # standard values 28 | 29 | DISCOVERY_SENSOR = '"dev_cla":"{!s}",' \ 30 | '"unit_of_meas":"{!s}",' \ 31 | '"val_tpl":"{!s}",' \ 32 | '"expire_after":"{!s}",' 33 | 34 | DISCOVERY_TIMELAPSE = '"dev_cla":"timestamp",' \ 35 | '"ic":"mdi:timelapse",' 36 | 37 | DISCOVERY_BINARY_SENSOR = '"dev_cla":"{!s}",' \ 38 | '"expire_after":"{!s}",' # "pl_on":"ON", "pl_off":"OFF",' are default 39 | 40 | DISCOVERY_SWITCH = '"cmd_t":"~/set",' # '"stat_on":"ON","stat_off":"OFF",' are default 41 | 42 | VALUE_TEMPLATE_JSON = "{{{{ value_json.{!s} }}}}" 43 | VALUE_TEMPLATE_FLOAT = "{{ value|float }}" 44 | VALUE_TEMPLATE_INT = "{{ value|int }}" 45 | VALUE_TEMPLATE = "{{ value }}" 46 | 47 | # By homeassistant supported sensor definitions to be used as device classes in discovery 48 | # Sensors 49 | SENSOR_BATTERY = 'battery' 50 | SENSOR_HUMIDITY = 'humidity' 51 | SENSOR_ILLUMINANCE = 'illuminance' 52 | SENSOR_SIGNAL_STRENGTH = 'signal_strength' 53 | SENSOR_TEMPERATURE = 'temperature' 54 | SENSOR_POWER = 'power' 55 | SENSOR_PRESSURE = 'pressure' 56 | SENSOR_TIMESTAMP = 'timestamp' 57 | 58 | # Binary sensors 59 | SENSOR_BINARY_BATTERY = ' battery' 60 | SENSOR_BINARY_COLD = 'cold' 61 | SENSOR_BINARY_CONNECTIVITY = 'connectivity' 62 | SENSOR_BINARY_DOOR = 'door' 63 | SENSOR_BINARY_GARAGE_DOOR = 'garage_door' 64 | SENSOR_BINARY_GAS = 'gas' 65 | SENSOR_BINARY_HEAT = 'heat' 66 | SENSOR_BINARY_LIGHT = 'light' 67 | SENSOR_BINARY_LOCK = 'lock' 68 | SENSOR_BINARY_MOISTURE = 'moisture' 69 | SENSOR_BINARY_MOTION = 'motion' 70 | SENSOR_BINARY_MOVING = 'moving' 71 | SENSOR_BINARY_OCCUPANCY = 'occupancy' 72 | SENSOR_BINARY_OPENING = 'opening' 73 | SENSOR_BINARY_PLUG = 'plug' 74 | SENSOR_BINARY_POWER = 'power' 75 | SENSOR_BINARY_PRESENCE = 'presence' 76 | SENSOR_BINARY_PROBLEM = 'problem' 77 | SENSOR_BINARY_SAFETY = 'safety' 78 | SENSOR_BINARY_SMOKE = 'smoke' 79 | SENSOR_BINARY_SOUND = 'sound' 80 | SENSOR_BINARY_VIBRATION = 'vibration' 81 | SENSOR_BINARY_WINDOW = 'window' 82 | -------------------------------------------------------------------------------- /pysmartnode/utils/locksync.py: -------------------------------------------------------------------------------- 1 | class Lock: 2 | """ synchronous lock that does not block""" 3 | 4 | def __init__(self): 5 | self._locked = False 6 | 7 | def acquire(self): 8 | if self._locked: 9 | return False 10 | else: 11 | self._locked = True 12 | return True 13 | 14 | def locked(self): 15 | return self._locked 16 | 17 | def release(self): 18 | self._locked = False 19 | # not checking if it was locked 20 | return True 21 | -------------------------------------------------------------------------------- /pysmartnode/utils/registerComponents.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import uasyncio as asyncio 3 | from pysmartnode import config 4 | from sys import platform 5 | import io 6 | import sys 7 | 8 | __updated__ = "2020-04-02" 9 | __version__ = "0.6" 10 | 11 | if config.DEBUG: 12 | def __printRAM(start, info=""): 13 | print(info, "Mem free", gc.mem_free(), "diff:", gc.mem_free() - start) 14 | else: 15 | __printRAM = lambda *_: None 16 | 17 | 18 | def _checkArgs(d, _log): 19 | required = ("package", "component") 20 | for r in required: 21 | if r not in d: 22 | _log.error("Missing required value {!r} in component".format(r)) 23 | return False 24 | return True 25 | 26 | 27 | def _getKwargs(kwargs): 28 | if type(kwargs) != dict: 29 | return {} 30 | COMPONENTS = config.COMPONENTS 31 | for key in kwargs: 32 | arg = kwargs[key] 33 | if type(arg) == str: 34 | if arg in COMPONENTS: 35 | kwargs[key] = COMPONENTS[arg] 36 | return kwargs 37 | 38 | 39 | def _checkPackage(data): 40 | if data["package"].startswith("."): 41 | data["package"] = "{}{}".format("pysmartnode.components", data["package"]) 42 | 43 | 44 | async def registerComponentsAsync(data, _log): 45 | for component in data["_order"]: 46 | registerComponent(component, data[component], _log) 47 | gc.collect() 48 | await asyncio.sleep_ms(750 if platform == "esp8266" else 200) 49 | gc.collect() 50 | 51 | 52 | def registerComponent(componentname, component, _log): 53 | gc.collect() 54 | mem_start = gc.mem_free() 55 | __printRAM(mem_start) 56 | res = True 57 | COMPONENTS = config.COMPONENTS 58 | version = None 59 | if componentname in COMPONENTS: 60 | _log.error("Component {!r} already added".format(componentname)) 61 | # asyncLog would block if no network 62 | else: 63 | if _checkArgs(component, _log): 64 | _checkPackage(component) 65 | try: 66 | module = __import__(component["package"], globals(), 67 | locals(), [component["component"]], 0) 68 | except Exception as e: 69 | s = io.StringIO() 70 | sys.print_exception(e, s) 71 | _log.critical( 72 | "Error importing package {!s}, error: {!s}".format(component["package"], 73 | s.getvalue())) 74 | module = None 75 | gc.collect() 76 | err = False 77 | if module is not None: 78 | if hasattr(module, "__version__"): 79 | version = getattr(module, "__version__") 80 | if hasattr(module, "COMPONENT_NAME"): 81 | module_name = getattr(module, "COMPONENT_NAME") 82 | else: 83 | module_name = component["package"] 84 | if hasattr(module, component["component"]): 85 | kwargs = _getKwargs( 86 | component["constructor_args"]) if "constructor_args" in component and type( 87 | component["constructor_args"]) == dict else {} 88 | try: 89 | obj = getattr(module, component["component"]) 90 | obj = obj(**kwargs) 91 | # only support functions (no coroutines) to prevent network block in user/component code 92 | except Exception as e: 93 | s = io.StringIO() 94 | sys.print_exception(e, s) 95 | _log.error( 96 | "Error during creation of object {!r}, {!r}, version {!s}: {!s}".format( 97 | component["component"], componentname, version, s.getvalue())) 98 | obj = None 99 | err = True 100 | if obj is not None: 101 | COMPONENTS[componentname] = obj 102 | # _log.info("Added module {!r} version {!s} as component {!r}".format( 103 | # module_name, version, componentname)) 104 | elif err is False: # but no obj because no obj got created as component was a function 105 | _log.info( 106 | "Added module {!s} version {!s}, component {!r} as service".format( 107 | module_name, version, 108 | componentname)) # function. Prpbably unused since 5.0.0. 109 | else: 110 | res = False 111 | else: 112 | _log.critical( 113 | "error during import of module {!s}".format(component["component"])) 114 | res = False 115 | gc.collect() 116 | __printRAM(mem_start) 117 | return res 118 | -------------------------------------------------------------------------------- /pysmartnode/utils/sys_vars.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2018-2020 Released under the MIT license 3 | # Created on 2018-02-02 4 | 5 | __updated__ = "2020-09-01" 6 | 7 | import os 8 | import ubinascii 9 | from sys import platform 10 | 11 | DISCOVERY_DEVICE_BASE = '{{' \ 12 | '"ids":"{!s}",' \ 13 | '"sw":"pysmartnode {!s}",' \ 14 | '"mf":"{!s}",' \ 15 | '"mdl":"{!s}",' \ 16 | '"name": "{!s}"' \ 17 | '{!s}' \ 18 | '}}' 19 | 20 | 21 | def getDeviceType(): 22 | if platform == "linux": 23 | return os.name 24 | return os.uname().sysname 25 | 26 | 27 | def getDeviceID(): 28 | from pysmartnode import config 29 | if config.DEVICE_NAME is not None: 30 | return config.DEVICE_NAME 31 | import machine 32 | return ubinascii.hexlify(machine.unique_id()).decode() 33 | 34 | 35 | def hasFilesystem(): 36 | if platform == "linux": 37 | return True # assume working filesystem 38 | return not os.statvfs("")[0] == 0 39 | 40 | 41 | def getDeviceDiscovery(): 42 | from pysmartnode import config 43 | mf = "espressif" if platform in ("esp8266", "esp32") else "None" 44 | if platform != "linux": 45 | import network 46 | s = network.WLAN(network.STA_IF) 47 | mac = ',"connections": [["mac", "{!s}"]]'.format( 48 | ubinascii.hexlify(s.config("mac"), ":").decode()) 49 | else: 50 | mac = "" 51 | return DISCOVERY_DEVICE_BASE.format(getDeviceID(), 52 | config.VERSION, 53 | mf, 54 | os.uname().sysname if platform != "linux" else "linux", 55 | config.DEVICE_NAME if config.DEVICE_NAME is not None else getDeviceID(), 56 | mac) 57 | -------------------------------------------------------------------------------- /pysmartnode/utils/wrappers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinkk525/pysmartnode/a0998ad6582a28fe5a0529fb15dd4f61e254d25f/pysmartnode/utils/wrappers/__init__.py -------------------------------------------------------------------------------- /pysmartnode/utils/wrappers/async_wrapper.py: -------------------------------------------------------------------------------- 1 | type_gen = type((lambda: (yield))()) # Generator type 2 | 3 | 4 | def async_wrapper(f): 5 | """ property not supported, only function and coroutine """ 6 | 7 | async def wrap(*args, **kwargs): 8 | res = f(*args, **kwargs) 9 | if type(res) == type_gen: 10 | res = await res 11 | return res 12 | 13 | return wrap 14 | -------------------------------------------------------------------------------- /pysmartnode/utils/wrappers/callAsyncSafe.py: -------------------------------------------------------------------------------- 1 | from pysmartnode.config import _log 2 | 3 | 4 | async def callAsyncSafe(func, name, args, kwargs=None): 5 | kwargs = kwargs or {} 6 | try: 7 | await func(*args, **kwargs) 8 | except Exception as e: 9 | _log.error("Error calling async function {!r}, error: {!s}".format(name, e)) 10 | -------------------------------------------------------------------------------- /pysmartnode/utils/wrappers/timeit.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 18.02.2018 3 | 4 | @author: Kevin Köck 5 | ''' 6 | 7 | import time 8 | 9 | 10 | def timeit(f): 11 | myname = str(f).split(' ')[1] # do this calculation outside of measurement 12 | 13 | def new_func(*args, **kwargs): 14 | t = time.ticks_us() 15 | result = f(*args, **kwargs) 16 | delta = time.ticks_diff(time.ticks_us(), t) 17 | print('[Time] Function {}: {:6.3f}ms'.format(myname, delta / 1000)) 18 | return result 19 | 20 | return new_func 21 | 22 | 23 | def timeitAsync(f): 24 | async def new_func(*args, **kwargs): 25 | gen = f(*args, **kwargs) 26 | myname = str(gen).split(' ')[2] 27 | t = time.ticks_us() 28 | result = await gen 29 | delta = time.ticks_diff(time.ticks_us(), t) 30 | print('[Time] Coroutine {}: {:6.3f}ms'.format(myname, delta / 1000)) 31 | return result 32 | 33 | return new_func 34 | -------------------------------------------------------------------------------- /tools/esp32/esp32_get_repository.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools python-serial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja-build ccache 3 | sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools cmake ninja-build ccache libffi-dev libssl-dev 4 | pip3 install pyserial 'pyparsing<2.4' esptool rshell 5 | cd ~/ 6 | git clone https://github.com/micropython/micropython.git 7 | cd micropython 8 | git submodule update --init 9 | cd ports/esp32 10 | hash=$(make | grep "Supported git hash (v4.0) (experimental):") 11 | hash=${hash:42} 12 | cd ~/ 13 | git clone -b release/v4.0 --recursive https://github.com/espressif/esp-idf.git 14 | cd esp-idf 15 | git pull 16 | git checkout $hash 17 | git submodule update --init 18 | ./install.sh 19 | . $HOME/esp-idf/export.sh 20 | cd .. 21 | mkdir esp 22 | cd esp 23 | wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz 24 | tar -zxvf xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz 25 | export PATH="$HOME/esp/xtensa-esp32-elf/bin:$PATH" 26 | cd .. 27 | cd micropython/ports/esp32 28 | echo '''ESPIDF = $(HOME)'/esp-idf' 29 | #PORT = /dev/ttyUSB0 30 | #FLASH_MODE = qio 31 | #FLASH_SIZE = 4MB 32 | #CROSS_COMPILE = xtensa-esp32-elf- 33 | 34 | include Makefile''' > makefile 35 | cd .. 36 | cd .. 37 | git submodule update --init 38 | make -C mpy-cross 39 | cd ports/esp32 40 | make PYTHON=python2 -j12 -------------------------------------------------------------------------------- /tools/esp32/ftp/esp32_sync_ftp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | HOST="192.168.178.70" 3 | USER="" 4 | PASS="" 5 | TARGETFOLDER="/pysmartnode" 6 | SOURCEFOLDER="/mnt/r/WNodePython/pysmartnode/" 7 | 8 | lftp -f " 9 | open $HOST 10 | user $USER $PASS 11 | lcd $SOURCEFOLDER 12 | mirror --reverse --delete --verbose $SOURCEFOLDER $TARGETFOLDER 13 | bye 14 | " 15 | echo "Done" -------------------------------------------------------------------------------- /tools/esp32/ftp/generate_bytecode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Cur dir: $(pwd)" 4 | 5 | mkdir /mnt/r/WNodePython 6 | cd /mnt/r/WNodePython 7 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/pysmartnode/" ./pysmartnode/ --delete 8 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/_testing/" ./_testing/ --delete 9 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/external_modules/" ./external_modules/ --delete 10 | 11 | function foo { 12 | echo $1 13 | #local res=$(/home/kevin/.local/bin/strip-hints --only-assigns-and-defs --only-test-for-changes $1) 14 | #if [[ $res = "True" ]]; then 15 | # echo "$1 stripped of hints" 16 | # v=$(/home/kevin/.local/bin/strip-hints --only-assigns-and-defs $1) 17 | # echo "$v" > $1 18 | #else 19 | # echo $1 $res 20 | #fi 21 | ~/micropython/mpy-cross/mpy-cross -march=xtensawin $1; 22 | rm $1 23 | } 24 | export -f foo 25 | for d in pysmartnode _testing external_modules 26 | do 27 | 28 | find ./$d -name \*.py -exec bash -c 'foo "$@"' bash {} \; 29 | done 30 | echo "generated bytecode" -------------------------------------------------------------------------------- /tools/esp32/ftp/renew.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./generate_bytecode.sh 4 | ./esp32_sync_ftp.sh -------------------------------------------------------------------------------- /tools/esp8266/esp8266_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd ~/micropython/ports/esp8266 3 | export PATH=/home/kevin/esp-open-sdk/xtensa-lx106-elf/bin:$PATH 4 | make clean BOARD=pysmartnode_4M 5 | make BOARD=pysmartnode_4M -j12 6 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_build_1M.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd ~/micropython/ports/esp8266 3 | export PATH=/home/kevin/esp-open-sdk/xtensa-lx106-elf/bin:$PATH 4 | make clean BOARD=pysmartnode_1M 5 | make BOARD=pysmartnode_1M -j12 6 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_erase_flash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd ~/micropython/ports/esp8266 3 | /usr/local/bin/esptool.py --port /dev/ttyS7 erase_flash 4 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_flash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd ~/micropython/ports/esp8266 3 | export PATH=/home/kevin/esp-open-sdk/xtensa-lx106-elf/bin:$PATH 4 | make BOARD=pysmartnode_4M PORT=/dev/ttyS7 FLASH_MODE=qio FLASH_SIZE=32m BAUDRATE=115200 deploy 5 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_flash_1M.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | esptool.py --port /dev/ttyS7 --baud 115200 write_flash --flash_size=1MB -fm dout 0x0 /home/kevin/micropython/ports/esp8266/build-pysmartnode_1M/firmware-combined.bin 3 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_get_repository.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | sudo apt install make unrar-free autoconf automake libtool gcc g++ gperf \ 3 | flex bison texinfo gawk ncurses-dev libexpat-dev python-dev python python3-serial \ 4 | sed git unzip bash help2man wget bzip2 5 | sudo apt install python3-dev python3-pip libtool-bin 6 | pip3 install rshell esptool 7 | cd ~/ 8 | git clone --recursive https://github.com/pfalcon/esp-open-sdk.git 9 | cd esp-open-sdk 10 | curl https://bootstrap.pypa.io/get-pip.py --output get-pip.py 11 | sudo python2 get-pip.py 12 | pip install pyserial 13 | sed -i 's/GNU bash, version (3\.[1-9]|4)/GNU bash, version ([0-9\.]+)/' ~/esp-open-sdk/crosstool-NG/configure.ac 14 | make STANDALONE=y 15 | export PATH=/home/kevin/esp-open-sdk/xtensa-lx106-elf/bin:$PATH 16 | cd ~/ 17 | git clone https://github.com/micropython/micropython.git 18 | cd micropython 19 | git submodule update --init 20 | make -C mpy-cross 21 | cd ports/esp8266 22 | make axtls 23 | make -j12 24 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_initialize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "/home/kevin/ws_cloud/Programme Python/WNodePython/" 3 | #mpfshell -o ttyS9 -n -c "put main.py" 4 | #mpfshell -o ttyS9 -n -c "put config.py" 5 | #mpfshell -o ttyS9 -n -c "put boot.py" 6 | rshell -p /dev/ttyS7 "cp config.py /pyboard/;cp boot.py /pyboard/;cp main.py /pyboard/" 7 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_renew.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ./esp8266_sync.sh 3 | #./esp8266_remove_hints.sh 4 | ./esp8266_build.sh 5 | ./esp8266_flash.sh 6 | #./esp8266_initialize.sh 7 | echo "Done" 8 | -------------------------------------------------------------------------------- /tools/esp8266/esp8266_sync.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rsync -av --delete pysmartnode_4M /home/kevin/micropython/ports/esp8266/boards/ 3 | rsync -av --delete pysmartnode_1M /home/kevin/micropython/ports/esp8266/boards/ 4 | cd ~/micropython/ports/esp8266/modules 5 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/pysmartnode/" ~/micropython/ports/esp8266/modules/pysmartnode/ --delete 6 | #rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/_testing/" ./_testing/ --delete 7 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/external_modules/" ~/micropython/ports/esp8266/modules/ 8 | -------------------------------------------------------------------------------- /tools/esp8266/pysmartnode_1M/esp8266_1m.ld: -------------------------------------------------------------------------------- 1 | /* GNU linker script for ESP8266 with 1M flash 2 | 3 | Flash layout: 4 | 0x40200000 36k header + iram/dram init 5 | 0x40209000 872k firmware (irom0) 6 | 0x40298000 96k filesystem 7 | 0x402fb000 20k SDK parameters 8 | */ 9 | 10 | MEMORY 11 | { 12 | dport0_0_seg : org = 0x3ff00000, len = 16 13 | dram0_0_seg : org = 0x3ffe8000, len = 80K 14 | iram1_0_seg : org = 0x40100000, len = 32K 15 | irom0_0_seg : org = 0x40209000, len = 872K 16 | } 17 | 18 | /* define common sections and symbols */ 19 | INCLUDE boards/esp8266_common.ld 20 | -------------------------------------------------------------------------------- /tools/esp8266/pysmartnode_1M/manifest.py: -------------------------------------------------------------------------------- 1 | include("$(PORT_DIR)/boards/manifest.py") 2 | include("$(MPY_DIR)/extmod/uasyncio/manifest.py") 3 | -------------------------------------------------------------------------------- /tools/esp8266/pysmartnode_1M/mpconfigboard.h: -------------------------------------------------------------------------------- 1 | #define MICROPY_HW_BOARD_NAME "ESP module" 2 | #define MICROPY_HW_MCU_NAME "ESP8266" 3 | 4 | #define MICROPY_PERSISTENT_CODE_LOAD (1) 5 | #define MICROPY_EMIT_XTENSA (1) 6 | #define MICROPY_EMIT_INLINE_XTENSA (1) 7 | 8 | #define MICROPY_DEBUG_PRINTERS (1) 9 | #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) 10 | 11 | #define MICROPY_READER_VFS (MICROPY_VFS) 12 | #define MICROPY_VFS (1) 13 | 14 | #define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) 15 | #define MICROPY_PY_ALL_SPECIAL_METHODS (1) 16 | #define MICROPY_PY_IO_FILEIO (1) 17 | #define MICROPY_PY_SYS_STDIO_BUFFER (1) 18 | #define MICROPY_PY_UASYNCIO (1) 19 | #define MICROPY_PY_URE_SUB (1) 20 | #define MICROPY_PY_UCRYPTOLIB (1) 21 | #define MICROPY_PY_FRAMEBUF (1) 22 | -------------------------------------------------------------------------------- /tools/esp8266/pysmartnode_1M/mpconfigboard.mk: -------------------------------------------------------------------------------- 1 | LD_FILES = $(BOARD_DIR)/esp8266_1m.ld 2 | 3 | MICROPY_PY_BTREE ?= 0 4 | MICROPY_VFS_FAT ?= 0 5 | MICROPY_VFS_LFS2 ?= 1 6 | 7 | FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py 8 | -------------------------------------------------------------------------------- /tools/esp8266/pysmartnode_4M/manifest.py: -------------------------------------------------------------------------------- 1 | include("$(PORT_DIR)/boards/manifest.py") 2 | include("$(MPY_DIR)/extmod/uasyncio/manifest.py") 3 | -------------------------------------------------------------------------------- /tools/esp8266/pysmartnode_4M/mpconfigboard.h: -------------------------------------------------------------------------------- 1 | #define MICROPY_HW_BOARD_NAME "ESP module" 2 | #define MICROPY_HW_MCU_NAME "ESP8266" 3 | 4 | #define MICROPY_PERSISTENT_CODE_LOAD (1) 5 | #define MICROPY_EMIT_XTENSA (1) 6 | #define MICROPY_EMIT_INLINE_XTENSA (1) 7 | 8 | #define MICROPY_DEBUG_PRINTERS (1) 9 | #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) 10 | 11 | #define MICROPY_READER_VFS (MICROPY_VFS) 12 | #define MICROPY_VFS (1) 13 | 14 | #define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) 15 | #define MICROPY_PY_ALL_SPECIAL_METHODS (1) 16 | #define MICROPY_PY_IO_FILEIO (1) 17 | #define MICROPY_PY_SYS_STDIO_BUFFER (1) 18 | #define MICROPY_PY_UASYNCIO (1) 19 | #define MICROPY_PY_URE_SUB (1) 20 | #define MICROPY_PY_UCRYPTOLIB (1) 21 | #define MICROPY_PY_FRAMEBUF (1) 22 | -------------------------------------------------------------------------------- /tools/esp8266/pysmartnode_4M/mpconfigboard.mk: -------------------------------------------------------------------------------- 1 | LD_FILES = boards/esp8266_2m.ld 2 | 3 | MICROPY_PY_BTREE ?= 0 4 | MICROPY_VFS_FAT ?= 0 5 | MICROPY_VFS_LFS2 ?= 1 6 | 7 | FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py 8 | -------------------------------------------------------------------------------- /tools/local/generate_component_definitions.py: -------------------------------------------------------------------------------- 1 | # Author: Kevin Köck 2 | # Copyright Kevin Köck 2019-2020 Released under the MIT license 3 | # Created on 2019-10-30 4 | 5 | __updated__ = "2019-10-30" 6 | __version__ = "0.1" 7 | 8 | # Generate component definitions from Homeassistant website by replacing the documentation list 9 | 10 | 11 | list_binary = ''' battery: On means low, Off means normal 12 | cold: On means cold, Off means normal 13 | connectivity: On means connected, Off means disconnected 14 | door: On means open, Off means closed 15 | garage_door: On means open, Off means closed 16 | gas: On means gas detected, Off means no gas (clear) 17 | heat: On means hot, Off means normal 18 | light: On means light detected, Off means no light 19 | lock: On means open (unlocked), Off means closed (locked) 20 | moisture: On means moisture detected (wet), Off means no moisture (dry) 21 | motion: On means motion detected, Off means no motion (clear) 22 | moving: On means moving, Off means not moving (stopped) 23 | occupancy: On means occupied, Off means not occupied (clear) 24 | opening: On means open, Off means closed 25 | plug: On means device is plugged in, Off means device is unplugged 26 | power: On means power detected, Off means no power 27 | presence: On means home, Off means away 28 | problem: On means problem detected, Off means no problem (OK) 29 | safety: On means unsafe, Off means safe 30 | smoke: On means smoke detected, Off means no smoke (clear) 31 | sound: On means sound detected, Off means no sound (clear) 32 | vibration: On means vibration detected, Off means no vibration (clear) 33 | window: On means open, Off means closed''' 34 | 35 | list_sensor = '''battery: Percentage of battery that is left. 36 | humidity: Percentage of humidity in the air. 37 | illuminance: The current light level in lx or lm. 38 | signal_strength: Signal strength in dB or dBm. 39 | temperature: Temperature in °C or °F. 40 | power: Power in W or kW. 41 | pressure: Pressure in hPa or mbar. 42 | timestamp: Datetime object or timestamp string.''' 43 | 44 | 45 | def generateList(b, binary: bool): 46 | b = b.split("\n") 47 | for i, v in enumerate(b): 48 | b[i] = v[:v.find(":")] 49 | for i in b: 50 | print("SENSOR_{!s}{!s} = {!r}".format("BINARY_" if binary else "", i.upper(), i)) 51 | 52 | 53 | print("# Sensors") 54 | generateList(list_sensor, False) 55 | print("\n# Binary sensors") 56 | generateList(list_binary, True) 57 | -------------------------------------------------------------------------------- /tools/unix/sync.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd ~/.micropython/lib 3 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/pysmartnode/" ./pysmartnode/ --delete 4 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/_testing/" ./_testing/ --delete 5 | rsync -av --prune-empty-dirs --include "*/" --include "*.py" --exclude "*" --exclude "*.*" "/home/kevin/ws_cloud/Programme Python/WNodePython/external_modules/" ./ 6 | --------------------------------------------------------------------------------