From 3685ec6c933fe0a6d22df0e41c16f13d7be075cf Mon Sep 17 00:00:00 2001 From: Sam Anthony Date: Thu, 7 Nov 2024 18:41:53 -0500 Subject: server: refactor --- Makefile | 2 +- server/duty.go | 36 +++++++++++++ server/humidity.go | 95 +++++++++++++++++++++++++++++++++ server/server.go | 150 ----------------------------------------------------- server/target.go | 45 ++++++++++++++++ 5 files changed, 177 insertions(+), 151 deletions(-) create mode 100644 server/duty.go create mode 100644 server/humidity.go create mode 100644 server/target.go diff --git a/Makefile b/Makefile index 91b6c05..cf4ae0d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SENSOR_SRC = SensorStation/SensorStation.ino HVAC_SRC = HvacStation/HvacStation.ino -SERVER_SRC = server/server.go server/record.go +SERVER_SRC = server/*.go BOARD = esp32:esp32:lilygo_t_display PORT = /dev/ttyACM0 diff --git a/server/duty.go b/server/duty.go new file mode 100644 index 0000000..ae32ecb --- /dev/null +++ b/server/duty.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "strconv" + "sync" +) + +type DutyCycle float32 + +type DutyCycleHandler struct { + mu sync.Mutex + dc DutyCycle +} + +func (h *DutyCycleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.URL) + + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "invalid method: '%s'", r.Method) + return + } + + dc, err := strconv.ParseFloat(r.URL.RawQuery, 32) + if err != nil { + badRequest(w, "invalid duty cycle: '%s'", r.URL.RawQuery) + return + } + + h.mu.Lock() + defer h.mu.Unlock() + h.dc = DutyCycle(dc) +} diff --git a/server/humidity.go b/server/humidity.go new file mode 100644 index 0000000..079c9bf --- /dev/null +++ b/server/humidity.go @@ -0,0 +1,95 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "strconv" +) + +type Humidity float32 + +type HumidityHandler struct { + rooms map[RoomID]Record[Humidity] +} + +func newHumidityHandler(rooms []RoomID) HumidityHandler { + h := HumidityHandler{make(map[RoomID]Record[Humidity])} + for _, id := range rooms { + h.rooms[id] = newRecord[Humidity]() + } + return h +} + +func (h HumidityHandler) Close() { + for _, record := range h.rooms { + record.Close() + } +} + +func (h HumidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.URL) + switch r.Method { + case http.MethodGet: + h.get(w, r) + case http.MethodPost: + h.post(w, r) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "invalid method: '%s'", r.Method) + } +} + +func (h HumidityHandler) get(w http.ResponseWriter, r *http.Request) { + if humidity, ok := h.average(); ok { + fmt.Fprintf(w, "%.2f", humidity) + } else { + w.WriteHeader(http.StatusGone) + fmt.Fprintf(w, "no humidity data stored on server") + } +} + +func (h HumidityHandler) post(w http.ResponseWriter, r *http.Request) { + queryVals, err := parseQuery(r.URL.RawQuery, []string{"room", "humidity"}) + if err != nil { + badRequest(w, "invalid query: %v", err) + return + } + room := RoomID(queryVals["room"]) + humidityStr := queryVals["humidity"] + + humidity, err := strconv.ParseFloat(humidityStr, 32) + if err != nil { + badRequest(w, "invalid humidity: '%s'", humidityStr) + return + } + + record, ok := h.rooms[room] + if !ok { + badRequest(w, "invalid room ID: '%s'", room) + return + } + + record.put <- Humidity(humidity) +} + +// Calculate the average humidity in the building. Returns false if there is not enough data available. +func (h HumidityHandler) average() (Humidity, bool) { + var sum Humidity = 0 + nRooms := 0 + for room, record := range h.rooms { + c := make(chan Humidity) + record.getRecent <- c + if humidity, ok := <-c; ok { + sum += humidity + nRooms++ + } else { + log.Printf("Warning: no humidity for room '%s'\n", room) + } + } + if nRooms == 0 { + log.Println("Warning: not enough data to calculate average humidity") + return -1.0, false + } + return sum / Humidity(nRooms), true +} diff --git a/server/server.go b/server/server.go index 88bfaa3..7a70cc6 100644 --- a/server/server.go +++ b/server/server.go @@ -5,8 +5,6 @@ import ( "log" "net/http" "net/url" - "strconv" - "sync" ) const addr = ":9090" @@ -16,23 +14,7 @@ var rooms = []RoomID{ "rEKKa5TW5xjArmR25wT4Uiw7tksk4noE", } -type Humidity float32 type RoomID string -type DutyCycle float32 - -type HumidityHandler struct { - rooms map[RoomID]Record[Humidity] -} - -type TargetHumidityHandler struct { - mu sync.Mutex - target Humidity -} - -type DutyCycleHandler struct { - mu sync.Mutex - dc DutyCycle -} func main() { humidityHandler := newHumidityHandler(rooms) @@ -46,138 +28,6 @@ func main() { log.Fatal(http.ListenAndServe(addr, nil)) } -func newHumidityHandler(rooms []RoomID) HumidityHandler { - h := HumidityHandler{make(map[RoomID]Record[Humidity])} - for _, id := range rooms { - h.rooms[id] = newRecord[Humidity]() - } - return h -} - -func (h HumidityHandler) Close() { - for _, record := range h.rooms { - record.Close() - } -} - -func (h HumidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL) - switch r.Method { - case http.MethodGet: - h.get(w, r) - case http.MethodPost: - h.post(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - fmt.Fprintf(w, "invalid method: '%s'", r.Method) - } -} - -func (h HumidityHandler) get(w http.ResponseWriter, r *http.Request) { - if humidity, ok := h.average(); ok { - fmt.Fprintf(w, "%.2f", humidity) - } else { - w.WriteHeader(http.StatusGone) - fmt.Fprintf(w, "no humidity data stored on server") - } -} - -func (h HumidityHandler) post(w http.ResponseWriter, r *http.Request) { - queryVals, err := parseQuery(r.URL.RawQuery, []string{"room", "humidity"}) - if err != nil { - badRequest(w, "invalid query: %v", err) - return - } - room := RoomID(queryVals["room"]) - humidityStr := queryVals["humidity"] - - humidity, err := strconv.ParseFloat(humidityStr, 32) - if err != nil { - badRequest(w, "invalid humidity: '%s'", humidityStr) - return - } - - record, ok := h.rooms[room] - if !ok { - badRequest(w, "invalid room ID: '%s'", room) - return - } - - record.put <- Humidity(humidity) -} - -// Calculate the average humidity in the building. Returns false if there is not enough data available. -func (h HumidityHandler) average() (Humidity, bool) { - var sum Humidity = 0 - nRooms := 0 - for room, record := range h.rooms { - c := make(chan Humidity) - record.getRecent <- c - if humidity, ok := <-c; ok { - sum += humidity - nRooms++ - } else { - log.Printf("Warning: no humidity for room '%s'\n", room) - } - } - if nRooms == 0 { - log.Println("Warning: not enough data to calculate average humidity") - return -1.0, false - } - return sum / Humidity(nRooms), true -} - -func (h *TargetHumidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL) - switch r.Method { - case http.MethodGet: - h.get(w, r) - case http.MethodPost: - h.post(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - fmt.Fprintf(w, "invalid method: '%s'", r.Method) - } -} - -func (h *TargetHumidityHandler) get(w http.ResponseWriter, r *http.Request) { - h.mu.Lock() - defer h.mu.Unlock() - fmt.Fprintf(w, "%.2f", h.target) -} - -func (h *TargetHumidityHandler) post(w http.ResponseWriter, r *http.Request) { - target, err := strconv.ParseFloat(r.URL.RawQuery, 32) - if err != nil { - badRequest(w, "invalid humidity: '%s'", r.URL.RawQuery) - return - } - - h.mu.Lock() - defer h.mu.Unlock() - h.target = Humidity(target) -} - -func (h *DutyCycleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL) - - if r.Method != http.MethodPost { - w.WriteHeader(http.StatusMethodNotAllowed) - fmt.Fprintf(w, "invalid method: '%s'", r.Method) - return - } - - dc, err := strconv.ParseFloat(r.URL.RawQuery, 32) - if err != nil { - badRequest(w, "invalid duty cycle: '%s'", r.URL.RawQuery) - return - } - - h.mu.Lock() - defer h.mu.Unlock() - h.dc = DutyCycle(dc) -} - // Parse the value associated with each key in the query string. Returns a map of // keys and values, or error if one of the keys is missing, or if there is no value // associated with one of the keys. diff --git a/server/target.go b/server/target.go new file mode 100644 index 0000000..4c58f1d --- /dev/null +++ b/server/target.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "strconv" + "sync" +) + +type TargetHumidityHandler struct { + mu sync.Mutex + target Humidity +} + +func (h *TargetHumidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.URL) + switch r.Method { + case http.MethodGet: + h.get(w, r) + case http.MethodPost: + h.post(w, r) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "invalid method: '%s'", r.Method) + } +} + +func (h *TargetHumidityHandler) get(w http.ResponseWriter, r *http.Request) { + h.mu.Lock() + defer h.mu.Unlock() + fmt.Fprintf(w, "%.2f", h.target) +} + +func (h *TargetHumidityHandler) post(w http.ResponseWriter, r *http.Request) { + target, err := strconv.ParseFloat(r.URL.RawQuery, 32) + if err != nil { + badRequest(w, "invalid humidity: '%s'", r.URL.RawQuery) + return + } + + h.mu.Lock() + defer h.mu.Unlock() + h.target = Humidity(target) +} -- cgit v1.2.3