mirror of
https://github.com/shouptech/tempgopher.git
synced 2026-02-03 16:49:42 +00:00
Provide the ability to update configuration via the API
See merge request shouptech/tempgopher!2
This commit is contained in:
commit
b4c4ce8650
4 changed files with 108 additions and 11 deletions
62
config.go
62
config.go
|
|
@ -3,6 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
@ -30,6 +33,64 @@ type Config struct {
|
||||||
DisplayFahrenheit bool `yaml:"displayfahrenheit"`
|
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
|
// LoadConfig will loads a file and parses it into a Config struct
|
||||||
func LoadConfig(path string) (*Config, error) {
|
func LoadConfig(path string) (*Config, error) {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
|
|
@ -37,6 +98,7 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configFilePath = path
|
||||||
var config Config
|
var config Config
|
||||||
yaml.Unmarshal(data, &config)
|
yaml.Unmarshal(data, &config)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ function renderThermostats() {
|
||||||
var statusdiv = $("<div></div>").addClass("three columns").append(statusp);
|
var statusdiv = $("<div></div>").addClass("three columns").append(statusp);
|
||||||
rowdiv.append(statusdiv);
|
rowdiv.append(statusdiv);
|
||||||
|
|
||||||
// Display config
|
// Display sensor config
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: jsconfig.baseurl + "/api/config/" + data[key].alias
|
url: jsconfig.baseurl + "/api/config/sensors/" + data[key].alias
|
||||||
}).then(function(configData){
|
}).then(function(configData){
|
||||||
if (jsconfig.fahrenheit) {
|
if (jsconfig.fahrenheit) {
|
||||||
var hightemp = celsiusToFahrenheit(parseFloat(configData.hightemp)).toFixed(1) + "°F";
|
var hightemp = celsiusToFahrenheit(parseFloat(configData.hightemp)).toFixed(1) + "°F";
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version is the current code version of tempgopher
|
// Version is the current code version of tempgopher
|
||||||
const Version = "0.1.0"
|
const Version = "0.2.0-dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var args struct {
|
var args struct {
|
||||||
|
|
|
||||||
51
web.go
51
web.go
|
|
@ -14,14 +14,15 @@ import (
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gobuffalo/packr"
|
"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) {
|
func PingHandler(c *gin.Context) {
|
||||||
c.String(http.StatusOK, "pong")
|
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 {
|
func ConfigHandler(config *Config) gin.HandlerFunc {
|
||||||
fn := func(c *gin.Context) {
|
fn := func(c *gin.Context) {
|
||||||
if c.Param("alias") != "/" && c.Param("alias") != "" {
|
if c.Param("alias") != "/" && c.Param("alias") != "" {
|
||||||
|
|
@ -34,16 +35,37 @@ func ConfigHandler(config *Config) gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
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 {
|
} else {
|
||||||
c.JSON(http.StatusOK, *config)
|
c.JSON(http.StatusOK, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gin.HandlerFunc(fn)
|
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 {
|
func StatusHandler(states *map[string]State) gin.HandlerFunc {
|
||||||
fn := func(c *gin.Context) {
|
fn := func(c *gin.Context) {
|
||||||
if c.Param("alias") == "/" || c.Param("alias") == "" {
|
if c.Param("alias") == "/" || c.Param("alias") == "" {
|
||||||
|
|
@ -63,7 +85,7 @@ func GetBox() packr.Box {
|
||||||
return packr.NewBox("./html")
|
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 {
|
func JSConfigHandler(config *Config) gin.HandlerFunc {
|
||||||
fn := func(c *gin.Context) {
|
fn := func(c *gin.Context) {
|
||||||
jsconfig := "var jsconfig={baseurl:\"" + config.BaseURL + "\",fahrenheit:" + strconv.FormatBool(config.DisplayFahrenheit) + "};"
|
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
|
// Config
|
||||||
r.GET("/api/config", ConfigHandler(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
|
// App
|
||||||
r.GET("/jsconfig.js", JSConfigHandler(config))
|
r.GET("/jsconfig.js", JSConfigHandler(config))
|
||||||
|
|
@ -125,6 +148,18 @@ func SetupRouter(config *Config, states *map[string]State) *gin.Engine {
|
||||||
return r
|
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.
|
// 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) {
|
func RunWeb(configpath string, sc <-chan State, wg *sync.WaitGroup) {
|
||||||
// Update sensor states when a new state comes back from the thermostat.
|
// 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() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-hup
|
<-hup
|
||||||
config, err = LoadConfig(configpath)
|
err = reloadWebConfig(config, configpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue