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()