260 lines
6.3 KiB
Go
260 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
import (
|
|
s "shroom_server/shroom_internals"
|
|
)
|
|
|
|
import (
|
|
"database/sql"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
//go:embed auth_secret
|
|
var auth_secret string
|
|
|
|
//go:embed static/*
|
|
var content embed.FS
|
|
|
|
type statusJson struct {
|
|
Connected bool `json:"connected"`
|
|
Humidifier bool `json:"humidifier"`
|
|
Humidifier2 bool `json:"humidifier2"`
|
|
ManualMode bool `json:"manual_mode"`
|
|
}
|
|
|
|
type adminMsg struct {
|
|
Auth string `json:"auth"`
|
|
Msg map[string]interface{} `json:"data"`
|
|
}
|
|
|
|
// returns a function that multiplies the number at the very last segment of the url
|
|
// and returns the data that was collected in the last n*multiplier milliseconds
|
|
func dumpData(db *sql.DB, dc *s.DataCache, multiplier int64) func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
now := time.Now().Unix()
|
|
path := strings.Split(req.URL.Path, "/")
|
|
last := path[len(path)-1]
|
|
count, err := strconv.Atoi(last)
|
|
if err != nil {
|
|
w.WriteHeader(400)
|
|
w.Write([]byte("could not read integer in path: " + err.Error()))
|
|
return
|
|
}
|
|
offset := int64(count) * multiplier
|
|
t := now*1000 - offset
|
|
//log.Println("req start ", t)
|
|
var msg []byte
|
|
cachedPoints := dc.ReadSince(uint64(t))
|
|
if cachedPoints != nil {
|
|
msg, err = s.DatapointsToJson(cachedPoints)
|
|
if err != nil {
|
|
msg = nil
|
|
}
|
|
}
|
|
// fallback to actually reading from the sql
|
|
if msg == nil {
|
|
log.Println("unable to read from cache for ", t, " using sql")
|
|
msg, err = s.GetRows(db, t)
|
|
}
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
w.Write([]byte(err.Error()))
|
|
} else {
|
|
w.Write(msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
db, err := sql.Open("sqlite3", "shrooms.db")
|
|
if err != nil {
|
|
log.Fatal("unable to open db ", err)
|
|
}
|
|
if err = db.Ping(); err != nil {
|
|
log.Fatal("unable to ping db ", err)
|
|
}
|
|
|
|
err = s.CreateTable(db)
|
|
if err != nil {
|
|
log.Fatal("unable to create table ", err)
|
|
}
|
|
|
|
state := s.NewShroomState()
|
|
s.InitTcpServer(db, &state)
|
|
|
|
contentSub, err := fs.Sub(content, "static")
|
|
if err != nil {
|
|
log.Fatal("unable to use subdirectory of embedded fs: ", err)
|
|
}
|
|
|
|
dumpWeek := dumpData(db, &state.Cache, 7*24*60*60*1000)
|
|
dumpDay := dumpData(db, &state.Cache, 24*60*60*1000)
|
|
dumpHour := dumpData(db, &state.Cache, 60*60*1000)
|
|
dumpMinute := dumpData(db, &state.Cache, 60*1000)
|
|
dumpSecond := dumpData(db, &state.Cache, 1000)
|
|
|
|
lastPoint := func(w http.ResponseWriter, _req *http.Request) {
|
|
var err error
|
|
time := state.Cache.LatestTime()
|
|
if time > 0 {
|
|
msg, err := json.Marshal(time)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
w.Write([]byte(err.Error()))
|
|
} else {
|
|
w.Write(msg)
|
|
}
|
|
return
|
|
}
|
|
|
|
msg, err := s.LastTime(db)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
w.Write([]byte(err.Error()))
|
|
} else {
|
|
w.Write(msg)
|
|
}
|
|
}
|
|
|
|
getStatus := func(w http.ResponseWriter, _req *http.Request) {
|
|
state.RLock()
|
|
num_connections := state.NumConnections
|
|
humidifier := state.HumidifierOn
|
|
humidifier2 := state.Humidifier2On
|
|
manual_mode := state.ManualMode
|
|
state.RUnlock()
|
|
s := statusJson{
|
|
Connected: num_connections > 0,
|
|
Humidifier: humidifier,
|
|
Humidifier2: humidifier2,
|
|
ManualMode: manual_mode,
|
|
}
|
|
msg, err := json.Marshal(s)
|
|
if err != nil {
|
|
err = fmt.Errorf("unable to marshal json: %w", err)
|
|
w.WriteHeader(400)
|
|
w.Write([]byte(err.Error()))
|
|
} else {
|
|
w.Write(msg)
|
|
}
|
|
}
|
|
|
|
adminHandler := func(w http.ResponseWriter, req *http.Request) {
|
|
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
|
|
}
|
|
|
|
// switch to embedded secret
|
|
if adminReq.Auth != auth_secret {
|
|
w.WriteHeader(401)
|
|
w.Write([]byte("invalid secret"))
|
|
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 state.Commands <- inner_msg:
|
|
w.Write([]byte("ok"))
|
|
default:
|
|
w.WriteHeader(503)
|
|
w.Write([]byte("unable to forward request; controller may not be connected"))
|
|
}
|
|
}
|
|
|
|
paramsHandler := func(w http.ResponseWriter, req *http.Request) {
|
|
msg, err := s.QueryParams(&state)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
w.Write(msg)
|
|
}
|
|
|
|
updateHandler := func(w http.ResponseWriter, req *http.Request) {
|
|
s.WaitForClose(state.Wait)
|
|
w.Write([]byte("ok"))
|
|
}
|
|
statusUpdateHandler := func(w http.ResponseWriter, req *http.Request) {
|
|
s.WaitForClose(state.StatusWait)
|
|
w.Write([]byte("ok"))
|
|
}
|
|
|
|
http.Handle("/d/", http.StripPrefix("/d/", http.FileServer(http.Dir("./dev"))))
|
|
http.Handle("/", http.FileServer(http.FS(contentSub)))
|
|
http.HandleFunc("/api/last/weeks/", dumpWeek)
|
|
http.HandleFunc("/api/last/days/", dumpDay)
|
|
http.HandleFunc("/api/last/hours/", dumpHour)
|
|
http.HandleFunc("/api/last/minutes/", dumpMinute)
|
|
http.HandleFunc("/api/last/seconds/", dumpSecond)
|
|
http.HandleFunc("/api/latest", lastPoint)
|
|
http.HandleFunc("/api/status", getStatus)
|
|
http.HandleFunc("/api/admin", adminHandler)
|
|
http.HandleFunc("/api/params", paramsHandler)
|
|
http.HandleFunc("/api/update", updateHandler)
|
|
http.HandleFunc("/api/status_update", statusUpdateHandler)
|
|
|
|
// periodically clear old entries from the database
|
|
go func() {
|
|
// TODO maybe make this exit gracefully
|
|
for {
|
|
t, err := s.OldestTime(db)
|
|
if err != nil {
|
|
log.Println("unable to get oldest time: ", err)
|
|
}
|
|
now := time.Now().Unix()
|
|
diff := now*1000 - t
|
|
log.Println("oldest time", t, " current time", now, "diff", diff)
|
|
if diff > 2*7*24*60*60*1000 {
|
|
err = s.ClearOldRows(db, 1000*now-8*24*60*60*1000)
|
|
if err != nil {
|
|
log.Println("unable to delete rows: ", err)
|
|
}
|
|
}
|
|
time.Sleep(24 * time.Hour)
|
|
}
|
|
}()
|
|
|
|
err = http.ListenAndServe(":8085", nil)
|
|
if err != nil {
|
|
log.Fatal("unable to start server: ", err)
|
|
}
|
|
defer db.Close()
|
|
}
|