From 3a7bb603601e3eb616e23f11f1ed04a820e76f5b Mon Sep 17 00:00:00 2001 From: Kelvin Ly Date: Mon, 15 May 2023 16:38:37 -0400 Subject: [PATCH] Test out admin mode commands; it looks like the basic idea works for now at least --- dev/dev.htm | 20 ++++++++++++- shroom_internals/tcp_server.go | 31 +++++++++++++++++-- shroom_server.go | 54 +++++++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/dev/dev.htm b/dev/dev.htm index 4f1b702..446f109 100644 --- a/dev/dev.htm +++ b/dev/dev.htm @@ -166,11 +166,12 @@ async function updateCharts() { } const sleep = (ms) => new Promise(r => setTimeout(r, ms)) +var chart_update_millis = 2000 async function chartupdater() { if (chart_updater != null) return - await sleep(2000) + 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 @@ -200,6 +201,19 @@ async function waitThenUpdateStatus() { updateStatus() } +async function testAdminMode() { + const msg = JSON.stringify({ + auth: "password", + data: { + manual_mode: true + } + }) + await fetch("/api/admin", { + method: "POST", + body: msg + }) +} + window.onload = () => { initCharts() updateCharts() @@ -212,6 +226,9 @@ window.onload = () => { document.getElementById('autoupdate').addEventListener('click', (e) => { autoupdate = document.getElementById('autoupdate').checked }) + document.getElementById('test-admin').addEventListener('click', (e) => { + testAdminMode() + }) } @@ -223,6 +240,7 @@ window.onload = () => {
Autoupdate +
diff --git a/shroom_internals/tcp_server.go b/shroom_internals/tcp_server.go index bf32325..20b5958 100644 --- a/shroom_internals/tcp_server.go +++ b/shroom_internals/tcp_server.go @@ -31,6 +31,8 @@ type ShroomStatus struct { NumConnections int Wait chan struct{} StatusWait chan struct{} + + Commands chan []byte } func (s *ShroomStatus) Update() { @@ -71,7 +73,7 @@ func parseMsg(line []byte, db *sql.DB, status *ShroomStatus) { // we got a data packet status.Update() } else if data.Status != -1 { - log.Println("received status ", data.Status) + //log.Println("received status ", data.Status) status.Lock() // TODO change to have more detailed data status.HumidifierOn = (data.Status & 1) == 1 @@ -96,6 +98,12 @@ func InitTcpServer(db *sql.DB, status *ShroomStatus) { log.Println("tcp accept error: ", err) return } + // not spawning a goroutine here + // should limit the number of connections to + // one hopefully + + // wrapping in a func() so that I can use defer + // to automatically decrement the number of connections func() { status.Lock() status.NumConnections += 1 @@ -112,8 +120,27 @@ func InitTcpServer(db *sql.DB, status *ShroomStatus) { log.Println("connection started") + // write loop; waits for commands and forwards them + exiting := make(chan struct{}) go func() { - // TODO deal with the write side of the connection + for { + select { + case v, ok := <-status.Commands: + if !ok { + return + } + v = append(v, []byte("\n")...) + _, err := conn.Write(v) + if err != nil { + log.Println("tcp write err: ", err) + } + case <-exiting: + return + } + } + }() + defer func() { + close(exiting) }() // deal with the read side of the connection diff --git a/shroom_server.go b/shroom_server.go index 4007fd9..0b194fb 100644 --- a/shroom_server.go +++ b/shroom_server.go @@ -29,6 +29,11 @@ type statusJson struct { Humidifier bool `json:"humidifier"` } +type adminMsg struct { + Auth string `json:"auth"` + Msg map[string]interface{} `json:"data"` +} + func dumpData(db *sql.DB, multiplier int64) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { now := time.Now().Unix() @@ -70,6 +75,7 @@ func main() { status := s.ShroomStatus{ Wait: make(chan struct{}), StatusWait: make(chan struct{}), + Commands: make(chan []byte), } s.InitTcpServer(db, &status) @@ -106,7 +112,7 @@ func main() { msg, err := json.Marshal(s) if err != nil { err = fmt.Errorf("unable to marshal json: %w", err) - w.WriteHeader(500) + w.WriteHeader(400) w.Write([]byte(err.Error())) } else { w.Write(msg) @@ -114,9 +120,49 @@ func main() { } adminHandler := func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(500) - w.Write([]byte("unimplemented")) - // TODO + if req.Method != "POST" { + w.WriteHeader(405) + w.Write([]byte("method must be POST")) + return + } + msg := make([]byte, 256) + l, err := req.Body.Read(msg) + if err != nil && l == 0 { + err = fmt.Errorf("unable to read body: %w", err) + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + adminReq := adminMsg{} + err = json.Unmarshal(msg[:l], &adminReq) + if err != nil { + err = fmt.Errorf("unable to unmarshal body as json: %w", err) + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + + // TODO switch to embedded secret + if adminReq.Auth != "password" { + w.WriteHeader(401) + w.Write([]byte(err.Error())) + return + } + + inner_msg, err := json.Marshal(adminReq.Msg) + if err != nil { + err = fmt.Errorf("unable to marshal inner message: %w", err) + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + select { + case status.Commands <- inner_msg: + w.Write([]byte("ok")) + default: + w.WriteHeader(503) + w.Write([]byte("unable to forward request; controller may not be connected")) + } } updateHandler := func(w http.ResponseWriter, req *http.Request) {