diff --git a/config.go b/config.go index 9e5e633..ebe3b91 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,9 @@ package main import ( "errors" "io/ioutil" + "log" + "os" + "syscall" "gopkg.in/yaml.v2" ) @@ -30,6 +33,64 @@ type Config struct { DisplayFahrenheit bool `yaml:"displayfahrenheit"` } +var configFilePath string + +// UpdateSensorConfig updates the configuration of an individual sensor and writes to disk +func UpdateSensorConfig(s Sensor) error { + config, err := LoadConfig(configFilePath) + if err != nil { + return err + } + + for i := range config.Sensors { + if config.Sensors[i].ID == s.ID { + config.Sensors[i].Alias = s.Alias + config.Sensors[i].HighTemp = s.HighTemp + config.Sensors[i].LowTemp = s.LowTemp + config.Sensors[i].HeatGPIO = s.HeatGPIO + config.Sensors[i].HeatInvert = s.HeatInvert + config.Sensors[i].HeatMinutes = s.HeatMinutes + config.Sensors[i].CoolGPIO = s.CoolGPIO + config.Sensors[i].CoolInvert = s.CoolInvert + config.Sensors[i].CoolMinutes = s.CoolMinutes + config.Sensors[i].Verbose = s.Verbose + log.Println(config.Sensors[i]) + } + } + + if err = SaveConfig(configFilePath, *config); err != nil { + return err + } + + log.Println(config.Sensors[0]) + + if err = SignalReload(); err != nil { + return err + } + + return nil +} + +// SignalReload sends a SIGHUP to the process, initiating a configuration reload +func SignalReload() error { + p := os.Process{Pid: os.Getpid()} + return p.Signal(syscall.SIGHUP) +} + +// SaveConfig will write a new configuration file +func SaveConfig(path string, config Config) error { + d, err := yaml.Marshal(config) + if err != nil { + return err + } + + if err = ioutil.WriteFile(path, d, 0644); err != nil { + return err + } + + return nil +} + // LoadConfig will loads a file and parses it into a Config struct func LoadConfig(path string) (*Config, error) { data, err := ioutil.ReadFile(path) @@ -37,6 +98,7 @@ func LoadConfig(path string) (*Config, error) { return nil, err } + configFilePath = path var config Config yaml.Unmarshal(data, &config) diff --git a/html/js/thermostat.js b/html/js/thermostat.js index c7f8fde..9f7c85b 100644 --- a/html/js/thermostat.js +++ b/html/js/thermostat.js @@ -38,9 +38,9 @@ function renderThermostats() { var statusdiv = $("
").addClass("three columns").append(statusp); rowdiv.append(statusdiv); - // Display config + // Display sensor config $.ajax({ - url: jsconfig.baseurl + "/api/config/" + data[key].alias + url: jsconfig.baseurl + "/api/config/sensors/" + data[key].alias }).then(function(configData){ if (jsconfig.fahrenheit) { var hightemp = celsiusToFahrenheit(parseFloat(configData.hightemp)).toFixed(1) + "°F"; diff --git a/main.go b/main.go index bd4be31..ddf67bf 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( ) // Version is the current code version of tempgopher -const Version = "0.1.0" +const Version = "0.2.0-dev" func main() { var args struct { diff --git a/web.go b/web.go index 1b31b53..d435099 100644 --- a/web.go +++ b/web.go @@ -14,14 +14,15 @@ import ( "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/gobuffalo/packr" + "github.com/jinzhu/copier" ) -// PingHandler responds to get requests with the message "pong". +// PingHandler responds to GET requests with the message "pong". func PingHandler(c *gin.Context) { c.String(http.StatusOK, "pong") } -// ConfigHandler responds to get requests with the current configuration. +// ConfigHandler responds to GET requests with the current configuration. func ConfigHandler(config *Config) gin.HandlerFunc { fn := func(c *gin.Context) { if c.Param("alias") != "/" && c.Param("alias") != "" { @@ -34,16 +35,37 @@ func ConfigHandler(config *Config) gin.HandlerFunc { } } if !found { - c.String(http.StatusNotFound, "Not found") + c.JSON(http.StatusNotFound, gin.H{"error": "Not Found"}) } + } else if c.Param("alias") == "/" { + c.JSON(http.StatusOK, config.Sensors) } else { - c.JSON(http.StatusOK, *config) + c.JSON(http.StatusOK, config) } } return gin.HandlerFunc(fn) } -// StatusHandler responds to get requests with the current status of a sensor +// UpdateSensorsHandler responds to POST requests by updating the stored configuration and issuing a reload to the app +func UpdateSensorsHandler(c *gin.Context) { + var sensors []Sensor + + if err := c.ShouldBindJSON(&sensors); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + for _, s := range sensors { + if err := UpdateSensorConfig(s); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err}) + return + } + } + + c.JSON(http.StatusOK, gin.H{"status": "updated"}) +} + +// StatusHandler responds to GET requests with the current status of a sensor func StatusHandler(states *map[string]State) gin.HandlerFunc { fn := func(c *gin.Context) { if c.Param("alias") == "/" || c.Param("alias") == "" { @@ -63,7 +85,7 @@ func GetBox() packr.Box { return packr.NewBox("./html") } -// JSConfigHandler responds to get requests with the current configuration for the JS app +// JSConfigHandler responds to GET requests with the current configuration for the JS app func JSConfigHandler(config *Config) gin.HandlerFunc { fn := func(c *gin.Context) { jsconfig := "var jsconfig={baseurl:\"" + config.BaseURL + "\",fahrenheit:" + strconv.FormatBool(config.DisplayFahrenheit) + "};" @@ -111,7 +133,8 @@ func SetupRouter(config *Config, states *map[string]State) *gin.Engine { // Config r.GET("/api/config", ConfigHandler(config)) - r.GET("/api/config/*alias", ConfigHandler(config)) + r.GET("/api/config/sensors/*alias", ConfigHandler(config)) + r.POST("/api/config/sensors", UpdateSensorsHandler) // App r.GET("/jsconfig.js", JSConfigHandler(config)) @@ -125,6 +148,18 @@ func SetupRouter(config *Config, states *map[string]State) *gin.Engine { return r } +// reloadWebConfig reloads the current copy of configuration +func reloadWebConfig(c *Config, p string) error { + nc, err := LoadConfig(p) + if err != nil { + return err + } + + copier.Copy(&c, &nc) + + return nil +} + // RunWeb launches a web server. sc is used to update the states from the Thermostats. func RunWeb(configpath string, sc <-chan State, wg *sync.WaitGroup) { // Update sensor states when a new state comes back from the thermostat. @@ -145,7 +180,7 @@ func RunWeb(configpath string, sc <-chan State, wg *sync.WaitGroup) { go func() { for { <-hup - config, err = LoadConfig(configpath) + err = reloadWebConfig(config, configpath) if err != nil { log.Panicln(err) }