diff --git a/shroom_controller.py b/shroom_controller.py index f5577a1..1886d32 100644 --- a/shroom_controller.py +++ b/shroom_controller.py @@ -1,37 +1,97 @@ import numpy as np +import json import serial import subprocess +import threading import time -s = serial.Serial("/dev/ttyUSB0", 115200, timeout=10) -q = queue.Queue() +#process = subprocess.Popen(["ssh", "shrooms@localhost", "/usr/bin/env", "python", "/home/shrooms/go/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) -process = subprocess.Popen(["ssh", "shrooms@threefortiethofonehamster.com", "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() -# TODO run thread to process data from process's stdout +exiting = False +# run thread to process data from process's stdout +def stdout_loop(): + while not exiting: + msg = process.stdout.readline() + print("got message ", msg) +stdout_thread = threading.Thread(target=stdout_loop) +stdout_thread.start() + +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] = 1.5 + # use the gradient to determine the change in humidity + avg = 0.5*(self.humidity[:-2] + self.humidity[2:]) + self.humidity[1:-1] += 0.2*(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") + +s = MockSerial() def reset_serial(): - s.close() - s = serial.Serial("/dev/ttyUSB0", 115200, timeout = 10) + pass time.sleep(10) class Humidifier: def __init__(self): self.on = False - self.history = np.array(30) + 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.state: + if self.on: if avg < 0.2: self.on = False + print("send status off") + send_update({"status": 0}) + self.switch_timeout = time.time() + 1 else: if avg > 2.6: self.on = True + print("send status on") + send_update({"status": 1}) + self.switch_timeout = time.time() + 1 def toggle(self, s): if time.time() > self.switch_timeout: @@ -40,34 +100,73 @@ class Humidifier: humidifier = Humidifier() target_lower = 0.85 -target_higher = 0.90 +target_upper = 0.90 +feedforward_coeff = 50 + +humidifier_history = np.zeros(30) +first_sample = False try: last_sample = 0 while True: now = time.time() if now - last_sample < 0.5: - 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]) - print(humidity, temp, volts) + time.sleep(0.5 - (now - last_sample)) + continue + last_sample = now - humidifier.update(volts) - if humidity < target_lower and humidifier.off: - humidifier.toggle(s) - elif humidity > target_upper and humidifier.on: - humidifier.toggle(s) - # TODO check on the process + 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]) + print(humidity, temp, volts) + if first_sample: + humidifier_history[:] = humidity + first_sample = False else: - time.sleep(0.5-(now - last_sample)) + 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) + + update = { + "time": int(now*1000), + "temp": temp, + "hum": humidity, + "hv": volts + } + send_update(update) + 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: - # TODO kill ssh connection - process. - pass + # kill ssh connection + exiting = True + process.kill() + stdout_thread.join() diff --git a/shroom_controller_mock.py b/shroom_controller_mock.py deleted file mode 100644 index 90b5b66..0000000 --- a/shroom_controller_mock.py +++ /dev/null @@ -1,170 +0,0 @@ -import numpy as np - -import json -import serial -import subprocess -import threading -import time - -#process = subprocess.Popen(["ssh", "shrooms@localhost", "/usr/bin/env", "python", "/home/shrooms/go/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) - -def send_update(msg): - global process - process.stdin.write(bytes(json.dumps(msg) + "\n", "utf8")) - process.stdin.flush() - -exiting = False -# run thread to process data from process's stdout -def stdout_loop(): - while not exiting: - msg = process.stdout.readline() - print("got message ", msg) -stdout_thread = threading.Thread(target=stdout_loop) -stdout_thread.start() - -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.2*(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") - -s = MockSerial() - -def reset_serial(): - pass - 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": 0}) - self.switch_timeout = time.time() + 1 - else: - if avg > 2.6: - self.on = True - print("send status on") - send_update({"status": 1}) - 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 - -humidifier_history = np.zeros(30) -first_sample = False -try: - last_sample = 0 - while True: - now = time.time() - if now - last_sample < 0.5: - time.sleep(0.5 - (now - last_sample)) - 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]) - print(humidity, temp, volts) - 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 - slope = (humidifier_history[-1] - humidifier_history[0])/humidifier_history.shape[0] - comp_humidity = humidity + 50*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) - - update = { - "time": int(now*1000), - "temp": temp, - "hum": humidity, - "hv": volts - } - send_update(update) - 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()