From 3baa19d145656f6252be391fc1ec1cd766fcb32a Mon Sep 17 00:00:00 2001 From: Kelvin Ly Date: Tue, 16 May 2023 15:42:57 -0400 Subject: [PATCH] Implement manual mode controls and thread manual mode status logic from the controller to the front end --- dev/admin.htm | 100 ++++++++++++++++++++++++++++++--- shroom_controller.py | 33 ++++++++--- shroom_internals/tcp_server.go | 5 +- shroom_server.go | 3 + static/index.js | 47 ++++++++++------ 5 files changed, 155 insertions(+), 33 deletions(-) diff --git a/dev/admin.htm b/dev/admin.htm index 3dc024f..2ef3c8b 100644 --- a/dev/admin.htm +++ b/dev/admin.htm @@ -6,14 +6,13 @@ function status(msg) { document.getElementById('status').textContent = msg } async function queryParams() { - const req = await fetch('/api/params') - const json = await req.json() + const resp = await fetch('/api/params') //console.log(json) //console.log(Object.keys(json)) - if (!req.ok) { + if (!resp.ok) { var err_msg = null - if (req.body != null) { - err_msg = await req.text() + if (resp.body != null) { + err_msg = await resp.text() } if (err_msg != null) { status("query failed: " + resp.status + " " + err_msg) @@ -21,6 +20,7 @@ async function queryParams() { status("query failed: " + resp.status) } } + const json = await resp.json() status('parameter query successful!') const paramList = document.getElementById('param-list') while (paramList.firstChild) { @@ -43,12 +43,10 @@ async function setParam(auth, name, value) { } } }) - console.log('set param fetch pre') const resp = await fetch('/api/admin', { method: 'POST', body: msg }) - console.log('set param fetch post') if (!resp.ok) { var err_msg = null if (resp.body != null) { @@ -65,6 +63,78 @@ async function setParam(auth, name, value) { } } +var manual_mode = false +var manual_loop_running = false +const sleep = (ms) => new Promise(r => setTimeout(r, ms)) +async function manualModeLoop(auth) { + if (manual_loop_running) return + manual_loop_running = true + try { + while (manual_mode) { + await sleep(60*1000) + if (manual_mode) await manualMode(auth, manual_mode) + } + } finally { + manual_loop_running = false + } +} +async function manualMode(auth, on) { + const msg = JSON.stringify({ + auth: auth, + data: { + manual_mode: on + } + }) + const resp = await fetch('/api/admin', { + method: 'POST', + body: msg + }) + if (!resp.ok) { + var err_msg = null + if (resp.body != null) { + err_msg = await resp.text() + } + if (err_msg != null) { + status("manual mode set failed: " + resp.status + " " + err_msg) + } else { + status("manual mode set failed: " + resp.status) + } + } else { + status("manual mode set successful!") + manual_mode = on + if (manual_mode) { + manualModeLoop(auth) + } + //await queryParams() + } +} + +async function manualHumidifier(auth, on) { + const msg = JSON.stringify({ + auth: auth, + data: { + manual_mode_on: on + } + }) + const resp = await fetch('/api/admin', { + method: 'POST', + body: msg + }) + if (!resp.ok) { + var err_msg = null + if (resp.body != null) { + err_msg = await resp.text() + } + if (err_msg != null) { + status("manual hum set failed: " + resp.status + " " + err_msg) + } else { + status("manual hum set failed: " + resp.status) + } + } else { + status("manual hum set successful!") + } +} + window.onload = () => { document.getElementById('query-params').addEventListener('click', (e) => { queryParams() @@ -80,6 +150,17 @@ window.onload = () => { } setParam(auth, name, value) }) + + document.getElementById('manual-mode').addEventListener('click', (e) => { + const auth = document.getElementById('password').value + const on = document.getElementById('manual-mode').checked + manualMode(auth, on) + }) + document.getElementById('manual-on').addEventListener('click', (e) => { + const auth = document.getElementById('password').value + const on = document.getElementById('manual-on').checked + manualHumidifier(auth, on) + }) } @@ -104,6 +185,11 @@ window.onload = () => {

+
+ Manual mode +
+
+
diff --git a/shroom_controller.py b/shroom_controller.py index 2a4fda4..f1a3f41 100644 --- a/shroom_controller.py +++ b/shroom_controller.py @@ -77,10 +77,24 @@ class Humidifier: self.on_threshold = 2.6 self.toggle_cooldown = 7 - self.on = False + 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 @@ -93,14 +107,10 @@ class Humidifier: if self.on: if avg < self.off_threshold: self.on = False - print("send status off") - send_update({"status": {"humidifier": False}}) self.switch_timeout = time.time() + 1 else: if avg > self.on_threshold: self.on = True - print("send status on") - send_update({"status": {"humidifier": True}}) self.switch_timeout = time.time() + 1 def toggle(self, s): @@ -114,7 +124,7 @@ class Controller: self.target_upper = 0.90 self.feedforward_coeff = 50 - self.manual_mode = False + self._manual_mode = False self.manual_on = False self.manual_timeout = 0 self.manual_duration = 120 @@ -122,6 +132,15 @@ class Controller: self.humidifier_history = np.zeros(30) 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 update(self, humidifier, humidity): if self.first_sample: self.humidifier_history[:] = humidity @@ -141,7 +160,7 @@ class Controller: if self.manual_mode: if humidifier.off and self.manual_on: humidifier.toggle(s) - elif humdifier.on and not self.manual_on: + elif humidifier.on and not self.manual_on: humidifier.toggle(s) else: if comp_humidity < self.target_lower and humidifier.off: diff --git a/shroom_internals/tcp_server.go b/shroom_internals/tcp_server.go index c5873a5..37aff87 100644 --- a/shroom_internals/tcp_server.go +++ b/shroom_internals/tcp_server.go @@ -40,6 +40,7 @@ type ShroomPacket struct { type ShroomState struct { sync.RWMutex HumidifierOn bool + ManualMode bool NumConnections int Params map[string]float32 @@ -162,10 +163,12 @@ func parseMsg(line []byte, db *sql.DB, state *ShroomState) { } else if packet.Status != nil { //log.Println("received status ", data.Status) state.Lock() - // TODO change to have more detailed data if packet.Status.HumOn != nil { state.HumidifierOn = *packet.Status.HumOn } + if packet.Status.ManualMode != nil { + state.ManualMode = *packet.Status.ManualMode + } state.Unlock() state.StatusUpdate() } else if packet.Params != nil { diff --git a/shroom_server.go b/shroom_server.go index feb918c..4c6a7ed 100644 --- a/shroom_server.go +++ b/shroom_server.go @@ -30,6 +30,7 @@ var content embed.FS type statusJson struct { Connected bool `json:"connected"` Humidifier bool `json:"humidifier"` + ManualMode bool `json:"manual_mode"` } type adminMsg struct { @@ -103,10 +104,12 @@ func main() { state.RLock() num_connections := state.NumConnections humidifier := state.HumidifierOn + manual_mode := state.ManualMode state.RUnlock() s := statusJson{ Connected: num_connections > 0, Humidifier: humidifier, + ManualMode: manual_mode, } msg, err := json.Marshal(s) if err != nil { diff --git a/static/index.js b/static/index.js index 82e0562..08912a1 100644 --- a/static/index.js +++ b/static/index.js @@ -155,9 +155,7 @@ async function updateCharts() { } if (autoupdate) { - chart_updater = chartupdater() - } else { - chart_updater = null + chartupdater() } } @@ -165,36 +163,49 @@ const sleep = (ms) => new Promise(r => setTimeout(r, ms)) var chart_update_millis = 2000 async function chartupdater() { - if (chart_updater != null) return + if (chart_updater) return + chart_updater = true - await sleep(chart_update_millis) - // wait at least two seconds to avoid wasting a lot of bandwidth - const resp = await fetch("/api/update") - chart_updater = null - updateCharts() + try { + while (autoupdate) { + await sleep(chart_update_millis) + // wait at least two seconds to avoid wasting a lot of bandwidth + const resp = await fetch("/api/update") + updateCharts() + } + } finally { + chart_updater = false + } } -var status_updater = null +var status_updater = false async function updateStatus() { const status_resp = await fetch("/api/status") const status = await status_resp.json() //console.log(status) const humidifier_state = (status.humidifier ? "on" : "off") + const manual_mode_state = (status.manual_mode ? "on" : "off") //console.log(humidifier_state) - document.getElementById("device-status").textContent = "connected: " + status.connected + ", humidifier: " + humidifier_state + document.getElementById("device-status").textContent = "connected: " + + status.connected + ", manual mode: " + manual_mode_state + ", humidifier: " + + humidifier_state if (autoupdate) { - status_updater = waitThenUpdateStatus() - } else { - status_updater = null + waitThenUpdateStatus() } } async function waitThenUpdateStatus() { - if (status_updater != null) return - await fetch("/api/status_update") - status_updater = null - updateStatus() + if (status_updater) return + status_updater = true + try { + while (autoupdate) { + await fetch("/api/status_update") + updateStatus() + } + } finally { + status_updater = false + } } async function testAdminMode() {