update
This commit is contained in:
parent
b8549ac286
commit
9fce8b9e56
42
code/bmp280_node.py
Normal file
42
code/bmp280_node.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
from homie.constants import FLOAT
|
||||||
|
from homie.property import HomieProperty
|
||||||
|
from update_homie_node import UpdateHomieNode
|
||||||
|
# from homie.device import await_ready_state
|
||||||
|
|
||||||
|
# import uasyncio as asyncio
|
||||||
|
# from time import ticks_ms, ticks_add, ticks_diff
|
||||||
|
|
||||||
|
|
||||||
|
class BMP280Node(UpdateHomieNode):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
bmp280,
|
||||||
|
interval=60*5):
|
||||||
|
super().__init__(id=id, name=name, type="sensor", interval=interval)
|
||||||
|
|
||||||
|
# BMP280
|
||||||
|
self.bmp280 = bmp280
|
||||||
|
self.property_temerature = HomieProperty(
|
||||||
|
id="temperature",
|
||||||
|
name="Temperatur",
|
||||||
|
datatype=FLOAT,
|
||||||
|
unit="°C",
|
||||||
|
)
|
||||||
|
self.add_property(self.property_temerature)
|
||||||
|
self.property_pressure = HomieProperty(
|
||||||
|
id="pressure",
|
||||||
|
name="Druck",
|
||||||
|
datatype=FLOAT,
|
||||||
|
unit="Pa",
|
||||||
|
)
|
||||||
|
self.add_property(self.property_pressure)
|
||||||
|
|
||||||
|
def update_data(self):
|
||||||
|
self.property_pressure.value = "{:1.0f}".format(
|
||||||
|
self.bmp280.pressure * 100) # hPa = 100 Pa
|
||||||
|
self.property_temerature.value = "{:1.2f}".format(
|
||||||
|
self.bmp280.temperature)
|
114
code/lib/rotary.py
Normal file
114
code/lib/rotary.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# The MIT License (MIT)
|
||||||
|
# Copyright (c) 2020 Mike Teachman
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Platform-independent MicroPython code for the rotary encoder module
|
||||||
|
|
||||||
|
# Documentation:
|
||||||
|
# https://github.com/MikeTeachman/micropython-rotary
|
||||||
|
|
||||||
|
_DIR_CW = const(0x10) # Clockwise step
|
||||||
|
_DIR_CCW = const(0x20) # Counter-clockwise step
|
||||||
|
|
||||||
|
# Rotary Encoder States
|
||||||
|
_R_START = const(0x0)
|
||||||
|
_R_CW_1 = const(0x1)
|
||||||
|
_R_CW_2 = const(0x2)
|
||||||
|
_R_CW_3 = const(0x3)
|
||||||
|
_R_CCW_1 = const(0x4)
|
||||||
|
_R_CCW_2 = const(0x5)
|
||||||
|
_R_CCW_3 = const(0x6)
|
||||||
|
_R_ILLEGAL = const(0x7)
|
||||||
|
|
||||||
|
_transition_table = [
|
||||||
|
|
||||||
|
#|------------- NEXT STATE -------------| |CURRENT STATE|
|
||||||
|
# CLK/DT CLK/DT CLK/DT CLK/DT
|
||||||
|
# 00 01 10 11
|
||||||
|
[_R_START, _R_CCW_1, _R_CW_1, _R_START], # _R_START
|
||||||
|
[_R_CW_2, _R_START, _R_CW_1, _R_START], # _R_CW_1
|
||||||
|
[_R_CW_2, _R_CW_3, _R_CW_1, _R_START], # _R_CW_2
|
||||||
|
[_R_CW_2, _R_CW_3, _R_START, _R_START | _DIR_CW], # _R_CW_3
|
||||||
|
[_R_CCW_2, _R_CCW_1, _R_START, _R_START], # _R_CCW_1
|
||||||
|
[_R_CCW_2, _R_CCW_1, _R_CCW_3, _R_START], # _R_CCW_2
|
||||||
|
[_R_CCW_2, _R_START, _R_CCW_3, _R_START | _DIR_CCW], # _R_CCW_3
|
||||||
|
[_R_START, _R_START, _R_START, _R_START]] # _R_ILLEGAL
|
||||||
|
|
||||||
|
_STATE_MASK = const(0x07)
|
||||||
|
_DIR_MASK = const(0x30)
|
||||||
|
|
||||||
|
def _wrap(value, incr, lower_bound, upper_bound):
|
||||||
|
range = upper_bound - lower_bound + 1
|
||||||
|
value = value + incr
|
||||||
|
|
||||||
|
if value < lower_bound:
|
||||||
|
value += range * ((lower_bound - value) // range + 1)
|
||||||
|
|
||||||
|
return lower_bound + (value - lower_bound) % range
|
||||||
|
|
||||||
|
def _bound(value, incr, lower_bound, upper_bound):
|
||||||
|
return min(upper_bound, max(lower_bound, value + incr))
|
||||||
|
|
||||||
|
class Rotary(object):
|
||||||
|
|
||||||
|
RANGE_UNBOUNDED = const(1)
|
||||||
|
RANGE_WRAP = const(2)
|
||||||
|
RANGE_BOUNDED = const(3)
|
||||||
|
|
||||||
|
def __init__(self, min_val, max_val, reverse, range_mode):
|
||||||
|
self._min_val = min_val
|
||||||
|
self._max_val = max_val
|
||||||
|
self._reverse = -1 if reverse else 1
|
||||||
|
self._range_mode = range_mode
|
||||||
|
self._value = min_val
|
||||||
|
self._state = _R_START
|
||||||
|
|
||||||
|
def set(self, value=None, min_val=None, max_val=None, reverse=None, range_mode=None):
|
||||||
|
# disable DT and CLK pin interrupts
|
||||||
|
self._hal_disable_irq()
|
||||||
|
|
||||||
|
if value != None:
|
||||||
|
self._value = value
|
||||||
|
if min_val != None:
|
||||||
|
self._min_val = min_val
|
||||||
|
if max_val != None:
|
||||||
|
self._max_val = max_val
|
||||||
|
if reverse != None:
|
||||||
|
self._reverse = -1 if reverse else 1
|
||||||
|
if range_mode != None:
|
||||||
|
self._range_mode = range_mode
|
||||||
|
self._state = _R_START
|
||||||
|
|
||||||
|
# enable DT and CLK pin interrupts
|
||||||
|
self._hal_enable_irq()
|
||||||
|
|
||||||
|
def value(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self._value = 0
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._hal_close()
|
||||||
|
|
||||||
|
def _process_rotary_pins(self, pin):
|
||||||
|
clk_dt_pins = (self._hal_get_clk_value() << 1) | self._hal_get_dt_value()
|
||||||
|
# Determine next state
|
||||||
|
self._state = _transition_table[self._state & _STATE_MASK][clk_dt_pins]
|
||||||
|
direction = self._state & _DIR_MASK
|
||||||
|
|
||||||
|
incr = 0
|
||||||
|
if direction == _DIR_CW:
|
||||||
|
incr = 1
|
||||||
|
elif direction == _DIR_CCW:
|
||||||
|
incr = -1
|
||||||
|
|
||||||
|
incr *= self._reverse
|
||||||
|
|
||||||
|
if self._range_mode == self.RANGE_WRAP:
|
||||||
|
self._value = _wrap(self._value, incr, self._min_val, self._max_val)
|
||||||
|
elif self._range_mode == self.RANGE_BOUNDED:
|
||||||
|
self._value = _bound(self._value, incr, self._min_val, self._max_val)
|
||||||
|
else:
|
||||||
|
self._value = self._value + incr
|
||||||
|
|
68
code/lib/rotary_irq_esp.py
Normal file
68
code/lib/rotary_irq_esp.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# The MIT License (MIT)
|
||||||
|
# Copyright (c) 2020 Mike Teachman
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Platform-specific MicroPython code for the rotary encoder module
|
||||||
|
# ESP8266/ESP32 implementation
|
||||||
|
|
||||||
|
# Documentation:
|
||||||
|
# https://github.com/MikeTeachman/micropython-rotary
|
||||||
|
|
||||||
|
from machine import Pin
|
||||||
|
from rotary import Rotary
|
||||||
|
from sys import platform
|
||||||
|
|
||||||
|
_esp8266_deny_pins = [16]
|
||||||
|
|
||||||
|
class RotaryIRQ(Rotary):
|
||||||
|
|
||||||
|
def __init__(self, pin_num_clk, pin_num_dt, min_val=0, max_val=10, reverse=False, range_mode=Rotary.RANGE_UNBOUNDED, pull_up=False):
|
||||||
|
|
||||||
|
if platform == 'esp8266':
|
||||||
|
if pin_num_clk in _esp8266_deny_pins:
|
||||||
|
raise ValueError('%s: Pin %d not allowed. Not Available for Interrupt: %s' % (platform, pin_num_clk,_esp8266_deny_pins))
|
||||||
|
if pin_num_dt in _esp8266_deny_pins:
|
||||||
|
raise ValueError('%s: Pin %d not allowed. Not Available for Interrupt: %s' % (platform, pin_num_dt,_esp8266_deny_pins))
|
||||||
|
|
||||||
|
super().__init__(min_val, max_val, reverse, range_mode)
|
||||||
|
|
||||||
|
if pull_up == True:
|
||||||
|
self._pin_clk = Pin(pin_num_clk, Pin.IN, Pin.PULL_UP)
|
||||||
|
self._pin_dt = Pin(pin_num_dt, Pin.IN, Pin.PULL_UP)
|
||||||
|
else:
|
||||||
|
self._pin_clk = Pin(pin_num_clk, Pin.IN)
|
||||||
|
self._pin_dt = Pin(pin_num_dt, Pin.IN)
|
||||||
|
|
||||||
|
self._enable_clk_irq(self._process_rotary_pins)
|
||||||
|
self._enable_dt_irq(self._process_rotary_pins)
|
||||||
|
|
||||||
|
def _enable_clk_irq(self, callback=None):
|
||||||
|
self._pin_clk.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=callback)
|
||||||
|
|
||||||
|
def _enable_dt_irq(self, callback=None):
|
||||||
|
self._pin_dt.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=callback)
|
||||||
|
|
||||||
|
def _disable_clk_irq(self):
|
||||||
|
self._pin_clk.irq(handler=None)
|
||||||
|
|
||||||
|
def _disable_dt_irq(self):
|
||||||
|
self._pin_dt.irq(handler=None)
|
||||||
|
|
||||||
|
def _hal_get_clk_value(self):
|
||||||
|
return self._pin_clk.value()
|
||||||
|
|
||||||
|
def _hal_get_dt_value(self):
|
||||||
|
return self._pin_dt.value()
|
||||||
|
|
||||||
|
def _hal_enable_irq(self):
|
||||||
|
self._enable_clk_irq(self._process_rotary_pins)
|
||||||
|
self._enable_dt_irq(self._process_rotary_pins)
|
||||||
|
|
||||||
|
def _hal_disable_irq(self):
|
||||||
|
self._disable_clk_irq()
|
||||||
|
self._disable_dt_irq()
|
||||||
|
|
||||||
|
def _hal_close(self):
|
||||||
|
self._hal_disable_irq()
|
||||||
|
|
||||||
|
|
179
code/plant_box_control_panel.py
Normal file
179
code/plant_box_control_panel.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
from sh1106 import SH1106_I2C
|
||||||
|
from rotary_irq_esp import RotaryIRQ
|
||||||
|
|
||||||
|
import uasyncio as asyncio
|
||||||
|
from time import ticks_ms, ticks_add, ticks_diff
|
||||||
|
|
||||||
|
|
||||||
|
from homie.constants import BOOLEAN, TRUE, FALSE, FLOAT
|
||||||
|
from homie.property import HomieProperty
|
||||||
|
from homie.node import HomieNode
|
||||||
|
from homie.device import await_ready_state
|
||||||
|
from machine import Pin
|
||||||
|
from primitives.pushbutton import Pushbutton
|
||||||
|
|
||||||
|
|
||||||
|
class PlantBoxControlPanel(HomieNode):
|
||||||
|
|
||||||
|
def __init__(self, id, name, waterlevel_sensor, bmp280,
|
||||||
|
i2c, pin_clk, pin_dt, pin_sw):
|
||||||
|
super().__init__(id=id, name=name, type="controller")
|
||||||
|
self.i2c = i2c
|
||||||
|
self.bmp280 = bmp280
|
||||||
|
self.display = SH1106_I2C(
|
||||||
|
i2c=self.i2c, width=128, height=64)
|
||||||
|
self.display.rotate(True, False)
|
||||||
|
|
||||||
|
self.rotary_encoder = RotaryIRQ(
|
||||||
|
pin_num_clk=int(str(pin_clk)[4:][:-1]),
|
||||||
|
pin_num_dt=int(str(pin_dt)[4:][:-1]),
|
||||||
|
min_val=0,
|
||||||
|
max_val=3,
|
||||||
|
reverse=False,
|
||||||
|
pull_up=True,
|
||||||
|
range_mode=RotaryIRQ.RANGE_WRAP)
|
||||||
|
|
||||||
|
self.waterlevel_sensor = waterlevel_sensor
|
||||||
|
pin_sw.init(mode=Pin.IN, pull=Pin.PULL_UP)
|
||||||
|
self.button = Pushbutton(pin_sw, suppress=True, sense=1)
|
||||||
|
|
||||||
|
self._screen_on = False
|
||||||
|
self.needs_redraw = False
|
||||||
|
self.update_interval = 0.3
|
||||||
|
|
||||||
|
self.property_button_pressed = HomieProperty(
|
||||||
|
id="button_pressed",
|
||||||
|
name="Knöpgen gedrückt",
|
||||||
|
settable=True,
|
||||||
|
default=FALSE,
|
||||||
|
on_message=self._on_button_pressed_msg,
|
||||||
|
datatype=BOOLEAN,
|
||||||
|
)
|
||||||
|
self.add_property(self.property_button_pressed)
|
||||||
|
|
||||||
|
self.property_screen_on = HomieProperty(
|
||||||
|
id="screen_on",
|
||||||
|
name="Bildschrim angeschaltet",
|
||||||
|
settable=True,
|
||||||
|
default=FALSE,
|
||||||
|
on_message=self.on_screen_on_msg,
|
||||||
|
datatype=BOOLEAN,
|
||||||
|
)
|
||||||
|
self.add_property(self.property_screen_on)
|
||||||
|
|
||||||
|
self.screen_timeout = 30
|
||||||
|
self.screen_timeout_ticks = 0
|
||||||
|
self.property_screen_timeout = HomieProperty(
|
||||||
|
id="screen_timeout",
|
||||||
|
name="Bildschrimtimeout",
|
||||||
|
settable=True,
|
||||||
|
default=str(self.screen_timeout),
|
||||||
|
on_message=self.on_screen_timeout_msg,
|
||||||
|
datatype=FLOAT,
|
||||||
|
)
|
||||||
|
self.add_property(self.property_screen_timeout)
|
||||||
|
|
||||||
|
self.on_button_pressed = None
|
||||||
|
self.on_button_released = None
|
||||||
|
asyncio.create_task(self._update_data_async())
|
||||||
|
|
||||||
|
def _on_button_pressed_msg(self, topic, payload, retained):
|
||||||
|
if {FALSE: False, TRUE: True}[payload] and not self.button.rawstate():
|
||||||
|
self._on_button_pressed()
|
||||||
|
self._on_button_released()
|
||||||
|
|
||||||
|
def _on_button_pressed(self):
|
||||||
|
self.property_button_pressed.value = TRUE
|
||||||
|
self.needs_redraw = True
|
||||||
|
if self.on_button_pressed:
|
||||||
|
self.on_button_pressed()
|
||||||
|
|
||||||
|
def _on_button_released(self):
|
||||||
|
self.property_button_pressed.value = FALSE
|
||||||
|
self._set_screen_on(True)
|
||||||
|
self.needs_redraw = True
|
||||||
|
if self.on_button_released:
|
||||||
|
self.on_button_released()
|
||||||
|
|
||||||
|
def on_screen_on_msg(self, topic, payload, retained):
|
||||||
|
self._set_screen_on({FALSE: False, TRUE: True}[payload])
|
||||||
|
|
||||||
|
def on_screen_timeout_msg(self, topic, payload, retained):
|
||||||
|
new_screen_timeout = float(payload)
|
||||||
|
self.screen_timeout_ticks = \
|
||||||
|
ticks_add(
|
||||||
|
ticks_diff(
|
||||||
|
self.screen_timeout_ticks,
|
||||||
|
int(self.screen_timeout * 1000)),
|
||||||
|
int(new_screen_timeout * 1000))
|
||||||
|
self.screen_timeout = new_screen_timeout
|
||||||
|
|
||||||
|
def reset_screen_timout(self):
|
||||||
|
self.screen_timeout_ticks = ticks_add(
|
||||||
|
ticks_ms(),
|
||||||
|
int(self.screen_timeout * 1000))
|
||||||
|
|
||||||
|
# @await_ready_state
|
||||||
|
async def _update_data_async(self):
|
||||||
|
self.button.press_func(self._on_button_pressed)
|
||||||
|
self.button.release_func(self._on_button_released)
|
||||||
|
rotary_last = 0
|
||||||
|
last_temp = 0
|
||||||
|
while True:
|
||||||
|
if ticks_diff(self.screen_timeout_ticks, ticks_ms()) <= 0:
|
||||||
|
self._set_screen_on(False)
|
||||||
|
if self._screen_on:
|
||||||
|
if rotary_last != self.rotary_encoder.value():
|
||||||
|
rotary_last = self.rotary_encoder.value()
|
||||||
|
self.needs_redraw = True
|
||||||
|
if last_temp != self.bmp280.temperature:
|
||||||
|
self.needs_redraw = True
|
||||||
|
last_temp = self.bmp280.temperature
|
||||||
|
if self.needs_redraw:
|
||||||
|
self.needs_redraw = False
|
||||||
|
# self.display.fill(0)
|
||||||
|
# self.display.text(str(self.rotary_encoder.value()), 0, 20)
|
||||||
|
# self.display.text(str(self.button.rawstate()), 0, 30)
|
||||||
|
self.display.fill_rect(0, 0, 128, 10, 0)
|
||||||
|
self.display.text("{:1.2f}°C".format(self.bmp280.temperature), 1, 1)
|
||||||
|
actions = [("Einst.", None), ("Wasser", lambda : self._set_screen_on(False))]
|
||||||
|
self.rotary_encoder.set(max_val=len(actions) - 1)
|
||||||
|
for i, (a, br) in enumerate(actions):
|
||||||
|
selected = i == self.rotary_encoder.value()
|
||||||
|
color = 0 if selected else 1
|
||||||
|
screen_dif = int(128 / len(actions))
|
||||||
|
self.display.fill_rect(
|
||||||
|
screen_dif * i+1, 50, screen_dif-2, 10, 1-color)
|
||||||
|
self.display.text(a, screen_dif*i+2, 51, color)
|
||||||
|
if not self.screen_just_turned_on:
|
||||||
|
if selected:
|
||||||
|
self.on_button_released = br
|
||||||
|
self.display.show()
|
||||||
|
self.screen_just_turned_on = False
|
||||||
|
else:
|
||||||
|
self.rotary_encoder.set(value=0)
|
||||||
|
self.on_button_released, self.on_button_released = None, None
|
||||||
|
await asyncio.sleep_ms(int(self.update_interval*1000.0))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def screen_on(self):
|
||||||
|
return self._screen_on
|
||||||
|
|
||||||
|
@screen_on.setter
|
||||||
|
def set_screen_on(self, on):
|
||||||
|
self._set_screen_on(on)
|
||||||
|
|
||||||
|
def _set_screen_on(self, on):
|
||||||
|
if on == self._screen_on:
|
||||||
|
if on:
|
||||||
|
self.reset_screen_timout()
|
||||||
|
else:
|
||||||
|
self._screen_on = on
|
||||||
|
if on:
|
||||||
|
# self.display.poweron()
|
||||||
|
self.reset_screen_timout()
|
||||||
|
self.needs_redraw = True
|
||||||
|
self.screen_just_turned_on = True
|
||||||
|
else:
|
||||||
|
self.display.poweroff()
|
||||||
|
self.property_screen_on = TRUE if on else FALSE
|
131
code/plant_node.py
Normal file
131
code/plant_node.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
from homie.constants import BOOLEAN, FALSE, TRUE, FLOAT
|
||||||
|
from homie.property import HomieProperty
|
||||||
|
from update_homie_node import UpdateHomieNode
|
||||||
|
# from homie.device import await_ready_state
|
||||||
|
from machine import Pin, ADC
|
||||||
|
import uasyncio as asyncio
|
||||||
|
|
||||||
|
|
||||||
|
class PlantNode(UpdateHomieNode):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
pin_watering,
|
||||||
|
pin_moisture,
|
||||||
|
pin_water_tank,
|
||||||
|
waterlevel_sensor,
|
||||||
|
interval=60*5,
|
||||||
|
interval_watering=0.1):
|
||||||
|
super().__init__(
|
||||||
|
id=id, name=name, type="watering",
|
||||||
|
interval=interval,
|
||||||
|
interval_short=interval_watering)
|
||||||
|
|
||||||
|
# Update Interval
|
||||||
|
self.interval_normal = interval
|
||||||
|
self.interval_watering = interval_watering
|
||||||
|
|
||||||
|
# WaterLevelSensor
|
||||||
|
self.waterlevel_sensor = waterlevel_sensor
|
||||||
|
self.property_waterlevel = HomieProperty(
|
||||||
|
id="waterlevel",
|
||||||
|
name="Wassertankstand",
|
||||||
|
datatype=FLOAT,
|
||||||
|
unit="L",
|
||||||
|
)
|
||||||
|
self.add_property(self.property_waterlevel)
|
||||||
|
self.property_waterlevel_percent = HomieProperty(
|
||||||
|
id="waterlevel_percent",
|
||||||
|
name="Wassertankstand [%]",
|
||||||
|
datatype=FLOAT,
|
||||||
|
format="0.00:100.00",
|
||||||
|
unit="%",
|
||||||
|
)
|
||||||
|
self.add_property(self.property_waterlevel_percent)
|
||||||
|
|
||||||
|
self.property_waterlevel_volume_liter = HomieProperty(
|
||||||
|
id="waterlevel_volume_max",
|
||||||
|
name="Wassertankgröße",
|
||||||
|
settable=True,
|
||||||
|
datatype=FLOAT,
|
||||||
|
unit="L",
|
||||||
|
on_message=self._set_waterlevel_volume
|
||||||
|
)
|
||||||
|
self.add_property(self.property_waterlevel_volume_liter)
|
||||||
|
|
||||||
|
# Moisture
|
||||||
|
self.adc = ADC(pin_moisture)
|
||||||
|
self.adc.atten(ADC.ATTN_11DB)
|
||||||
|
self.property_moisture = HomieProperty(
|
||||||
|
id="moisture",
|
||||||
|
name="Feuchte",
|
||||||
|
datatype=FLOAT,
|
||||||
|
format="0.00:100.00",
|
||||||
|
unit="%",
|
||||||
|
)
|
||||||
|
self.add_property(self.property_moisture)
|
||||||
|
|
||||||
|
# Watering Motor
|
||||||
|
self.pin_watering_motor = pin_watering
|
||||||
|
self.pin_watering_motor.init(mode=Pin.OUT, value=0)
|
||||||
|
self.property_water_power = HomieProperty(
|
||||||
|
id="power",
|
||||||
|
name="Bewässerung",
|
||||||
|
settable=True,
|
||||||
|
datatype=BOOLEAN,
|
||||||
|
default=FALSE,
|
||||||
|
on_message=self.toggle_motor,
|
||||||
|
)
|
||||||
|
self.add_property(self.property_water_power)
|
||||||
|
|
||||||
|
self.property_watering_max_duration = HomieProperty(
|
||||||
|
id="watering_duration_max",
|
||||||
|
name="Bewässerungszeit",
|
||||||
|
settable=True,
|
||||||
|
datatype=FLOAT,
|
||||||
|
default=3,
|
||||||
|
unit="s",
|
||||||
|
)
|
||||||
|
self.add_property(self.property_watering_max_duration)
|
||||||
|
|
||||||
|
def update_data(self):
|
||||||
|
self.property_moisture.value = "{:1.2f}".format(
|
||||||
|
(4096 - self.adc.read()) / 40.96)
|
||||||
|
|
||||||
|
self.property_waterlevel_percent.value = "{:1.0f}".format(
|
||||||
|
self.waterlevel_sensor.level_percent)
|
||||||
|
self.property_waterlevel.value = "{:1.2f}".format(
|
||||||
|
self.waterlevel_sensor.level)
|
||||||
|
self.property_waterlevel_volume_liter.value = "{:1.4f}".format(
|
||||||
|
self.waterlevel_sensor.volume)
|
||||||
|
|
||||||
|
if self.pin_watering_motor.value() == 1:
|
||||||
|
self.interval_normal = self._interval
|
||||||
|
self.set_interval(self.interval_watering)
|
||||||
|
|
||||||
|
def toggle_motor(self, topic, payload, retained):
|
||||||
|
ONOFF = {FALSE: 0, TRUE: 1}
|
||||||
|
v = ONOFF[payload]
|
||||||
|
self.pin_watering_motor(v)
|
||||||
|
if v == 1:
|
||||||
|
self.interval_normal = self._interval
|
||||||
|
self.set_interval(self.interval_watering)
|
||||||
|
asyncio.create_task(self.stop_motor())
|
||||||
|
|
||||||
|
def stop_motor(self):
|
||||||
|
await asyncio.sleep_ms(
|
||||||
|
int(float(self.property_watering_max_duration.value) * 1000))
|
||||||
|
self.pin_watering_motor.value(0)
|
||||||
|
self.set_interval(self.interval_normal)
|
||||||
|
self.property_water_power.value = FALSE
|
||||||
|
|
||||||
|
def _set_waterlevel_min_value(self, topic, payload, retained):
|
||||||
|
self.waterlevel_sensor.value_min = float(payload)
|
||||||
|
|
||||||
|
def _set_waterlevel_max_value(self, topic, payload, retained):
|
||||||
|
self.waterlevel_sensor.value_max = float(payload)
|
||||||
|
|
||||||
|
def _set_waterlevel_volume(self, topic, payload, retained):
|
||||||
|
self.waterlevel_sensor.volume = float(payload)
|
76
code/update_homie_node.py
Normal file
76
code/update_homie_node.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
from homie.constants import FLOAT
|
||||||
|
from homie.property import HomieProperty
|
||||||
|
from homie.node import HomieNode
|
||||||
|
from homie.device import await_ready_state
|
||||||
|
|
||||||
|
import uasyncio as asyncio
|
||||||
|
from time import ticks_ms, ticks_add, ticks_diff
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateHomieNode(HomieNode):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
interval=60*5,
|
||||||
|
interval_short=0.1):
|
||||||
|
super().__init__(id=id, name=name, type=type)
|
||||||
|
|
||||||
|
self.interval_changed = False
|
||||||
|
self.interval_short = interval_short
|
||||||
|
# Update Interval
|
||||||
|
self._interval = interval
|
||||||
|
self.property_interval = HomieProperty(
|
||||||
|
id="update_interval",
|
||||||
|
name="Aktualisierungsrate",
|
||||||
|
datatype=FLOAT, # TODO ISO8601
|
||||||
|
settable=True,
|
||||||
|
on_message=self._set_interval,
|
||||||
|
unit="s",
|
||||||
|
)
|
||||||
|
self.add_property(self.property_interval)
|
||||||
|
|
||||||
|
asyncio.create_task(self._update_data_async())
|
||||||
|
|
||||||
|
@await_ready_state
|
||||||
|
async def _update_data_async(self):
|
||||||
|
while True:
|
||||||
|
|
||||||
|
# call child callback
|
||||||
|
self.update_data()
|
||||||
|
|
||||||
|
# TODO ISO8601
|
||||||
|
# self.property_interval.value = "PT{:1.3f}S".format(self.interval)
|
||||||
|
self.property_interval.value = "{:1.3f}".format(self.interval)
|
||||||
|
|
||||||
|
# We don't simply wait the update interval, as it can change while waiting.
|
||||||
|
last_update = ticks_ms()
|
||||||
|
wait_till = ticks_add(last_update, int(self.interval * 1000.0))
|
||||||
|
while ticks_diff(ticks_ms(), wait_till) < 0:
|
||||||
|
if self.interval_changed:
|
||||||
|
self.interval_changed = False
|
||||||
|
wait_till = ticks_add(
|
||||||
|
last_update,
|
||||||
|
int(self.interval * 1000))
|
||||||
|
sleep_for = min(int(self.interval_short * 1000.0),
|
||||||
|
ticks_diff(wait_till, ticks_ms()))
|
||||||
|
await asyncio.sleep_ms(sleep_for)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def interval(self):
|
||||||
|
return self._interval
|
||||||
|
|
||||||
|
@interval.setter
|
||||||
|
def setter_interval(self, i):
|
||||||
|
self.set_interval(i)
|
||||||
|
|
||||||
|
def set_interval(self, i):
|
||||||
|
if i != self._interval:
|
||||||
|
self.interval_changed = True
|
||||||
|
self._interval = float(i)
|
||||||
|
|
||||||
|
def _set_interval(self, topic, payload, retained):
|
||||||
|
self.set_interval(float(payload))
|
Loading…
x
Reference in New Issue
Block a user