Add sauna

This commit is contained in:
Thomas Klaehn
2023-01-30 15:54:49 +01:00
parent f8b2047cff
commit 89a78993b5
39 changed files with 241 additions and 127 deletions

173
main.go
View File

@@ -2,144 +2,107 @@ package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"mime"
"net/http"
"os"
"sync"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
const (
OPENED uint = 1
CLOSED uint = 2
CLOSING uint = 3
OPENING uint = 4
)
type gate struct {
State uint `json:"state"`
OpenTime uint `json:"open_time"`
CloseTime uint `json:"close_time"`
}
type config struct {
InnerGate gate `json:"inner_gate"`
OuterGate gate `json:"outer_gate"`
type temperature struct {
Value float64 `json:"value"`
Unit string `json:"unit"`
}
var (
logger log.Logger = *log.Default()
config_path string
logger log.Logger = *log.Default()
cache_inner_gate = gate{
State: 1,
OpenTime: 2,
CloseTime: 2,
}
cache_outer_gate = gate{
State: 1,
OpenTime: 4,
CloseTime: 2,
}
config_cache = config{
InnerGate: cache_inner_gate,
OuterGate: cache_outer_gate,
sauna_mutex sync.Mutex
sauna_temperature = temperature{
Value: 0.0,
Unit: "°C",
}
)
func read_config() {
data, err := os.ReadFile(config_path)
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
logger.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
}
var saunaHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
log.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
sauna_mutex.Lock()
err := json.Unmarshal(msg.Payload(), &sauna_temperature)
sauna_mutex.Unlock()
if err != nil {
logger.Printf("Unable to read %s", config_path)
return
}
err = json.Unmarshal(data, &config_cache)
if err != nil {
logger.Print("Unable to evaluate config data")
logger.Print(err)
return
}
}
func api_handler_gates(w http.ResponseWriter, r *http.Request) {
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
logger.Println("Connected")
}
var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
logger.Printf("Connect lost: %v", err)
}
func init() {
logger.SetPrefix("Homeservice: ")
}
func http_endpoint_sauna(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "application/json; charset=utf-8;")
if r.Method == "GET" {
res, err := json.Marshal(config_cache)
if r.Method == http.MethodGet {
sauna_mutex.Lock()
data, err := json.Marshal(sauna_temperature)
sauna_mutex.Unlock()
if err != nil {
res = json.RawMessage(`{"error": "` + err.Error() + `"}`)
w.WriteHeader(http.StatusInternalServerError)
w.Write(res)
return
w.Write(json.RawMessage(`{"error": "cannot marshal object to json"}`))
} else {
w.WriteHeader(http.StatusOK)
w.Write(data)
}
w.WriteHeader(http.StatusOK)
w.Write(res)
} else if r.Method == "PATCH" {
var tmp config
payload, _ := io.ReadAll(r.Body)
err := json.Unmarshal(payload, &tmp)
if err != nil {
logger.Print(err)
}
logger.Print(r.Body)
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func api_handler_gates_outer(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
res, err := json.Marshal(map[string]uint{"state": config_cache.OuterGate.State})
if err != nil {
res = json.RawMessage(`{"error": "` + err.Error() + `"}`)
w.WriteHeader(http.StatusInternalServerError)
w.Write(res)
return
}
w.WriteHeader(http.StatusOK)
w.Write(res)
} else if r.Method == "PATCH" {
var tmp config
payload, _ := io.ReadAll(r.Body)
err := json.Unmarshal(payload, &tmp)
if err != nil {
res := json.RawMessage(`{"error": "` + err.Error() + `"}`)
w.WriteHeader(http.StatusInternalServerError)
w.Write(res)
return
}
if tmp.OuterGate.State == 1 {
config_cache.OuterGate.State = 3 // Schliessen => Schliesst
} else if tmp.OuterGate.State == 2 {
config_cache.OuterGate.State = 4 // Oeffnen => Oeffnet
}
// FIXME: add real gate handling
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func main() {
flag.StringVar(&config_path, "c", "./config/config.json", "Specify path to find the config file. Default is ./config/config.json")
flag.Parse()
read_config()
logger.Println("starting")
// MQTT connection
opts := mqtt.NewClientOptions()
opts.AddBroker("tcp://nuc:1883")
opts.SetClientID("homeservice")
opts.SetDefaultPublishHandler(messagePubHandler)
opts.OnConnect = connectHandler
opts.OnConnectionLost = connectLostHandler
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
// MQTT subscribtion
topic := "sauna/temperature"
token := client.Subscribe(topic, 1, saunaHandler)
token.Wait()
logger.Printf("Subscribed to topic %s", topic)
mime.AddExtensionType(".js", "text/javascript; charset=utf-8")
mime.AddExtensionType(".css", "text/css; charset=utf-8")
// API routes
// Serve files from static folder
http.Handle("/", http.FileServer(http.Dir("./svelte/build")))
http.Handle("/", http.FileServer(http.Dir("/var/lib/home/")))
// Serve api
http.HandleFunc("/gates", api_handler_gates)
http.HandleFunc("/gates/outer", api_handler_gates_outer)
http.HandleFunc("/sauna/sample", http_endpoint_sauna)
port := ":5000"
fmt.Println("Server is running on port" + port)
logger.Println("Server is running on port" + port)
// Start server on port specified above
log.Fatal(http.ListenAndServe(port, nil))
logger.Fatal(http.ListenAndServe(port, nil))
}