220 lines
6.2 KiB
Python
220 lines
6.2 KiB
Python
import numpy as np
|
|
|
|
import json
|
|
import os
|
|
import serial
|
|
import subprocess
|
|
import threading
|
|
import time
|
|
|
|
SERIAL_PATH = "/dev/ttyUSB0"
|
|
SERIAL_BAUD = 115200
|
|
|
|
SAMPLE_PERIOD = 0.2
|
|
DECIMATION_RATE = 5
|
|
|
|
is_mock = os.environ['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)
|
|
else:
|
|
process = subprocess.Popen(["ssh", "shrooms@threefortiethofonehamster.com", "/usr/bin/env", "python", "/home/shrooms/go/src/shroom-server/shroom-pipe.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
|
|
def send_update(msg):
|
|
global process
|
|
process.stdin.write(bytes(json.dumps(msg) + "\n", "utf8"))
|
|
process.stdin.flush()
|
|
|
|
class MockSerial:
|
|
def __init__(self):
|
|
self.humidity = np.zeros(100)
|
|
self.humidifier_on = False
|
|
self.humidity[:] = 0.80
|
|
self.humidity[-1] = 0.20
|
|
self.humidity[0] = 0.20
|
|
|
|
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*0.20 + 0.8*self.humidity[-2]
|
|
self.humidity[0] = 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:
|
|
s = MockSerial()
|
|
else:
|
|
s = serial.Serial(SERIAL_PATH, SERIAL_BAUD, timeout = 10)
|
|
|
|
def reset_serial():
|
|
if not is_mock:
|
|
s.close()
|
|
s = serial.Serial(SERIAL_PATH, SERIAL_BAUD, timeout = 10)
|
|
time.sleep(10)
|
|
|
|
class Humidifier:
|
|
def __init__(self):
|
|
self.on = False
|
|
self.history = np.zeros(10)
|
|
self.switch_timeout = 0
|
|
|
|
@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 < 0.2:
|
|
self.on = False
|
|
print("send status off")
|
|
send_update({"status": {"humidifier": False}})
|
|
self.switch_timeout = time.time() + 1
|
|
else:
|
|
if avg > 2.6:
|
|
self.on = True
|
|
print("send status on")
|
|
send_update({"status": {"humidifier": True}})
|
|
self.switch_timeout = time.time() + 1
|
|
|
|
def toggle(self, s):
|
|
if time.time() > self.switch_timeout:
|
|
s.write(b"h")
|
|
self.switch_timeout = time.time() + 7
|
|
|
|
humidifier = Humidifier()
|
|
target_lower = 0.85
|
|
target_upper = 0.90
|
|
feedforward_coeff = 50
|
|
|
|
exiting = False
|
|
# run thread to process data from process's stdout
|
|
def stdout_loop():
|
|
global process, target_lower, target_upper, feedforward_coeff
|
|
while not exiting:
|
|
msg = process.stdout.readline()
|
|
if len(msg) == 0:
|
|
continue
|
|
print("got message ", msg)
|
|
try:
|
|
msg_js = json.loads(msg)
|
|
if "query_params" in msg_js:
|
|
if msg_js["query_params"]:
|
|
send_update({"params": {
|
|
"target_lower": target_lower,
|
|
"target_upper": target_upper,
|
|
"feedforward_coeff": feedforward_coeff
|
|
}
|
|
})
|
|
elif "set_params" in msg_js:
|
|
if type(msg_js["set_params"]) is dict:
|
|
set_params = msg_js["set_params"]
|
|
if "name" in set_params and "value" in set_params:
|
|
if type(set_params["value"]) is float:
|
|
if set_params["name"] == "target_lower":
|
|
target_lower = set_params["value"]
|
|
elif set_params["name"] == "target_upper":
|
|
target_upper = set_params["value"]
|
|
elif set_params["name"] == "feedforward_coeff":
|
|
feedforward_coeff = set_params["value"]
|
|
except json.JSONDecodeError as e:
|
|
print("received bad json ", msg)
|
|
stdout_thread = threading.Thread(target=stdout_loop)
|
|
stdout_thread.start()
|
|
|
|
|
|
humidifier_history = np.zeros(30)
|
|
first_sample = False
|
|
frame_num = 0
|
|
try:
|
|
last_sample = 0
|
|
while True:
|
|
now = time.time()
|
|
if now - last_sample < SAMPLE_PERIOD:
|
|
time.sleep(SAMPLE_PERIOD - (now - last_sample) + 0.001)
|
|
continue
|
|
last_sample = now
|
|
|
|
s.write(b"s")
|
|
resp = s.read(120)
|
|
if len(resp) == 0:
|
|
reset_serial()
|
|
time.sleep(5)
|
|
continue
|
|
parts = resp.split(b",")
|
|
humidity = float(parts[0])
|
|
temp = float(parts[1])
|
|
volts = float(parts[2])
|
|
if first_sample:
|
|
humidifier_history[:] = humidity
|
|
first_sample = False
|
|
else:
|
|
humidifier_history[:-1] = humidifier_history[1:]
|
|
humidifier_history[-1] = humidity
|
|
|
|
# compensate for the slow response time by adding a little feed forward
|
|
# using the slope of the humidifier data
|
|
slope = (humidifier_history[-1] - humidifier_history[0])/humidifier_history.shape[0]
|
|
comp_humidity = humidity + feedforward_coeff*slope
|
|
|
|
try:
|
|
humidifier.update(volts)
|
|
if comp_humidity < target_lower and humidifier.off:
|
|
humidifier.toggle(s)
|
|
elif comp_humidity > target_upper and humidifier.on:
|
|
humidifier.toggle(s)
|
|
|
|
if frame_num == 0:
|
|
print(humidity, temp, volts)
|
|
update = {
|
|
"data": {
|
|
"time": int(now*1000),
|
|
"temp": temp,
|
|
"hum": humidity,
|
|
"hv": volts
|
|
}
|
|
}
|
|
send_update(update)
|
|
frame_num = (frame_num + 1) % DECIMATION_RATE
|
|
except Exception as e:
|
|
print("pipe errored out, restarting: ", e)
|
|
# restart the process I guess
|
|
exiting = True
|
|
process.kill()
|
|
time.sleep(0.1)
|
|
stdout_thread.join()
|
|
exiting = False
|
|
|
|
process = subprocess.Popen(["/usr/bin/env", "python", "/home/kelvin/src/shroom-server/shroom_pipe.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
|
|
stdout_thread = threading.Thread(target=stdout_loop)
|
|
stdout_thread.start()
|
|
|
|
finally:
|
|
# kill ssh connection
|
|
exiting = True
|
|
process.kill()
|
|
stdout_thread.join()
|