From aea0620747c091a621b5d24d8507fec328f51d90 Mon Sep 17 00:00:00 2001 From: phoenix Date: Wed, 31 May 2023 19:30:27 +0200 Subject: [PATCH] Add configuration Add yaml configuration file handling --- .gitignore | 4 +++ cmd/smartbridge/config.go | 42 +++++++++++++++++++++++--- cmd/smartbridge/main.go | 63 +++++++++++++++++++++++---------------- cmd/smartbridge/mqtt.go | 16 ++++++---- cmd/smartbridge/utils.go | 11 +++++++ go.mod | 5 +++- go.sum | 4 +++ smartbridge.service | 11 +++++++ smartbridge.yml.example | 14 +++++++++ 9 files changed, 135 insertions(+), 35 deletions(-) create mode 100644 cmd/smartbridge/utils.go create mode 100644 smartbridge.service create mode 100644 smartbridge.yml.example diff --git a/.gitignore b/.gitignore index 1c75578..7e4dac8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ +# Binaries /smartbridge + +# Config files +/*.yml diff --git a/cmd/smartbridge/config.go b/cmd/smartbridge/config.go index 718c2f8..2fa40eb 100644 --- a/cmd/smartbridge/config.go +++ b/cmd/smartbridge/config.go @@ -1,13 +1,47 @@ package main +import ( + "io/ioutil" + + "gopkg.in/yaml.v2" +) + type Config struct { - Remote string // Remote IP address of the Smartbridge - Mqtt string // mqtt server + Remote string `yaml:"remote"` // Remote IP address of the Smartbridge + Mqtt MqttConfig `yaml:"mqtt"` // mqtt configuration + Delay int `yaml:"delay"` + Verbose bool `yaml:"verbose"` +} + +type MqttConfig struct { + Remote string `yaml:"remote"` + Port int `yaml:"port"` + ClientID string `yaml:"clientid"` + topic string `yaml:"topic"` + Username string `yaml:"username"` + Password string `yaml:"password"` } var cf Config func (cf *Config) SetDefaults() { - cf.Remote = "192.168.242.199" - cf.Mqtt = "192.168.0.42" + cf.Remote = "127.0.0.1" + cf.Delay = 5 + cf.Mqtt.Remote = "127.0.0.1" + cf.Mqtt.Port = 1883 + cf.Mqtt.ClientID = "smartbridge" + cf.Mqtt.topic = "home/power" + cf.Mqtt.Username = "" + cf.Mqtt.Password = "" +} + +func (cf *Config) LoadYAML(filename string) error { + content, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + if err := yaml.Unmarshal(content, cf); err != nil { + return err + } + return nil } diff --git a/cmd/smartbridge/main.go b/cmd/smartbridge/main.go index 35df45b..d296fdd 100644 --- a/cmd/smartbridge/main.go +++ b/cmd/smartbridge/main.go @@ -10,25 +10,9 @@ import ( var mqtt_c Mqtt -func establishMqtt() error { - var err error - n := 5 // Reconnection attempts - - for i := 0; i < n; i++ { - mqtt_c, err = ConnectMqtt(cf.Mqtt, 1883) - if err == nil { - return nil - } - fmt.Fprintf(os.Stderr, "mqtt error: %s (attempt %d/%d)\n", err, i+1, n) - time.Sleep(time.Second * time.Duration(i)) - } - return fmt.Errorf("connection failed") -} - -// awaits SIGINT or SIGTERM and quit program -func awaitTerminationSignal() { +func registerTerminationSignal() { sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT) go func() { sig := <-sigs fmt.Println(sig) @@ -36,15 +20,39 @@ func awaitTerminationSignal() { }() } -func main() { - cf.SetDefaults() - - if err := establishMqtt(); err != nil { - fmt.Fprintf(os.Stderr, "mqtt: %s\n", err) +func loadConfig(filename string) { + if !fileExists(filename) { + return + } + if err := cf.LoadYAML(filename); err != nil { + fmt.Fprintf(os.Stderr, "error loading configuration '%s': %s\n", filename, err) os.Exit(1) } +} - awaitTerminationSignal() +func main() { + var err error + + cf.SetDefaults() + // Try to load default locations + loadConfig("/etc/smartbridge.yml") + loadConfig("./smartbridge.yml") + // Program arguments are configuration files + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + if err := cf.LoadYAML(arg); err != nil { + fmt.Fprintf(os.Stderr, "error loading configuration '%s': %s\n", arg, err) + os.Exit(1) + } + } + + mqtt_c, err = ConnectMqtt(cf.Mqtt) + if err != nil { + fmt.Fprintf(os.Stderr, "mqtt error: %s\n", err) + os.Exit(101) + } + + registerTerminationSignal() fmt.Println("smartbridge SBWF3102 reader") for true { reading, err := ReadSmartbridge(cf.Remote) @@ -54,7 +62,12 @@ func main() { if reading.Status != "ok" { fmt.Fprintf(os.Stderr, "status: %s\n", reading.Status) } else { - data := fmt.Sprintf("{\"power\":%d,\"unit\":\"W\",\"timestamp\":%d}", reading.Electricity.Power.Current.Value, time.Now().Unix()) + data := fmt.Sprintf("{\"power\":%d,\"unit\":\"W\",\"min\":%d,\"max\":%d,\"timestamp\":%d}", reading.Electricity.Power.Current.Value, reading.Electricity.Power.Minimun.Value, reading.Electricity.Power.Maximum.Value, time.Now().Unix()) + if cf.Verbose { + fmt.Print(cf.Mqtt.topic) + fmt.Print(" ") + fmt.Println(data) + } mqtt_c.Publish("home/power", data) } time.Sleep(5 * time.Second) diff --git a/cmd/smartbridge/mqtt.go b/cmd/smartbridge/mqtt.go index 67298c9..12960da 100644 --- a/cmd/smartbridge/mqtt.go +++ b/cmd/smartbridge/mqtt.go @@ -10,13 +10,19 @@ type Mqtt struct { client mqtt.Client } -func ConnectMqtt(broker string, port int) (Mqtt, error) { +func ConnectMqtt(cf MqttConfig) (Mqtt, error) { var ret Mqtt opts := mqtt.NewClientOptions() - opts.AddBroker(fmt.Sprintf("tcp://%s:%d", broker, port)) - opts.SetClientID("smartbridge") - //opts.SetUsername("emqx") - //opts.SetPassword("public") + opts.AddBroker(fmt.Sprintf("tcp://%s:%d", cf.Remote, cf.Port)) + if cf.ClientID != "" { + opts.SetClientID(cf.ClientID) + } + if cf.Username != "" { + opts.SetUsername(cf.Username) + } + if cf.Password != "" { + opts.SetPassword(cf.Password) + } //opts.SetDefaultPublishHandler(messagePubHandler) //opts.OnConnect = connectHandler //opts.OnConnectionLost = connectLostHandler diff --git a/cmd/smartbridge/utils.go b/cmd/smartbridge/utils.go new file mode 100644 index 0000000..3027276 --- /dev/null +++ b/cmd/smartbridge/utils.go @@ -0,0 +1,11 @@ +package main + +import "os" + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} diff --git a/go.mod b/go.mod index 3b55254..752e3ab 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module feldspaten.org/smartbridge/v2 go 1.20 -require github.com/eclipse/paho.mqtt.golang v1.4.2 +require ( + github.com/eclipse/paho.mqtt.golang v1.4.2 + gopkg.in/yaml.v2 v2.4.0 +) require ( github.com/gorilla/websocket v1.4.2 // indirect diff --git a/go.sum b/go.sum index ddd8ce6..b7ac04d 100644 --- a/go.sum +++ b/go.sum @@ -10,3 +10,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/smartbridge.service b/smartbridge.service new file mode 100644 index 0000000..829ebbe --- /dev/null +++ b/smartbridge.service @@ -0,0 +1,11 @@ +[Unit] +Description=smartbridge reader service +After=network.target + +[Service] +Type=simple +User=nobody +#WorkingDirectory=/var/empty +ExecStart=/usr/bin/smartbridge +Restart=always +RestartSec=30 \ No newline at end of file diff --git a/smartbridge.yml.example b/smartbridge.yml.example new file mode 100644 index 0000000..97d72cd --- /dev/null +++ b/smartbridge.yml.example @@ -0,0 +1,14 @@ +--- +# smartbridge configuration + +remote: "192.168.0.1" # Smartbridge IP address +delay: 5 # Seconds between polls +verbose: False + +mqtt: + remote: "192.168.0.1" + port: 1883 + clientid: "smartbridge" + topic: "home/power" + username: "" + password: "" \ No newline at end of file