Separate out classes into separate files
This commit is contained in:
parent
476a5cb8d1
commit
2cb6472133
|
@ -0,0 +1,64 @@
|
||||||
|
class Controller:
|
||||||
|
def __init__(self, humidifiers):
|
||||||
|
self.target_lower = 85
|
||||||
|
self.target_upper = 90
|
||||||
|
self.feedforward_coeff = 50
|
||||||
|
self.last_toggle = 0
|
||||||
|
|
||||||
|
self._manual_mode = False
|
||||||
|
self.manual_on = False
|
||||||
|
self.manual_timeout = 0
|
||||||
|
self.manual_duration = 40
|
||||||
|
|
||||||
|
self.humidifier_history = np.zeros(50)
|
||||||
|
self.first_sample = False
|
||||||
|
self.humidifiers = humidifiers
|
||||||
|
|
||||||
|
@property
|
||||||
|
def manual_mode(self):
|
||||||
|
return self._manual_mode
|
||||||
|
|
||||||
|
@manual_mode.setter
|
||||||
|
def manual_mode(self, on):
|
||||||
|
self._manual_mode = on
|
||||||
|
send_update({"status": {"manual_mode": on}})
|
||||||
|
|
||||||
|
def set_checked(self, s, humidifier, on):
|
||||||
|
if time.time() - self.last_toggle > 0.8:
|
||||||
|
if humidifier is HumidifierV3:
|
||||||
|
humidifier.set(s, on)
|
||||||
|
else:
|
||||||
|
humidifier.toggle(s)
|
||||||
|
self.last_toggle = time.time()
|
||||||
|
|
||||||
|
def update(self, humidity):
|
||||||
|
if self.first_sample:
|
||||||
|
self.humidifier_history[:] = humidity
|
||||||
|
self.first_sample = False
|
||||||
|
else:
|
||||||
|
self.humidifier_history[:-1] = self.humidifier_history[1:]
|
||||||
|
self.humidifier_history[-1] = humidity
|
||||||
|
|
||||||
|
# compensate for the slow response time by adding a little feed forward
|
||||||
|
# using the slope of the humidifier data
|
||||||
|
slope = (self.humidifier_history[-1] - self.humidifier_history[0])/self.humidifier_history.shape[0]
|
||||||
|
comp_humidity = humidity + self.feedforward_coeff*slope
|
||||||
|
|
||||||
|
if self.manual_mode and time.time() > self.manual_timeout:
|
||||||
|
self.manual_mode = False
|
||||||
|
|
||||||
|
if self.manual_mode:
|
||||||
|
for humidifier in self.humidifiers:
|
||||||
|
if humidifier.off and self.manual_on:
|
||||||
|
self.set_checked(s, humidifier, True)
|
||||||
|
elif humidifier.on and not self.manual_on:
|
||||||
|
self.set_checked(s, humidifier, False)
|
||||||
|
else:
|
||||||
|
if comp_humidity < self.target_lower:
|
||||||
|
for humidifier in self.humidifiers:
|
||||||
|
if humidifier.off:
|
||||||
|
self.set_checked(s, humidifier, True)
|
||||||
|
elif comp_humidity > self.target_upper:
|
||||||
|
for humidifier in self.humidifiers:
|
||||||
|
if humidifier.on:
|
||||||
|
self.set_checked(s, humidifier, False)
|
|
@ -0,0 +1,101 @@
|
||||||
|
class Humidifier:
|
||||||
|
def __init__(self):
|
||||||
|
self.off_threshold = 0.4
|
||||||
|
self.on_threshold = 1.9
|
||||||
|
self.toggle_cooldown = 16
|
||||||
|
|
||||||
|
self._on = False
|
||||||
|
self.history = np.zeros(10)
|
||||||
|
self.switch_timeout = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on(self):
|
||||||
|
return self._on
|
||||||
|
|
||||||
|
@on.setter
|
||||||
|
def on(self, nv):
|
||||||
|
old_on = self._on
|
||||||
|
self._on = nv
|
||||||
|
if nv:
|
||||||
|
print("send hum on")
|
||||||
|
else:
|
||||||
|
print("send hum off")
|
||||||
|
send_update({"status": {"humidifier": nv}})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def off(self):
|
||||||
|
return not self.on
|
||||||
|
|
||||||
|
def update(self, volts):
|
||||||
|
self.history[1:] = self.history[:-1]
|
||||||
|
self.history[0] = volts
|
||||||
|
#print(self.history)
|
||||||
|
avg = np.sum(self.history)/self.history.shape[0]
|
||||||
|
if self.on:
|
||||||
|
if avg < self.off_threshold:
|
||||||
|
self.on = False
|
||||||
|
self.switch_timeout = time.time() + 1
|
||||||
|
else:
|
||||||
|
if avg > self.on_threshold:
|
||||||
|
self.on = True
|
||||||
|
self.switch_timeout = time.time() + 1
|
||||||
|
|
||||||
|
def toggle(self, s):
|
||||||
|
if time.time() > self.switch_timeout:
|
||||||
|
s.write(b"h")
|
||||||
|
s.flush()
|
||||||
|
self.switch_timeout = time.time() + self.toggle_cooldown
|
||||||
|
|
||||||
|
# the wiring's a little different so the thresholds for detecting on/off are inverted but hopefully a lot more reliable than the original
|
||||||
|
class HumidifierV2:
|
||||||
|
def __init__(self, toggle_cmd=b"i", humidifier_id="humidifier2"):
|
||||||
|
self.on_threshold = 1.5
|
||||||
|
self.off_threshold = 2.5
|
||||||
|
self.toggle_cooldown = 7
|
||||||
|
self.toggle_command = toggle_cmd
|
||||||
|
|
||||||
|
self._on = False
|
||||||
|
self.history = np.zeros(10)
|
||||||
|
self.switch_timeout = 0
|
||||||
|
self.humidifier_id = humidifier_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on(self):
|
||||||
|
return self._on
|
||||||
|
|
||||||
|
@on.setter
|
||||||
|
def on(self, nv):
|
||||||
|
old_on = self._on
|
||||||
|
self._on = nv
|
||||||
|
if nv:
|
||||||
|
print("send {} on".format(self.humidifier_id))
|
||||||
|
else:
|
||||||
|
print("send {} off".format(self.humidifier_id))
|
||||||
|
send_update({"status": {self.humidifier_id: nv}})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def off(self):
|
||||||
|
return not self.on
|
||||||
|
|
||||||
|
def update(self, volts):
|
||||||
|
self.history[1:] = self.history[:-1]
|
||||||
|
self.history[0] = volts
|
||||||
|
#print(self.history)
|
||||||
|
avg = np.sum(self.history)/self.history.shape[0]
|
||||||
|
if self.on:
|
||||||
|
if avg > self.off_threshold:
|
||||||
|
self.on = False
|
||||||
|
self.switch_timeout = time.time() + 3
|
||||||
|
else:
|
||||||
|
if avg < self.on_threshold:
|
||||||
|
self.on = True
|
||||||
|
self.switch_timeout = time.time() + 3
|
||||||
|
|
||||||
|
def toggle(self, s):
|
||||||
|
if time.time() > self.switch_timeout:
|
||||||
|
print("toggling {}".format(self.humidifier_id))
|
||||||
|
s.write(self.toggle_command)
|
||||||
|
s.flush()
|
||||||
|
self.switch_timeout = time.time() + self.toggle_cooldown
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import time
|
||||||
|
|
||||||
|
# this is driving a SSR that's powering a AC-powered humidifier
|
||||||
|
# we don't need voltage readings for this one
|
||||||
|
class HumidifierV3:
|
||||||
|
def __init__(self):
|
||||||
|
self.last_toggle = 0
|
||||||
|
self.last_state = False
|
||||||
|
self.cooldown = 5
|
||||||
|
|
||||||
|
def set(self, s, on):
|
||||||
|
if on != self.last_state:
|
||||||
|
if (time.time() - self.last_toggle) < self.cooldown:
|
||||||
|
return
|
||||||
|
if on:
|
||||||
|
s.write(b"Z")
|
||||||
|
s.flush()
|
||||||
|
else:
|
||||||
|
s.write(b"z")
|
||||||
|
s.flush()
|
||||||
|
if self.last_state != on:
|
||||||
|
self.last_toggle = time.time()
|
||||||
|
self.last_state = on
|
|
@ -0,0 +1,43 @@
|
||||||
|
import numpy as np
|
||||||
|
import time
|
||||||
|
|
||||||
|
class MockSerial:
|
||||||
|
def __init__(self):
|
||||||
|
self.humidity = np.zeros(100)
|
||||||
|
self.humidifier_on = False
|
||||||
|
self.humidity[:] = 80
|
||||||
|
self.humidity[-1] = 20
|
||||||
|
self.humidity[0] = 20
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, msg):
|
||||||
|
if msg == b'h':
|
||||||
|
print("mock hum toggle")
|
||||||
|
self.humidifier_on = not self.humidifier_on
|
||||||
|
|
||||||
|
def read(self, _):
|
||||||
|
t = time.time()
|
||||||
|
temp = 25 + np.sin(0.01*2*np.pi*t) + 0.5*np.sin(0.0001*2*np.pi*t + 7)
|
||||||
|
|
||||||
|
# very janky model of humidity diffusion
|
||||||
|
# fix end conditions
|
||||||
|
for _ in range(20):
|
||||||
|
self.humidity[-1] = 0.2*20 + 0.8*self.humidity[-2]
|
||||||
|
self.humidity[0] = 20
|
||||||
|
if self.humidifier_on:
|
||||||
|
self.humidity[20] = 2
|
||||||
|
# use the gradient to determine the change in humidity
|
||||||
|
avg = 0.5*(self.humidity[:-2] + self.humidity[2:])
|
||||||
|
self.humidity[1:-1] += 0.10*(avg - self.humidity[1:-1])
|
||||||
|
#print(self.humidity)
|
||||||
|
|
||||||
|
humidity = self.humidity[60] + np.random.random()*0.003
|
||||||
|
if self.humidifier_on:
|
||||||
|
hv = 3.3
|
||||||
|
else:
|
||||||
|
hv = 0.0
|
||||||
|
return bytes("{},{},{}\n".format(humidity, temp, hv), "utf8")
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,10 @@ import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from humidifier import Humidifier, HumidifierV2
|
||||||
|
from humidifier_v3 import HumidifierV3
|
||||||
|
from controller import Controller
|
||||||
|
|
||||||
SERIAL_PATH = "/dev/ttyACM0"
|
SERIAL_PATH = "/dev/ttyACM0"
|
||||||
SERIAL_BAUD = 115200
|
SERIAL_BAUD = 115200
|
||||||
|
|
||||||
|
@ -25,7 +29,8 @@ def start_process():
|
||||||
if is_mock:
|
if is_mock:
|
||||||
process = subprocess.Popen(["/usr/bin/env", "python", "/home/kelvin/src/shroom-server/shroom_pipe.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
process = subprocess.Popen(["/usr/bin/env", "python", "/home/kelvin/src/shroom-server/shroom_pipe.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
else:
|
else:
|
||||||
process = subprocess.Popen(["ssh", "shrooms@threefortiethofonehamster.com", "/usr/bin/env", "python3", "/home/shrooms/shrooms-server/shroom_pipe.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
#process = subprocess.Popen(["ssh", "shrooms@threefortiethofonehamster.com", "/usr/bin/env", "python3", "/home/shrooms/shrooms-server/shroom_pipe.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
process = subprocess.Popen(["ssh", "shrooms@35.211.7.97", "/usr/bin/env", "python3", "/home/shrooms/shrooms-server/shroom_pipe.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
start_process()
|
start_process()
|
||||||
|
|
||||||
def send_update(msg):
|
def send_update(msg):
|
||||||
|
@ -33,47 +38,9 @@ def send_update(msg):
|
||||||
process.stdin.write(bytes(json.dumps(msg) + "\n", "utf8"))
|
process.stdin.write(bytes(json.dumps(msg) + "\n", "utf8"))
|
||||||
process.stdin.flush()
|
process.stdin.flush()
|
||||||
|
|
||||||
class MockSerial:
|
|
||||||
def __init__(self):
|
|
||||||
self.humidity = np.zeros(100)
|
|
||||||
self.humidifier_on = False
|
|
||||||
self.humidity[:] = 80
|
|
||||||
self.humidity[-1] = 20
|
|
||||||
self.humidity[0] = 20
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def write(self, msg):
|
|
||||||
if msg == b'h':
|
|
||||||
print("mock hum toggle")
|
|
||||||
self.humidifier_on = not self.humidifier_on
|
|
||||||
|
|
||||||
def read(self, _):
|
|
||||||
t = time.time()
|
|
||||||
temp = 25 + np.sin(0.01*2*np.pi*t) + 0.5*np.sin(0.0001*2*np.pi*t + 7)
|
|
||||||
|
|
||||||
# very janky model of humidity diffusion
|
|
||||||
# fix end conditions
|
|
||||||
for _ in range(20):
|
|
||||||
self.humidity[-1] = 0.2*20 + 0.8*self.humidity[-2]
|
|
||||||
self.humidity[0] = 20
|
|
||||||
if self.humidifier_on:
|
|
||||||
self.humidity[20] = 2
|
|
||||||
# use the gradient to determine the change in humidity
|
|
||||||
avg = 0.5*(self.humidity[:-2] + self.humidity[2:])
|
|
||||||
self.humidity[1:-1] += 0.10*(avg - self.humidity[1:-1])
|
|
||||||
#print(self.humidity)
|
|
||||||
|
|
||||||
humidity = self.humidity[60] + np.random.random()*0.003
|
|
||||||
if self.humidifier_on:
|
|
||||||
hv = 3.3
|
|
||||||
else:
|
|
||||||
hv = 0.0
|
|
||||||
return bytes("{},{},{}\n".format(humidity, temp, hv), "utf8")
|
|
||||||
|
|
||||||
if is_mock:
|
if is_mock:
|
||||||
s = MockSerial()
|
import mock_serial
|
||||||
|
s = mock_serial.MockSerial()
|
||||||
else:
|
else:
|
||||||
s = serial.Serial(SERIAL_PATH, SERIAL_BAUD, timeout = 0.3)
|
s = serial.Serial(SERIAL_PATH, SERIAL_BAUD, timeout = 0.3)
|
||||||
print("pausing for bootloader...")
|
print("pausing for bootloader...")
|
||||||
|
@ -87,175 +54,8 @@ def reset_serial():
|
||||||
s = serial.Serial(SERIAL_PATH, SERIAL_BAUD, timeout = 0.3)
|
s = serial.Serial(SERIAL_PATH, SERIAL_BAUD, timeout = 0.3)
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
class Humidifier:
|
humidifier = HumidifierV3()
|
||||||
def __init__(self):
|
controller = Controller([humidifier])
|
||||||
self.off_threshold = 0.4
|
|
||||||
self.on_threshold = 1.9
|
|
||||||
self.toggle_cooldown = 7
|
|
||||||
|
|
||||||
self._on = False
|
|
||||||
self.history = np.zeros(10)
|
|
||||||
self.switch_timeout = 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def on(self):
|
|
||||||
return self._on
|
|
||||||
|
|
||||||
@on.setter
|
|
||||||
def on(self, nv):
|
|
||||||
old_on = self._on
|
|
||||||
self._on = nv
|
|
||||||
if nv:
|
|
||||||
print("send hum on")
|
|
||||||
else:
|
|
||||||
print("send hum off")
|
|
||||||
send_update({"status": {"humidifier": nv}})
|
|
||||||
|
|
||||||
@property
|
|
||||||
def off(self):
|
|
||||||
return not self.on
|
|
||||||
|
|
||||||
def update(self, volts):
|
|
||||||
self.history[1:] = self.history[:-1]
|
|
||||||
self.history[0] = volts
|
|
||||||
#print(self.history)
|
|
||||||
avg = np.sum(self.history)/self.history.shape[0]
|
|
||||||
if self.on:
|
|
||||||
if avg < self.off_threshold:
|
|
||||||
self.on = False
|
|
||||||
self.switch_timeout = time.time() + 1
|
|
||||||
else:
|
|
||||||
if avg > self.on_threshold:
|
|
||||||
self.on = True
|
|
||||||
self.switch_timeout = time.time() + 1
|
|
||||||
|
|
||||||
def toggle(self, s):
|
|
||||||
if time.time() > self.switch_timeout:
|
|
||||||
s.write(b"h")
|
|
||||||
s.flush()
|
|
||||||
self.switch_timeout = time.time() + self.toggle_cooldown
|
|
||||||
|
|
||||||
# the wiring's a little different so the thresholds for detecting on/off are inverted but hopefully a lot more reliable than the original
|
|
||||||
class HumidifierV2:
|
|
||||||
def __init__(self, toggle_cmd=b"i", humidifier_id="humidifier2"):
|
|
||||||
self.on_threshold = 1.5
|
|
||||||
self.off_threshold = 2.5
|
|
||||||
self.toggle_cooldown = 7
|
|
||||||
self.toggle_command = toggle_cmd
|
|
||||||
|
|
||||||
self._on = False
|
|
||||||
self.history = np.zeros(10)
|
|
||||||
self.switch_timeout = 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def on(self):
|
|
||||||
return self._on
|
|
||||||
|
|
||||||
@on.setter
|
|
||||||
def on(self, nv):
|
|
||||||
old_on = self._on
|
|
||||||
self._on = nv
|
|
||||||
if nv:
|
|
||||||
print("send hum 2 on")
|
|
||||||
else:
|
|
||||||
print("send hum 2 off")
|
|
||||||
send_update({"status": {humidifier_id: nv}})
|
|
||||||
|
|
||||||
@property
|
|
||||||
def off(self):
|
|
||||||
return not self.on
|
|
||||||
|
|
||||||
def update(self, volts):
|
|
||||||
self.history[1:] = self.history[:-1]
|
|
||||||
self.history[0] = volts
|
|
||||||
#print(self.history)
|
|
||||||
avg = np.sum(self.history)/self.history.shape[0]
|
|
||||||
if self.on:
|
|
||||||
if avg > self.off_threshold:
|
|
||||||
self.on = False
|
|
||||||
self.switch_timeout = time.time() + 1
|
|
||||||
else:
|
|
||||||
if avg < self.on_threshold:
|
|
||||||
self.on = True
|
|
||||||
self.switch_timeout = time.time() + 1
|
|
||||||
|
|
||||||
def toggle(self, s):
|
|
||||||
if time.time() > self.switch_timeout:
|
|
||||||
s.write(self.toggle_command)
|
|
||||||
s.flush()
|
|
||||||
self.switch_timeout = time.time() + self.toggle_cooldown
|
|
||||||
|
|
||||||
|
|
||||||
class Controller:
|
|
||||||
def __init__(self):
|
|
||||||
self.target_lower = 85
|
|
||||||
self.target_upper = 90
|
|
||||||
self.feedforward_coeff = 50
|
|
||||||
self.last_toggle = 0
|
|
||||||
|
|
||||||
self._manual_mode = False
|
|
||||||
self.manual_on = False
|
|
||||||
self.manual_timeout = 0
|
|
||||||
self.manual_duration = 40
|
|
||||||
|
|
||||||
self.humidifier_history = np.zeros(50)
|
|
||||||
self.first_sample = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def manual_mode(self):
|
|
||||||
return self._manual_mode
|
|
||||||
|
|
||||||
@manual_mode.setter
|
|
||||||
def manual_mode(self, on):
|
|
||||||
self._manual_mode = on
|
|
||||||
send_update({"status": {"manual_mode": on}})
|
|
||||||
|
|
||||||
def toggle_checked(self, humidifier, s):
|
|
||||||
if time.time() - self.last_toggle > 0.8:
|
|
||||||
humidifier.toggle(s)
|
|
||||||
self.last_toggle = time.time()
|
|
||||||
|
|
||||||
def update(self, humidifier, humidifier2, humidity):
|
|
||||||
if self.first_sample:
|
|
||||||
self.humidifier_history[:] = humidity
|
|
||||||
self.first_sample = False
|
|
||||||
else:
|
|
||||||
self.humidifier_history[:-1] = self.humidifier_history[1:]
|
|
||||||
self.humidifier_history[-1] = humidity
|
|
||||||
|
|
||||||
# compensate for the slow response time by adding a little feed forward
|
|
||||||
# using the slope of the humidifier data
|
|
||||||
slope = (self.humidifier_history[-1] - self.humidifier_history[0])/self.humidifier_history.shape[0]
|
|
||||||
comp_humidity = humidity + self.feedforward_coeff*slope
|
|
||||||
|
|
||||||
if self.manual_mode and time.time() > self.manual_timeout:
|
|
||||||
self.manual_mode = False
|
|
||||||
|
|
||||||
# TODO add in support for manual control of second humidifier
|
|
||||||
if self.manual_mode:
|
|
||||||
if humidifier.off and self.manual_on:
|
|
||||||
self.toggle_checked(humidifier, s)
|
|
||||||
elif humidifier.on and not self.manual_on:
|
|
||||||
self.toggle_checked(humidifier, s)
|
|
||||||
if humidifier2.off and self.manual_on:
|
|
||||||
self.toggle_checked(humidifier2, s)
|
|
||||||
elif humidifier2.on and not self.manual_on:
|
|
||||||
self.toggle_checked(humidifier2, s)
|
|
||||||
else:
|
|
||||||
if comp_humidity < self.target_lower:
|
|
||||||
if humidifier.off:
|
|
||||||
self.toggle_checked(humidifier, s)
|
|
||||||
if humidifier2.off:
|
|
||||||
self.toggle_checked(humidifier2, s)
|
|
||||||
elif comp_humidity > self.target_upper:
|
|
||||||
if humidifier.on:
|
|
||||||
self.toggle_checked(humidifier, s)
|
|
||||||
if humidifier2.on:
|
|
||||||
self.toggle_checked(humidifier2, s)
|
|
||||||
|
|
||||||
humidifier = HumidifierV2(b"h", "humidifier")
|
|
||||||
humidifier2 = HumidifierV2()
|
|
||||||
controller = Controller()
|
|
||||||
|
|
||||||
exiting = False
|
exiting = False
|
||||||
# run thread to process data from process's stdout
|
# run thread to process data from process's stdout
|
||||||
|
@ -346,13 +146,12 @@ try:
|
||||||
parts = resp.split(b",")
|
parts = resp.split(b",")
|
||||||
humidity = float(parts[0])
|
humidity = float(parts[0])
|
||||||
temp = float(parts[1])
|
temp = float(parts[1])
|
||||||
volts = float(parts[2])
|
#volts = float(parts[2])
|
||||||
volts2 = float(parts[3])
|
#volts2 = float(parts[3])
|
||||||
|
#print(parts)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
humidifier.update(volts)
|
controller.update(humidity)
|
||||||
humidifier2.update(volts2)
|
|
||||||
controller.update(humidifier, humidifier2, humidity)
|
|
||||||
|
|
||||||
if frame_num == 0:
|
if frame_num == 0:
|
||||||
#print(humidity, temp, volts)
|
#print(humidity, temp, volts)
|
||||||
|
@ -361,8 +160,8 @@ try:
|
||||||
"time": int(now*1000),
|
"time": int(now*1000),
|
||||||
"temp": temp,
|
"temp": temp,
|
||||||
"hum": humidity,
|
"hum": humidity,
|
||||||
"hv": volts,
|
"hv": 1 if humidifier.on else 0,
|
||||||
"hv2": volts2,
|
#"hv2": volts2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
send_update(update)
|
send_update(update)
|
||||||
|
|
Loading…
Reference in New Issue