diff --git a/.gitignore b/.gitignore index 2e1f8da..8a7f400 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ # go build output spaceapid + +# Saved state +spaceapid-state.json diff --git a/ccchh-template.json b/ccchh-template.json deleted file mode 100644 index 70d7a62..0000000 --- a/ccchh-template.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "api_compatibility": [ - "14" - ], - "space": "CCCHH", - "logo": "https://next.hamburg.ccc.de/images/logo.svg", - "ext_ccc": "erfa", - "url": "https://hamburg.ccc.de/", - "location": { - "address": "Zeiseweg 9, 22765 Hamburg, Germany", - "lon": 9.9445899999999998, - "lat": 53.55836 - }, - "contact": { - "phone": "+494023830150", - "irc": "ircs://irc.hackint.org:6697/#ccchh", - "mastodon": "@ccchh@chaos.social", - "email": "mail@hamburg.ccc.de", - "ml": "talk@hamburg.ccc.de", - "matrix": "#ccchh:hamburg.ccc.de" - }, - "feeds": { - "blog": { - "type": "application/atom+xml", - "url": "https://hamburg.ccc.de/feed.xml" - }, - "calendar": { - "type": "ical", - "url": "webcal://cloud.hamburg.ccc.de/remote.php/dav/public-calendars/QJAdExziSnNJEz5g/?export" - } - }, - "links": [ - { - "name": "Wiki", - "url": "https://wiki.ccchh.net" - }, - { - "name": "GitLab", - "url": "https://gitlab.hamburg.ccc.de" - } - ] -} diff --git a/config-template.json b/config-template.json new file mode 100644 index 0000000..6fbaf81 --- /dev/null +++ b/config-template.json @@ -0,0 +1,101 @@ +{ + "credentials": { + "home-assistant": { + "username": "home-assistant", + "password": "hamiau" + }, + "dooris-hauptraum": { + "username": "dooris-hauptraum", + "password": "doorimiau" + } + }, + "dynamic": { + "sensors": { + "temperature": [ + { + "sensor_data": { + "unit": "C", + "location": "Hauptraum", + "name": "Kueche", + "description": "Sensor im Ofen" + }, + "allowed_credentials": [ + "home-assistant" + ] + }, + { + "sensor_data": { + "unit": "C", + "location": "Hauptraum", + "description": "Sensor im Hauptraum" + }, + "allowed_credentials": [ + "home-assistant" + ] + } + ], + "humidity": [ + { + "sensor_data": { + "unit": "hPa", + "location": "Hauptraum", + "name": "Kueche", + "description": "Sensor im Wasserhahn" + }, + "allowed_credentials": [ + "home-assistant" + ] + } + ] + }, + "state": { + "open": { + "allowed_credentials": [ + "dooris-hauptraum" + ] + } + } + }, + "response": { + "api_compatibility": [ + "14" + ], + "space": "CCCHH", + "logo": "https://next.hamburg.ccc.de/images/logo.svg", + "ext_ccc": "erfa", + "url": "https://hamburg.ccc.de/", + "location": { + "address": "Zeiseweg 9, 22765 Hamburg, Germany", + "lon": 9.9445899999999998, + "lat": 53.55836 + }, + "contact": { + "phone": "+49 40 23830150", + "irc": "ircs://irc.hackint.org:6697/#ccchh", + "mastodon": "@ccchh@chaos.social", + "email": "mail@hamburg.ccc.de", + "ml": "talk@hamburg.ccc.de", + "matrix": "#ccchh:hamburg.ccc.de" + }, + "feeds": { + "blog": { + "type": "application/atom+xml", + "url": "https://hamburg.ccc.de/feed.xml" + }, + "calendar": { + "type": "ical", + "url": "webcal://cloud.hamburg.ccc.de/remote.php/dav/public-calendars/QJAdExziSnNJEz5g/?export" + } + }, + "links": [ + { + "name": "Wiki", + "url": "https://wiki.ccchh.net" + }, + { + "name": "GitLab", + "url": "https://gitlab.hamburg.ccc.de" + } + ] + } +} diff --git a/config/config.go b/config/config.go index 2a40332..20c0419 100644 --- a/config/config.go +++ b/config/config.go @@ -1,11 +1,65 @@ package config -// Configuration represents the settings needed to configure spaceapid -type Configuration struct { - // The HTTP BasicAuth username for door status updates - BAUsername string - // The HTTP BasicAuth password for door status updates - BAPassword string - // The path to the JSON with initial values - TemplatePath string +import ( + "bytes" + "encoding/json" + "log" + "os" + "path/filepath" + "slices" + + "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" +) + +const ( + envConfigPath = "CONFIG_PATH" +) + +// getConfigPath gets the spaceapid configuration from the respective environment variables +func getConfigPath() string { + // JSON template path + configPath, ok := os.LookupEnv(envConfigPath) + if !ok || configPath == "" { + log.Fatalln("Could not retrieve", envConfigPath, "env variable or variable is empty") + } + // Save as absolute path + absConfigPath, err := filepath.Abs(configPath) + if err != nil { + log.Fatalln("Failed converting", configPath, "to absolute path:", err) + } + return absConfigPath +} + +// ParseConfiguration returns the config from file +func ParseConfiguration() (conf SpaceapidConfig) { + log.Println("Parsing configuration file") + // Read file + file, err := os.ReadFile(getConfigPath()) + if err != nil { + log.Fatalln("Failed reading file:", err) + } + + // Parse JSON + dec := json.NewDecoder(bytes.NewReader(file)) + dec.DisallowUnknownFields() + err = dec.Decode(&conf) + if err != nil { + log.Fatalln("Could not parse spaceapid config file:", err) + } + + // Check if compatible with v14 + if !slices.Contains(conf.Response.APICompatibility, "14") { + log.Fatalln("Provided file doesn't specify compatibility with API version 14") + } + + // Initialise fields for environment sensors + conf.Response.Sensors = make(map[string][]types.EnvironmentSensor) + for key, sensorConfigs := range conf.Dynamic.Sensors { + conf.Response.Sensors[key] = make([]types.EnvironmentSensor, len(sensorConfigs)) + for i, sensorConfig := range sensorConfigs { + conf.Response.Sensors[key][i] = sensorConfig.SensorData + } + } + + return } diff --git a/config/types.go b/config/types.go new file mode 100644 index 0000000..c2b3f12 --- /dev/null +++ b/config/types.go @@ -0,0 +1,38 @@ +package config + +import ( + "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" +) + +type HTTPBACredentialID string + +type HTTPBACredentials map[HTTPBACredentialID]HTTPBACredential + +type HTTPBACredential struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type EnvironmentSensorConfig struct { + SensorData types.EnvironmentSensor `json:"sensor_data"` + AllowedCredentials []HTTPBACredentialID `json:"allowed_credentials"` +} + +type DynamicStateConfig struct { + Sensors map[string][]EnvironmentSensorConfig `json:"sensors"` + State struct { + Open struct { + AllowedCredentials []HTTPBACredentialID `json:"allowed_credentials"` + } `json:"open"` + } `json:"state"` +} + +// SpaceapidConfig is the representation of the config.json file +type SpaceapidConfig struct { + // A list of username/password pairs for BasicAuth endpoints + Credentials HTTPBACredentials `json:"credentials"` + // The dynamic part of the spaceapi JSON + Dynamic DynamicStateConfig `json:"dynamic"` + // The static part of the spaceapi JSON + Response types.SpaceAPIResponseV14 `json:"response"` +} diff --git a/handlers/handlers.go b/handlers/handlers.go deleted file mode 100644 index e0bdfeb..0000000 --- a/handlers/handlers.go +++ /dev/null @@ -1,87 +0,0 @@ -package handlers - -import ( - "encoding/json" - "io" - "log" - "net/http" - "strconv" - "time" - - "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" -) - -func Root(resp *types.SpaceAPIResponseV14) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - // Check if GET method - if r.Method != http.MethodGet { - log.Println("Wrong METHOD from", r.RemoteAddr) - w.Header().Set("Allow", http.MethodGet) - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - // Serialize response - response, err := json.Marshal(resp) - if err != nil { - log.Println("Failed to serialize JSON response:", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - // Respond with OK - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write(response) - } -} - -func StateOpen( - validUsername, validPassword string, resp *types.SpaceAPIResponseV14, -) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - // Check BasicAuth credentials - username, password, ok := r.BasicAuth() - if !ok || username != validUsername || password != validPassword { - log.Println("Unauthorized request from", r.RemoteAddr) - w.Header().Set("WWW-Authenticate", "Basic realm=\"space-api\"") - w.WriteHeader(http.StatusUnauthorized) - return - } - - // Check if PUT method - if r.Method != http.MethodPut { - log.Println("Wrong METHOD from", r.RemoteAddr) - w.Header().Set("Allow", http.MethodPut) - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - // Read request body - body, err := io.ReadAll(r.Body) - if err != nil { - log.Println("Failed to read request body from", r.RemoteAddr) - w.WriteHeader(http.StatusInternalServerError) - _, _ = io.WriteString(w, "Failed reading HTTP request body") - return - } - - // Parse request body - newState, err := strconv.ParseBool(string(body)) - if err != nil { - log.Println("Failed to parse request body from", r.RemoteAddr) - w.WriteHeader(http.StatusBadRequest) - _, _ = io.WriteString(w, "HTTP request body should either be true or false") - return - } - - // Set SpaceAPI response values - resp.State.Open = newState - resp.State.LastChange = time.Now().Unix() - - // Respond with OK - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, "Update Successful") - } -} diff --git a/handlers/root.go b/handlers/root.go new file mode 100644 index 0000000..1015f49 --- /dev/null +++ b/handlers/root.go @@ -0,0 +1,35 @@ +package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" +) + +func Root(resp *types.SpaceAPIResponseV14) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // Check if GET method + if r.Method != http.MethodGet { + log.Println("Wrong METHOD from", r.RemoteAddr) + w.Header().Set("Allow", http.MethodGet) + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + // Serialize response + response, err := json.Marshal(resp) + if err != nil { + log.Println("Failed to serialize JSON response:", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + // Respond with OK + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(response) + } +} diff --git a/handlers/sensors.go b/handlers/sensors.go new file mode 100644 index 0000000..7441699 --- /dev/null +++ b/handlers/sensors.go @@ -0,0 +1,39 @@ +package handlers + +import ( + "io" + "log" + "math" + "net/http" + "strconv" + "time" + + "gitlab.hamburg.ccc.de/ccchh/spaceapid/config" + "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" +) + +func EnvironmentSensor( + authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, + resp *types.EnvironmentSensor, +) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + body := updateEndpointValidator(authDB, validCredentials, w, r) + + // Parse request body + newState, err := strconv.ParseFloat(string(body), 64) + if err != nil || math.IsInf(newState, 0) { + log.Println("Failed to parse request body from", r.RemoteAddr) + w.WriteHeader(http.StatusBadRequest) + _, _ = io.WriteString(w, "HTTP request body has to be a valid float64 value != +/-Inf") + return + } + + // Set SpaceAPI response values + resp.Value = newState + resp.LastChange = time.Now().Unix() + + // Respond with OK + w.WriteHeader(http.StatusOK) + _, _ = io.WriteString(w, "Update Successful") + } +} diff --git a/handlers/state.go b/handlers/state.go new file mode 100644 index 0000000..47bc6ab --- /dev/null +++ b/handlers/state.go @@ -0,0 +1,38 @@ +package handlers + +import ( + "io" + "log" + "net/http" + "strconv" + "time" + + "gitlab.hamburg.ccc.de/ccchh/spaceapid/config" + "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" +) + +func StateOpen( + authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, + resp *types.SpaceState, +) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + body := updateEndpointValidator(authDB, validCredentials, w, r) + + // Parse request body + newState, err := strconv.ParseBool(string(body)) + if err != nil { + log.Println("Failed to parse request body from", r.RemoteAddr) + w.WriteHeader(http.StatusBadRequest) + _, _ = io.WriteString(w, "HTTP request body should either be true or false") + return + } + + // Set SpaceAPI response values + resp.Open = newState + resp.LastChange = time.Now().Unix() + + // Respond with OK + w.WriteHeader(http.StatusOK) + _, _ = io.WriteString(w, "Update Successful") + } +} diff --git a/handlers/util.go b/handlers/util.go new file mode 100644 index 0000000..625a9c0 --- /dev/null +++ b/handlers/util.go @@ -0,0 +1,45 @@ +package handlers + +import ( + "io" + "log" + "net/http" + + "gitlab.hamburg.ccc.de/ccchh/spaceapid/config" + "gitlab.hamburg.ccc.de/ccchh/spaceapid/util" +) + +// updateEndpointValidator checks BasicAuth credentials, +// checks for correct HTTP method and then returns the request body +func updateEndpointValidator( + authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, + w http.ResponseWriter, r *http.Request, +) (body []byte) { + // Check BasicAuth credentials + username, password, ok := r.BasicAuth() + if !ok || !util.CheckCredentials(authDB, validCredentials, username, password) { + log.Println("Unauthorized request from", r.RemoteAddr) + w.Header().Set("WWW-Authenticate", "Basic realm=\"space-api\"") + w.WriteHeader(http.StatusUnauthorized) + return + } + + // Check if PUT method + if r.Method != http.MethodPut { + log.Println("Wrong Method: ", r.Method, "from", r.RemoteAddr, "at", r.RequestURI) + w.Header().Set("Allow", http.MethodPut) + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + // Read request body + body, err := io.ReadAll(r.Body) + if err != nil { + log.Println("Failed to read request body from", r.RemoteAddr) + w.WriteHeader(http.StatusInternalServerError) + _, _ = io.WriteString(w, "Failed reading HTTP request body") + return + } + + return +} diff --git a/main.go b/main.go index 7678238..5f516ea 100644 --- a/main.go +++ b/main.go @@ -1,40 +1,58 @@ package main import ( + "fmt" "log" "net/http" "os" "os/signal" + "strings" "syscall" + "gitlab.hamburg.ccc.de/ccchh/spaceapid/config" "gitlab.hamburg.ccc.de/ccchh/spaceapid/handlers" "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" "gitlab.hamburg.ccc.de/ccchh/spaceapid/util" ) func main() { - log.Println("Reading configuration values") - config := util.GetConfiguration() - - log.Println("Reading initial SpaceAPI response from", config.TemplatePath) - spaceApiResponse := util.ParseTemplate(config.TemplatePath) + // Get spaceapid configuration + conf := config.ParseConfiguration() // Merge old state if present - util.MergeOldState(&spaceApiResponse) + util.MergeOldState(&conf.Response) // Register signal handler sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM) - go func(ch chan os.Signal, resp *types.SpaceAPIResponseV14) { - <-ch + go func(resp *types.SpaceAPIResponseV14) { + <-sc log.Println("Saving state and shutting down...") util.SaveCurrentState(*resp) os.Exit(0) - }(sc, &spaceApiResponse) + }(&conf.Response) // Register HTTP handlers - http.HandleFunc("/", handlers.Root(&spaceApiResponse)) - http.HandleFunc("/state/open", handlers.StateOpen(config.BAUsername, config.BAPassword, &spaceApiResponse)) + http.HandleFunc("/", + handlers.Root(&conf.Response), + ) + http.HandleFunc("/state/open", + handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State), + ) + // Register handlers for Environmental Sensors + for key, envSensorConfigs := range conf.Dynamic.Sensors { + for i, envSensorConfig := range envSensorConfigs { + pattern := fmt.Sprintf("/sensors/%s/%s", key, envSensorConfig.SensorData.Location) + if envSensorConfig.SensorData.Name != "" { + pattern += "/" + envSensorConfig.SensorData.Name + } + http.HandleFunc(strings.ToLower(pattern), + handlers.EnvironmentSensor( + conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[key][i], + ), + ) + } + } // Start webserver log.Println("Starting HTTP server...") diff --git a/types/v14.go b/types/v14.go index 5f014ba..2bcdc06 100644 --- a/types/v14.go +++ b/types/v14.go @@ -11,10 +11,7 @@ type SpaceAPIResponseV14 struct { Lat float64 `json:"lat"` Lon float64 `json:"lon"` } `json:"location"` - State struct { - Open bool `json:"open"` - LastChange int64 `json:"lastchange"` - } `json:"state"` + State SpaceState `json:"state"` Contact struct { Phone string `json:"phone"` IRC string `json:"irc"` @@ -23,7 +20,8 @@ type SpaceAPIResponseV14 struct { ML string `json:"ml"` Matrix string `json:"matrix"` } `json:"contact"` - Feeds struct { + Sensors map[string][]EnvironmentSensor `json:"sensors"` + Feeds struct { Blog struct { Type string `json:"type"` URL string `json:"url"` @@ -39,9 +37,31 @@ type SpaceAPIResponseV14 struct { } `json:"links"` } +type SpaceState struct { + Open bool `json:"open"` + LastChange int64 `json:"lastchange"` +} + +type EnvironmentSensor struct { + Value float64 `json:"value"` + Unit string `json:"unit"` + Location string `json:"location"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + LastChange int64 `json:"lastchange,omitempty"` +} + type PersistentStateV14 struct { State struct { Open bool `json:"open"` LastChange int64 `json:"lastchange"` } `json:"state"` + Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"` +} + +type PersistentEnvironmentSensor struct { + Value float64 `json:"value"` + Location string `json:"location"` + Name string `json:"name,omitempty"` + LastChange int64 `json:"lastchange"` } diff --git a/util/config.go b/util/config.go deleted file mode 100644 index f42eaa1..0000000 --- a/util/config.go +++ /dev/null @@ -1,48 +0,0 @@ -package util - -import ( - "log" - "os" - "path/filepath" - - "gitlab.hamburg.ccc.de/ccchh/spaceapid/config" -) - -const ( - envBAUsername = "BA_USERNAME" - envBAPassword = "BA_PASSWORD" - envJSONTemplatePath = "JSON_TEMPLATE_PATH" -) - -// GetConfiguration gets the spaceapid configuration from the respective environment variables -func GetConfiguration() (c config.Configuration) { - var ( - success bool - err error - ) - - // HTTP BasicAuth username - c.BAUsername, success = os.LookupEnv(envBAUsername) - if !success || c.BAUsername == "" { - log.Fatalln("Could not retrieve env variable", envBAUsername, "or variable is empty") - } - - // HTTP BasicAuth password - c.BAPassword, success = os.LookupEnv(envBAPassword) - if !success || c.BAPassword == "" { - log.Fatalln("Could not retrieve", envBAPassword, "env variable or variable is empty") - } - - // JSON template path - templatePath, success := os.LookupEnv(envJSONTemplatePath) - if !success || templatePath == "" { - log.Fatalln("Could not retrieve", envJSONTemplatePath, "env variable or variable is empty") - } - // Save as absolute path - c.TemplatePath, err = filepath.Abs(templatePath) - if err != nil { - log.Fatalln("Failed converting", templatePath, "to absolute path:", err) - } - - return -} diff --git a/util/credentials.go b/util/credentials.go new file mode 100644 index 0000000..9352d5e --- /dev/null +++ b/util/credentials.go @@ -0,0 +1,20 @@ +package util + +import ( + "gitlab.hamburg.ccc.de/ccchh/spaceapid/config" +) + +// CheckCredentials validates whether a given username/password pair matches an +// entry in the authDB whose id is present in the validCredentials list +func CheckCredentials( + authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, username, password string, +) bool { + for _, id := range validCredentials { + if cred, present := authDB[id]; present { + if cred.Username == username && cred.Password == password { + return true + } + } + } + return false +} diff --git a/util/util.go b/util/util.go index e43123d..086ee2f 100644 --- a/util/util.go +++ b/util/util.go @@ -1,52 +1,30 @@ package util import ( - "bytes" "encoding/json" "errors" "log" "os" "path" - "slices" "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" ) -const savedStateJSONPath = "/var/lib/spaceapid/spaceapid-state.json" - -// ParseTemplate parses the given file and -func ParseTemplate(file string) (resp types.SpaceAPIResponseV14) { - - // Read template file - template, err := os.ReadFile(file) - if err != nil { - log.Fatalln("Failed reading file:", err) - } - - // Parse JSON - dec := json.NewDecoder(bytes.NewReader(template)) - dec.DisallowUnknownFields() - err = dec.Decode(&resp) - if err != nil { - log.Fatalln("Could not parse SpaceAPI response template:", err) - } - - // Check if compatible with v14 - if !slices.Contains(resp.APICompatibility, "14") { - log.Fatalln("Provided template doesn't specify compatibility with API version 14") - } - - return -} +const ( + savedStateJSONPath = "/var/lib/spaceapid/spaceapid-state.json" +) // MergeOldState merges a given SpaceAPIResponse with the state saved at the time of last program exit and then deletes // the file containing the old state. func MergeOldState(response *types.SpaceAPIResponseV14) { var ( - err error - oldState []byte + err error + oldState []byte + persistedState types.PersistentStateV14 ) + log.Println("Merging old state from", savedStateJSONPath, "...") + // Check if state.json is present _, err = os.Stat(savedStateJSONPath) if err != nil { @@ -57,18 +35,35 @@ func MergeOldState(response *types.SpaceAPIResponseV14) { goto removeOld } - // Read file and merge + // Read file and load persisted state oldState, err = os.ReadFile(savedStateJSONPath) if err != nil { log.Println("Error reading old state from", savedStateJSONPath, ", skipping merge... error:", err) goto removeOld } - err = json.Unmarshal(oldState, response) + err = json.Unmarshal(oldState, &persistedState) if err != nil { log.Println(savedStateJSONPath, "doesn't seem to contain valid data... error:", err) goto removeOld } + // Merge state + response.State = persistedState.State + + // Merge sensors + for key, environmentSensors := range persistedState.Sensors { + for _, sensor := range environmentSensors { + // Order or amount of sensors might have changed, so check sensors already present in response + // and then look for matching values in persisted state + for i := range response.Sensors[key] { + if rs := &response.Sensors[key][i]; rs.Location == sensor.Location && rs.Name == sensor.Name { + rs.Value = sensor.Value + rs.LastChange = sensor.LastChange + } + } + } + } + // Delete old state json removeOld: err = os.RemoveAll(savedStateJSONPath) @@ -84,14 +79,12 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) { log.Fatalln("Failed creating", savedStateJSONPath, ", aborting... error:", err) } - // Open persistent state file for reading - file, err := os.OpenFile(savedStateJSONPath, os.O_RDWR|os.O_CREATE, 0644) + // Open persistent state file + file, err := os.OpenFile(savedStateJSONPath, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { log.Fatalln("Failed opening", savedStateJSONPath, "while trying to save current state... error:", err) } - defer func(file *os.File) { - _ = file.Close() - }(file) + defer file.Close() // Create persistent state persistentStateV14 := types.PersistentStateV14{ @@ -100,6 +93,17 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) { LastChange int64 `json:"lastchange"` }{Open: response.State.Open, LastChange: response.State.LastChange}, } + // Save sensor state + persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor) + for key, environmentSensors := range response.Sensors { + persistentStateV14.Sensors[key] = make([]types.PersistentEnvironmentSensor, len(environmentSensors)) + for i, sensor := range environmentSensors { + persistentStateV14.Sensors[key][i].Value = sensor.Value + persistentStateV14.Sensors[key][i].Location = sensor.Location + persistentStateV14.Sensors[key][i].Name = sensor.Name + persistentStateV14.Sensors[key][i].LastChange = sensor.LastChange + } + } // Serialize persistent state marshal, err := json.MarshalIndent(persistentStateV14, "", "\t")