From 90064a22056d51518d9ed55c6642a4a647767851 Mon Sep 17 00:00:00 2001 From: Dustin Brewer Date: Mon, 19 Nov 2018 19:33:03 -0800 Subject: [PATCH] Implemented DI --- Makefile | 2 +- README.md | 17 ++++---- config.go | 62 +++++++++++++++++++++++++++ go.mod | 1 + go.sum | 31 +------------- main.go | 49 ++++++--------------- mqtt.go | 30 +++++++++++++ mysb.go | 119 +++++++++++++++++++++++++++------------------------ mysb_test.go | 88 ++++++++++++++++++------------------- 9 files changed, 222 insertions(+), 177 deletions(-) create mode 100644 config.go create mode 100644 mqtt.go diff --git a/Makefile b/Makefile index 2949cf0..a8193ae 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ build: format $(GOBUILD) $(BINARY_VERSION_FLAGS) -o $(BINARY_NAME) -v run: build ./$(BINARY_NAME) -docker: +docker: clean { \ set -e ;\ for arch in $(DOCKER_ARCHS); do \ diff --git a/README.md b/README.md index ad8411b..e3eae80 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A Firmware Uploading Tool for the MYSBootloader via MQTT ## Via Docker ``` -docker run -d --name="mysb" -v /the/path/to/config_folder:/config -v /etc/localtime:/etc/localtime:ro mannkind/mysb +docker run -d --name="mysb" -v /etc/localtime:/etc/localtime:ro mannkind/mysb ``` ## Via Make @@ -20,7 +20,7 @@ docker run -d --name="mysb" -v /the/path/to/config_folder:/config -v /etc/localt git clone https://github.com/mannkind/mysb cd mysb make -MYSB_CONFIGFILE="config.yaml" ./mysb +./mysb ``` # Configuration @@ -28,15 +28,14 @@ MYSB_CONFIGFILE="config.yaml" ./mysb Configuration happens via environmental variables ``` -MYSB_AUTOID - The flag that indicates Mysb should handle ID requests, defaults to false -MYSB_NEXTID - The number on which to base the next id, defaults to 1 -MYSB_FIRMWAREBASEPATH - The path to the firmware files, defaults to "/config/firmware" -MYSB_CONFIG - The yaml config that contains control variables for Mysb -MYSB_NODES - The nodes configuration (see below) +MYSB_SUBTOPIC - [OPTIONAL] The MQTT topic on which to subscribe, defaults to "mysensors_rx" +MYSB_PUBTOPIC - [OPTIONAL] The MQTT topic on which to publish, defaults to "mysensors_tx" +MYSB_AUTOID - [OPTIONAL] The flag that indicates Mysb should handle ID requests, defaults to false +MYSB_NEXTID - [OPTIONAL] The number on which to base the next id, defaults to 1 +MYSB_FIRMWAREBASEPATH - [OPTIONAL] The path to the firmware files, defaults to "/config/firmware" +MYSB_NODES - [OPTIONAL] The nodes configuration (see below) MQTT_CLIENTID - [OPTIONAL] The clientId, defaults to "DefaultMysbClientID" MQTT_BROKER - [OPTIONAL] The MQTT broker, defaults to "tcp://mosquitto.org:1883" -MQTT_SUBTOPIC - [OPTIONAL] The MQTT topic on which to subscribe, defaults to "mysensors_rx" -MQTT_PUBTOPIC - [OPTIONAL] The MQTT topic on which to publish, defaults to "mysensors_tx" MQTT_USERNAME - [OPTIONAL] The MQTT username, default to "" MQTT_PASSWORD - [OPTIONAL] The MQTT password, default to "" ``` diff --git a/config.go b/config.go new file mode 100644 index 0000000..4a8e53d --- /dev/null +++ b/config.go @@ -0,0 +1,62 @@ +package main + +import ( + "log" + "reflect" + + "github.com/caarlos0/env" + "gopkg.in/yaml.v2" +) + +// Config - Structured configuration for the application. +type Config struct { + ClientID string `env:"MQTT_CLIENTID" envDefault:"DefaultMysbClientID"` + Broker string `env:"MQTT_BROKER" envDefault:"tcp://mosquitto.org:1883"` + Username string `env:"MQTT_USERNAME"` + Password string `env:"MQTT_PASSWORD"` + SubTopic string `env:"MYSB_SUBTOPIC" envDefault:"mysensors_rx"` + PubTopic string `env:"MYSB_PUBTOPIC" envDefault:"mysensors_tx"` + AutoIDEnabled bool `env:"MYSB_AUTOID" envDefault:"false"` + NextID uint `env:"MYSB_NEXTID" envDefault:"1"` + FirmwareBasePath string `env:"MYSB_FIRMWAREBASEPATH" envDefault:"/config/firmware"` + Nodes nodeSettingsMap `env:"MYSB_NODES"` +} + +// NewConfig - Returns a new Config object with configuration from ENV variables. +func NewConfig() *Config { + c := Config{} + + if err := env.ParseWithFuncs(&c, env.CustomParsers{ + reflect.TypeOf(nodeSettingsMap{}): nodeSettingsParser, + }); err != nil { + log.Panicf("Error unmarshaling configuration: %s", err) + } + + redactedPassword := "" + if len(c.Password) > 0 { + redactedPassword = "" + } + + log.Printf("Environmental Settings:") + log.Printf(" * ClientID : %s", c.ClientID) + log.Printf(" * Broker : %s", c.Broker) + log.Printf(" * SubTopic : %s", c.SubTopic) + log.Printf(" * PubTopic : %s", c.PubTopic) + log.Printf(" * Username : %s", c.Username) + log.Printf(" * Password : %s", redactedPassword) + log.Printf(" * AutoID : %t", c.AutoIDEnabled) + log.Printf(" * NextID : %d", c.NextID) + log.Printf(" * FirmwareBasePath: %s", c.FirmwareBasePath) + log.Printf(" * Nodes : %+v", c.Nodes) + + return &c +} + +func nodeSettingsParser(value string) (interface{}, error) { + c := make(nodeSettingsMap) + if err := yaml.Unmarshal([]byte(value), &c); err != nil { + log.Panicf("Error unmarshaling control configuration: %s", err) + } + + return c, nil +} diff --git a/go.mod b/go.mod index 446d059..5e1c2a2 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/caarlos0/env v3.5.0+incompatible github.com/eclipse/paho.mqtt.golang v1.1.1 github.com/kierdavis/ihex-go v0.0.0-20180105024510-bf28f2206797 + go.uber.org/dig v1.6.0 golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 07fe599..537636a 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,13 @@ github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eclipse/paho.mqtt.golang v1.1.1 h1:iPJYXJLaViCshRTW/PSqImSS6HJ2Rf671WR0bXZ2GIU= github.com/eclipse/paho.mqtt.golang v1.1.1/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/kierdavis/ihex-go v0.0.0-20180105024510-bf28f2206797 h1:W3pEj5EglEQLUAMwE6PBm3Y1DZapp7AOsSeHeDZnTwI= github.com/kierdavis/ihex-go v0.0.0-20180105024510-bf28f2206797/go.mod h1:XCg/J3R8D3GZvAYjJx0Q6R9OzF+fUgzYHu2exysdiIE= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= -github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= -github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M= -github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= -golang.org/x/net v0.0.0-20181107093936-a544f70c90f1 h1:SwqD3GJ9PsSBBVv1HlDeSwjjPSeZjUZQcAgGnwsWpyc= -golang.org/x/net v0.0.0-20181107093936-a544f70c90f1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +go.uber.org/dig v1.6.0 h1:SUxK4CpYs3SRlqTLku5uaXbiSmyycpSFZG6B/fJ8C+4= +go.uber.org/dig v1.6.0/go.mod h1:z+dSd2TP9Usi48jL8M3v63iSBVkiwtVyMKxMZYYauPg= golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg= -golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 4d14dca..3f11eec 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,8 @@ package main import ( "log" - "reflect" - "github.com/caarlos0/env" - "gopkg.in/yaml.v2" + "go.uber.org/dig" ) // Version - Set during compilation when using included Makefile @@ -14,44 +12,23 @@ var Version = "X.X.X" func main() { log.Printf("Mysb Version: %s", Version) - log.Print("Stating Process") - controller := mysb{} - if err := env.ParseWithFuncs(&controller, env.CustomParsers{ - reflect.TypeOf(nodeSettingsMap{}): nodeSettingsParser, - }); err != nil { - log.Panicf("Error unmarshaling configuration: %s", err) - } - - redactedPassword := "" - if len(controller.Password) > 0 { - redactedPassword = "" - } + c := buildContainer() + err := c.Invoke(func(m *Mysb) error { + return m.Run() + }) - log.Printf("Environmental Settings:") - log.Printf(" * ClientID : %s", controller.ClientID) - log.Printf(" * Broker : %s", controller.Broker) - log.Printf(" * SubTopic : %s", controller.SubTopic) - log.Printf(" * PubTopic : %s", controller.PubTopic) - log.Printf(" * Username : %s", controller.Username) - log.Printf(" * Password : %s", redactedPassword) - log.Printf(" * AutoID : %t", controller.AutoIDEnabled) - log.Printf(" * NextID : %d", controller.NextID) - log.Printf(" * FirmwareBasePath: %s", controller.FirmwareBasePath) - log.Printf(" * Nodes : %+v", controller.Nodes) - - if err := controller.start(); err != nil { - log.Panicf("Error starting mysb: %s", err) + if err != nil { + log.Panicf("Error starting mysb process: %s", err) } - // log.Print("Ending Process") select {} } -func nodeSettingsParser(value string) (interface{}, error) { - control := make(nodeSettingsMap) - if err := yaml.Unmarshal([]byte(value), &control); err != nil { - log.Panicf("Error unmarshaling control configuration: %s", err) - } +func buildContainer() *dig.Container { + c := dig.New() + c.Provide(NewConfig) + c.Provide(NewMQTTFuncWrapper) + c.Provide(NewMysb) - return control, nil + return c } diff --git a/mqtt.go b/mqtt.go new file mode 100644 index 0000000..ef1fd4d --- /dev/null +++ b/mqtt.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/eclipse/paho.mqtt.golang" +) + +type newMqttClientOptsFunc func() *mqtt.ClientOptions +type newMqttClientFunc func(*mqtt.ClientOptions) mqtt.Client +type mqttDiscovery struct { + Name string `json:"name"` + StateTopic string `json:"state_topic"` + UniqueID string `json:"unique_id,omitempty"` + PayloadOn string `json:"payload_on,omitempty"` + PayloadOff string `json:"payload_off,omitempty"` + DeviceClass string `json:"device_class,omitempty"` +} + +// MQTTFuncWrapper - Wraps the functions needed to create a new MQTT client. +type MQTTFuncWrapper struct { + clientOptsFunc newMqttClientOptsFunc + clientFunc newMqttClientFunc +} + +// NewMQTTFuncWrapper - Returns a fancy new wrapper for the mqtt creation functions. +func NewMQTTFuncWrapper() *MQTTFuncWrapper { + return &MQTTFuncWrapper{ + clientOptsFunc: mqtt.NewClientOptions, + clientFunc: mqtt.NewClient, + } +} diff --git a/mysb.go b/mysb.go index fd76add..d359025 100644 --- a/mysb.go +++ b/mysb.go @@ -10,60 +10,65 @@ import ( "github.com/eclipse/paho.mqtt.golang" ) -// mysb - MQTT all the things! -type mysb struct { - ClientID string `env:"MQTT_CLIENTID" envDefault:"DefaultMysbClientID"` - Broker string `env:"MQTT_BROKER" envDefault:"tcp://mosquitto.org:1883"` - SubTopic string `env:"MQTT_SUBTOPIC" envDefault:"mysensors_rx"` - PubTopic string `env:"MQTT_PUBTOPIC" envDefault:"mysensors_tx"` - Username string `env:"MQTT_USERNAME"` - Password string `env:"MQTT_PASSWORD"` - AutoIDEnabled bool `env:"MYSB_AUTOID" envDefault:"false"` - NextID uint `env:"MYSB_NEXTID" envDefault:"1"` - FirmwareBasePath string `env:"MYSB_FIRMWAREBASEPATH" envDefault:"/config/firmware"` - Nodes nodeSettingsMap `env:"MYSB_NODES"` - +// Mysb - Coordinate OTA firmware uploads +type Mysb struct { + subTopic string + pubTopic string + autoIDEnabled bool + nextID uint + firmwareBasePath string + nodes nodeSettingsMap bootloaderCommands bootloaderCmdMap lastPublished string + client mqtt.Client +} + +// NewMysb - Returns a new, configured Mysb object. +func NewMysb(config *Config, mqttFuncWrapper *MQTTFuncWrapper) *Mysb { + m := Mysb{ + subTopic: config.SubTopic, + pubTopic: config.PubTopic, + autoIDEnabled: config.AutoIDEnabled, + nextID: config.NextID, + firmwareBasePath: config.FirmwareBasePath, + nodes: config.Nodes, + } + + opts := mqttFuncWrapper. + clientOptsFunc(). + AddBroker(config.Broker). + SetClientID(config.ClientID). + SetOnConnectHandler(m.onConnect). + SetConnectionLostHandler(m.onDisconnect). + SetUsername(config.Username). + SetPassword(config.Password) + + m.client = mqttFuncWrapper.clientFunc(opts) + + return &m } -func (t *mysb) start() error { +// Run - Start the Mysb process +func (t *Mysb) Run() error { log.Println("Connecting to MQTT") - opts := mqtt.NewClientOptions(). - AddBroker(t.Broker). - SetClientID(t.ClientID). - SetOnConnectHandler(t.onConnect). - SetConnectionLostHandler(func(client mqtt.Client, err error) { - log.Printf("Disconnected from MQTT: %s.", err) - }). - SetUsername(t.Username). - SetPassword(t.Password) - - client := mqtt.NewClient(opts) - if token := client.Connect(); !token.Wait() || token.Error() != nil { + if token := t.client.Connect(); !token.Wait() || token.Error() != nil { return token.Error() } return nil } -func (t *mysb) onConnect(client mqtt.Client) { +func (t *Mysb) onConnect(client mqtt.Client) { log.Println("Connected to MQTT") // Subscribe to topics subscriptions := map[string]mqtt.MessageHandler{ - fmt.Sprintf(idRequestTopic, t.SubTopic): t.idRequest, - fmt.Sprintf(firmwareConfigRequestTopic, t.SubTopic): t.configurationRequest, - fmt.Sprintf(firmwareRequestTopic, t.SubTopic): t.dataRequest, + fmt.Sprintf(idRequestTopic, t.subTopic): t.idRequest, + fmt.Sprintf(firmwareConfigRequestTopic, t.subTopic): t.configurationRequest, + fmt.Sprintf(firmwareRequestTopic, t.subTopic): t.dataRequest, firmwareBootloaderCommandTopic: t.bootloaderCommand, } - // - if !client.IsConnected() { - log.Print("Subscribe Error: Not Connected (Reloading Config?)") - return - } - for topic, handler := range subscriptions { log.Printf("Subscribing: %s", topic) if token := client.Subscribe(topic, 0, handler); !token.Wait() || token.Error() != nil { @@ -72,19 +77,23 @@ func (t *mysb) onConnect(client mqtt.Client) { } } -func (t *mysb) idRequest(client mqtt.Client, msg mqtt.Message) { +func (t *Mysb) onDisconnect(client mqtt.Client, err error) { + log.Printf("Disconnected from MQTT: %s.", err) +} + +func (t *Mysb) idRequest(client mqtt.Client, msg mqtt.Message) { log.Println("ID Request") - if !t.AutoIDEnabled { + if !t.autoIDEnabled { return } - t.NextID++ + t.nextID++ - log.Printf("Assigning ID: %d\n", t.NextID) - t.publish(client, fmt.Sprintf(idResponseTopic, t.PubTopic), fmt.Sprintf("%d", t.NextID)) + log.Printf("Assigning ID: %d\n", t.nextID) + t.publish(client, fmt.Sprintf(idResponseTopic, t.pubTopic), fmt.Sprintf("%d", t.nextID)) } -func (t *mysb) configurationRequest(client mqtt.Client, msg mqtt.Message) { +func (t *Mysb) configurationRequest(client mqtt.Client, msg mqtt.Message) { _, payload, to := t.msgParts(msg) // Attempt to run any bootloader commands @@ -102,14 +111,14 @@ func (t *mysb) configurationRequest(client mqtt.Client, msg mqtt.Message) { Crc: firmware.Crc, } - respTopic := fmt.Sprintf(firmwareConfigResponseTopic, t.PubTopic, to) + respTopic := fmt.Sprintf(firmwareConfigResponseTopic, t.pubTopic, to) respPayload := resp.String() log.Printf("Configuration Request: From: %s; Request: %s; Response: %s\n", to, req.String(), respPayload) t.publish(client, respTopic, respPayload) } -func (t *mysb) dataRequest(client mqtt.Client, msg mqtt.Message) { +func (t *Mysb) dataRequest(client mqtt.Client, msg mqtt.Message) { _, payload, to := t.msgParts(msg) req := newFirmwareRequest(payload) @@ -122,7 +131,7 @@ func (t *mysb) dataRequest(client mqtt.Client, msg mqtt.Message) { } data, _ := firmware.dataForBlock(req.Block) - respTopic := fmt.Sprintf(firmwareResponseTopic, t.PubTopic, to) + respTopic := fmt.Sprintf(firmwareResponseTopic, t.pubTopic, to) respPayload := resp.String(data) if req.Block+1 == firmware.Blocks { @@ -140,7 +149,7 @@ func (t *mysb) dataRequest(client mqtt.Client, msg mqtt.Message) { // * 0x01 - Erase EEPROM // * 0x02 - Set NodeID // * 0x03 - Set ParentID -func (t *mysb) bootloaderCommand(client mqtt.Client, msg mqtt.Message) { +func (t *Mysb) bootloaderCommand(client mqtt.Client, msg mqtt.Message) { topic, payload, _ := t.msgParts(msg) parts := strings.Split(topic, "/") @@ -167,9 +176,9 @@ func (t *mysb) bootloaderCommand(client mqtt.Client, msg mqtt.Message) { t.bootloaderCommands[to] = resp } -func (t *mysb) runBootloaderCommand(client mqtt.Client, to string) bool { +func (t *Mysb) runBootloaderCommand(client mqtt.Client, to string) bool { if blcmd, ok := t.bootloaderCommands[to]; ok { - outTopic := fmt.Sprintf(firmwareConfigResponseTopic, t.PubTopic, to) + outTopic := fmt.Sprintf(firmwareConfigResponseTopic, t.pubTopic, to) outPayload := blcmd.String() t.publish(client, outTopic, outPayload) @@ -180,7 +189,7 @@ func (t *mysb) runBootloaderCommand(client mqtt.Client, to string) bool { return false } -func (t *mysb) firmwareInfo(nodeID string, firmwareType uint16, firmwareVersion uint16) firmwareInformation { +func (t *Mysb) firmwareInfo(nodeID string, firmwareType uint16, firmwareVersion uint16) firmwareInformation { fw := firmwareInformation{ Source: fwUnknown, } @@ -191,7 +200,7 @@ func (t *mysb) firmwareInfo(nodeID string, firmwareType uint16, firmwareVersion // Attempt to load firmware based on the node's request if _, err := os.Stat(fw.Path); err != nil { fw.Type, fw.Version, fw.Source = firmwareType, firmwareVersion, fwReq - fw.Path = fmt.Sprintf("%s/%d/%d/firmware.hex", t.FirmwareBasePath, fw.Type, fw.Version) + fw.Path = fmt.Sprintf("%s/%d/%d/firmware.hex", t.firmwareBasePath, fw.Type, fw.Version) } // Attempt to laod the default firmware @@ -207,22 +216,22 @@ func (t *mysb) firmwareInfo(nodeID string, firmwareType uint16, firmwareVersion return fw } -func (t *mysb) firmwareInfoAssignment(nodeID string, source firmwareSource) firmwareInformation { +func (t *Mysb) firmwareInfoAssignment(nodeID string, source firmwareSource) firmwareInformation { fw := firmwareInformation{ Source: fwUnknown, } // Attempt to load firmware from the assignment in config.yaml - nodeSettings := t.Nodes[nodeID] + nodeSettings := t.nodes[nodeID] fw.Type = nodeSettings.Type fw.Version = nodeSettings.Version - fw.Path = fmt.Sprintf("%s/%d/%d/firmware.hex", t.FirmwareBasePath, fw.Type, fw.Version) + fw.Path = fmt.Sprintf("%s/%d/%d/firmware.hex", t.firmwareBasePath, fw.Type, fw.Version) fw.Source = source return fw } -func (t *mysb) msgParts(msg mqtt.Message) (string, string, string) { +func (t *Mysb) msgParts(msg mqtt.Message) (string, string, string) { topic := msg.Topic() payload := string(msg.Payload()) to := strings.Split(topic, "/")[1] @@ -230,7 +239,7 @@ func (t *mysb) msgParts(msg mqtt.Message) (string, string, string) { return topic, payload, to } -func (t *mysb) publish(client mqtt.Client, topic string, payload string) { +func (t *Mysb) publish(client mqtt.Client, topic string, payload string) { if token := client.Publish(topic, 0, false, payload); token.Wait() && token.Error() != nil { log.Printf("Publish Error: %s", token.Error()) } diff --git a/mysb_test.go b/mysb_test.go index f8fadc6..4db82f6 100644 --- a/mysb_test.go +++ b/mysb_test.go @@ -4,43 +4,37 @@ import ( "fmt" "testing" - "github.com/caarlos0/env" "gopkg.in/yaml.v2" - - "github.com/eclipse/paho.mqtt.golang" ) const nodeRequestHex = "test_files/1/1/firmware.hex" -var testClient = mqtt.NewClient(mqtt.NewClientOptions()) - -func defaultTestMQTT() *mysb { +func defaultTestMQTT() *Mysb { var testConfig = ` nodes: default: { type: 1, version: 1 } 1: { type: 1, version: 1, queueMessages: true } ` - myMqtt := mysb{} - env.Parse(&myMqtt) - myMqtt.AutoIDEnabled = true - myMqtt.NextID = 12 - myMqtt.FirmwareBasePath = "test_files" - if err := yaml.Unmarshal([]byte(testConfig), &myMqtt); err != nil { + mysb := NewMysb(NewConfig(), NewMQTTFuncWrapper()) + mysb.autoIDEnabled = true + mysb.nextID = 12 + mysb.firmwareBasePath = "test_files" + if err := yaml.Unmarshal([]byte(testConfig), &mysb); err != nil { panic(err) } - return &myMqtt + return mysb } func TestMqttIDRequest(t *testing.T) { - myMQTT := defaultTestMQTT() + mysb := defaultTestMQTT() var tests = []struct { Request string Response string AutoIDEnabled bool }{ - {fmt.Sprintf("%s/255/255/3/0/3", myMQTT.SubTopic), fmt.Sprintf("%s/255/255/3/0/4 %s", myMQTT.PubTopic, "13"), true}, - {fmt.Sprintf("%s/255/255/3/0/3", myMQTT.SubTopic), "", false}, + {fmt.Sprintf("%s/255/255/3/0/3", mysb.subTopic), fmt.Sprintf("%s/255/255/3/0/4 %s", mysb.pubTopic, "13"), true}, + {fmt.Sprintf("%s/255/255/3/0/3", mysb.subTopic), "", false}, } for _, v := range tests { msg := &mockMessage{ @@ -50,46 +44,46 @@ func TestMqttIDRequest(t *testing.T) { expected := v.Response - myMQTT.lastPublished = "" - myMQTT.AutoIDEnabled = v.AutoIDEnabled - myMQTT.idRequest(testClient, msg) - if myMQTT.lastPublished != expected { - t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", myMQTT.lastPublished, expected) + mysb.lastPublished = "" + mysb.autoIDEnabled = v.AutoIDEnabled + mysb.idRequest(mysb.client, msg) + if mysb.lastPublished != expected { + t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", mysb.lastPublished, expected) } } } func TestMqttConfigurationRequest(t *testing.T) { - myMQTT := defaultTestMQTT() + mysb := defaultTestMQTT() msg := &mockMessage{ - topic: fmt.Sprintf("%s/1/255/4/0/0", myMQTT.SubTopic), + topic: fmt.Sprintf("%s/1/255/4/0/0", mysb.subTopic), payload: []byte("010001005000D446"), } - expected := fmt.Sprintf("%s/1/255/4/0/1 %s", myMQTT.PubTopic, "010001005000D446") - myMQTT.configurationRequest(testClient, msg) - if myMQTT.lastPublished != expected { - t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", myMQTT.lastPublished, expected) + expected := fmt.Sprintf("%s/1/255/4/0/1 %s", mysb.pubTopic, "010001005000D446") + mysb.configurationRequest(mysb.client, msg) + if mysb.lastPublished != expected { + t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", mysb.lastPublished, expected) } } func TestMqttDataRequest(t *testing.T) { - myMQTT := defaultTestMQTT() + mysb := defaultTestMQTT() msg := &mockMessage{ - topic: fmt.Sprintf("%s/1/255/4/0/0", myMQTT.SubTopic), + topic: fmt.Sprintf("%s/1/255/4/0/0", mysb.subTopic), payload: []byte("010001000100"), } - expected := fmt.Sprintf("%s/1/255/4/0/3 %s", myMQTT.PubTopic, "0100010001000C946E000C946E000C946E000C946E00") + expected := fmt.Sprintf("%s/1/255/4/0/3 %s", mysb.pubTopic, "0100010001000C946E000C946E000C946E000C946E00") - myMQTT.dataRequest(testClient, msg) - if myMQTT.lastPublished != expected { - t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", myMQTT.lastPublished, expected) + mysb.dataRequest(mysb.client, msg) + if mysb.lastPublished != expected { + t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", mysb.lastPublished, expected) } } func TestMqttBootloaderCommand(t *testing.T) { - myMQTT := defaultTestMQTT() + mysb := defaultTestMQTT() var tests = []struct { To string Cmd string @@ -106,16 +100,16 @@ func TestMqttBootloaderCommand(t *testing.T) { payload: []byte(v.Payload), } - myMQTT.bootloaderCommand(testClient, msg) - if _, ok := myMQTT.bootloaderCommands[v.To]; !ok { + mysb.bootloaderCommand(mysb.client, msg) + if _, ok := mysb.bootloaderCommands[v.To]; !ok { t.Error("Bootloader command not found") } else { - if ok := myMQTT.runBootloaderCommand(testClient, v.To); !ok { + if ok := mysb.runBootloaderCommand(mysb.client, v.To); !ok { t.Error("Bootloader command not run") } else { - expected := fmt.Sprintf("%s/%s/255/4/0/1 %s", myMQTT.PubTopic, v.To, v.ExpectedPayload) - if myMQTT.lastPublished != expected { - t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", myMQTT.lastPublished, expected) + expected := fmt.Sprintf("%s/%s/255/4/0/1 %s", mysb.pubTopic, v.To, v.ExpectedPayload) + if mysb.lastPublished != expected { + t.Errorf("Wrong topic or payload - Actual: %s, Expected: %s", mysb.lastPublished, expected) } } } @@ -123,22 +117,22 @@ func TestMqttBootloaderCommand(t *testing.T) { } func TestMqttBadBootloaderCommand(t *testing.T) { - myMQTT := defaultTestMQTT() - if ok := myMQTT.runBootloaderCommand(testClient, "1"); ok { + mysb := defaultTestMQTT() + if ok := mysb.runBootloaderCommand(mysb.client, "1"); ok { t.Error("Bootloader command didn't exist, should not have returned true") } } -func TestMqttStart(t *testing.T) { - myMQTT := defaultTestMQTT() - if err := myMQTT.start(); err != nil { +func TestMqttRun(t *testing.T) { + mysb := defaultTestMQTT() + if err := mysb.Run(); err != nil { t.Error("Something went wrong; expected to connect!") } } func TestMqttConnect(t *testing.T) { - myMQTT := defaultTestMQTT() - myMQTT.onConnect(testClient) + mysb := defaultTestMQTT() + mysb.onConnect(mysb.client) } type mockMessage struct {