From 6837a07bebe0e33889f6548209a100c399c864b1 Mon Sep 17 00:00:00 2001 From: Nicolas Duhamel Date: Fri, 28 Oct 2022 19:20:04 +0200 Subject: [PATCH] gofmt --- main.go | 408 +++++++++--------- pkg/device/device.go | 522 +++++++++++------------ pkg/device/device_manager.go | 127 +++--- pkg/device/device_test.go | 798 +++++++++++++++++------------------ pkg/device/settings.go | 331 ++++++++------- pkg/device/settings_test.go | 179 ++++---- 6 files changed, 1171 insertions(+), 1194 deletions(-) diff --git a/main.go b/main.go index 153ad66..f739eb4 100644 --- a/main.go +++ b/main.go @@ -1,274 +1,270 @@ package main import ( - "citadel/heater/pkg/device" - "citadel/heater/mqtt" - mqttpaho "github.com/eclipse/paho.mqtt.golang" - "context" - "encoding/json" - "os" - "os/signal" - "syscall" - "time" - "strings" - "github.com/rs/zerolog" - "flag" + "citadel/heater/mqtt" + "citadel/heater/pkg/device" + "context" + "encoding/json" + "flag" + "os" + "os/signal" + "strings" + "syscall" + "time" + + mqttpaho "github.com/eclipse/paho.mqtt.golang" + "github.com/rs/zerolog" ) type Config struct { - Host string - Port string - Username string - Password string - Clientid string + Host string + Port string + Username string + Password string + Clientid string } type App struct { - mqtt_hub *mqtt.Hub - ctx context.Context + mqtt_hub *mqtt.Hub + ctx context.Context - DeviceManager *device.DeviceManager - PubChan chan device.Message + DeviceManager *device.DeviceManager + PubChan chan device.Message } func NewApp(conf Config) *App { - hub := mqtt.NewHub(&mqtt.Config{ - Host: conf.Host, - Port: conf.Port, - Username: conf.Username, - Password: conf.Password, - ClientID: conf.Clientid, - CleanSession: true, - AutoReconnect: true, - Retained: false, - KeepAlive: 15 * time.Second, - MsgChanDept: 100, - }) - pubchan := make(chan device.Message, 10) + hub := mqtt.NewHub(&mqtt.Config{ + Host: conf.Host, + Port: conf.Port, + Username: conf.Username, + Password: conf.Password, + ClientID: conf.Clientid, + CleanSession: true, + AutoReconnect: true, + Retained: false, + KeepAlive: 15 * time.Second, + MsgChanDept: 100, + }) + pubchan := make(chan device.Message, 10) - app := &App{ - mqtt_hub: hub, - PubChan: pubchan, - } - app.DeviceManager = device.NewDeviceManager(pubchan, app.subscribe) - return app + app := &App{ + mqtt_hub: hub, + PubChan: pubchan, + } + app.DeviceManager = device.NewDeviceManager(pubchan, app.subscribe) + return app } func (app *App) subscribe( - topic string, - qos int, - callback func(context.Context, mqttpaho.Message), - ) context.CancelFunc { + topic string, + qos int, + callback func(context.Context, mqttpaho.Message), +) context.CancelFunc { - ctx, cancelFunc := context.WithCancel(app.ctx) + ctx, cancelFunc := context.WithCancel(app.ctx) - log := zerolog.Ctx(ctx) + log := zerolog.Ctx(ctx) - go func(ctx context.Context, sub *mqtt.Subscriber) { - for { - select { - case msg, ok := <-sub.OnMessage: - if !ok { - return - } - callback(ctx, msg) - case err, ok := <-sub.OnError: - if !ok { - return - } - log.Error().Err(err) - case <-ctx.Done(): - return - } - } - }(ctx, app.mqtt_hub.Subscribe(ctx, topic, qos)) - log.Info().Str("topic", topic).Msg("subscribe") - return cancelFunc + go func(ctx context.Context, sub *mqtt.Subscriber) { + for { + select { + case msg, ok := <-sub.OnMessage: + if !ok { + return + } + callback(ctx, msg) + case err, ok := <-sub.OnError: + if !ok { + return + } + log.Error().Err(err) + case <-ctx.Done(): + return + } + } + }(ctx, app.mqtt_hub.Subscribe(ctx, topic, qos)) + log.Info().Str("topic", topic).Msg("subscribe") + return cancelFunc } func (app *App) runTicker() { - ticker := time.NewTicker(30 * time.Second) - logger := zerolog.Ctx(app.ctx).With().Str("action", "ticker").Logger() - logger.Info().Msg("Start ticker") + ticker := time.NewTicker(30 * time.Second) + logger := zerolog.Ctx(app.ctx).With().Str("action", "ticker").Logger() + logger.Info().Msg("Start ticker") - ctx := logger.WithContext(app.ctx) - go func() { - for { - select { - case <- app.ctx.Done(): - return - case t := <-ticker.C: - logger.Info().Time("at", t).Msg("Tick") - app.DeviceManager.CheckAll(ctx) - } - } - }() + ctx := logger.WithContext(app.ctx) + go func() { + for { + select { + case <-app.ctx.Done(): + return + case t := <-ticker.C: + logger.Info().Time("at", t).Msg("Tick") + app.DeviceManager.CheckAll(ctx) + } + } + }() } func (app *App) onSettingsMessage(ctx context.Context, msg mqttpaho.Message) { - // callback for topic /{prefix}/{tvr_id}/settings change's - device_name := strings.Split(msg.Topic(), "/")[1] + // callback for topic /{prefix}/{tvr_id}/settings change's + device_name := strings.Split(msg.Topic(), "/")[1] - logger := zerolog.Ctx(ctx).With(). - Str("action", "onSettingsMessage"). - Str("device", device_name). - Logger() + logger := zerolog.Ctx(ctx).With(). + Str("action", "onSettingsMessage"). + Str("device", device_name). + Logger() - logger.Debug().Str("topic", msg.Topic()).Str("payload", string(msg.Payload())).Msg("") - ctx = logger.WithContext(ctx) + ctx = logger.WithContext(ctx) - var device_settings device.DeviceSettings - if err := json.Unmarshal(msg.Payload(), &device_settings); err != nil { - logger.Error().Err(err).Msg("Parsing payload") - } + logger.Debug(). + Str("topic", msg.Topic()). + Str("payload", string(msg.Payload())). + Msg("") - tvr := device.Device{ - Name: device_name, - Settings: device_settings, - } + var device_settings device.DeviceSettings + if err := json.Unmarshal(msg.Payload(), &device_settings); err != nil { + logger.Error().Err(err).Msg("Parsing payload") + } - if _, ok := app.DeviceManager.Get(device_name); !ok { - err := app.DeviceManager.Add(tvr) - if err != nil { - logger.Error().Err(err).Msg("Unexpected") - return - } - } else { - err := app.DeviceManager.SetSettings(device_name, device_settings) - if err != nil { - logger.Error().Err(err).Msg("Unexpected") - return - } - } + tvr := device.Device{ + Name: device_name, + Settings: device_settings, + } - err := app.DeviceManager.Check(ctx, device_name) - if err != nil { - logger.Error().Err(err).Msg("During device `Check`") - } + if _, ok := app.DeviceManager.Get(device_name); !ok { + if err := app.DeviceManager.Add(tvr); err != nil { + logger.Error().Err(err).Msg("unexpected, abord") + return + } + } else { + if err := app.DeviceManager. + SetSettings(device_name, device_settings); err != nil { + logger.Error().Err(err).Msg("unexpected, abord") + return + } + } + + if err := app.DeviceManager.Check(ctx, device_name); err != nil { + logger.Error().Err(err).Msg("During device `Check`") + } } func (app *App) onSetStateMessage(ctx context.Context, msg mqttpaho.Message) { - // callback for topic /{prefix}/{tvr_id}/state/set change's + // callback for topic /{prefix}/{tvr_id}/state/set change's - device_name := strings.Split(msg.Topic(), "/")[1] + device_name := strings.Split(msg.Topic(), "/")[1] - logger := zerolog.Ctx(ctx).With(). - Str("action", "onSetStateMessage"). - Str("device", device_name). - Logger() - ctx = logger.WithContext(ctx) + logger := zerolog.Ctx(ctx).With(). + Str("action", "onSetStateMessage"). + Str("device", device_name). + Logger() + ctx = logger.WithContext(ctx) + var state device.DeviceState + if err := json.Unmarshal(msg.Payload(), &state); err != nil { + logger.Error().Err(err).Msg("Error while parsing payload") + return + } - var state device.DeviceState - if err := json.Unmarshal(msg.Payload(), &state); err != nil { - logger.Error().Err(err).Msg("Error while parsing payload") - } - - logger.Info().Interface("state", state).Msg("new state") - - device, ok := app.DeviceManager.Get(device_name) - if !ok { - logger.Error().Msg("Device not found, abord") - return - } - - if err := device.SetState(&logger, state, app.PubChan); err != nil { - logger.Error().Err(err).Msg("") - return - - } + logger.Debug().Interface("state", state).Msg("new state") + if device, ok := app.DeviceManager.Get(device_name); ok { + if err := device.SetState(&logger, state, app.PubChan); err != nil { + logger.Error().Err(err).Msg("unexpected") + return + } + } else { + logger.Error().Msg("Device not found, abord") + } } - func (app *App) Run() { - ctx, ctxCancel := context.WithCancel(context.Background()) - output := zerolog.ConsoleWriter{Out: os.Stderr} - log := zerolog.New(output). + ctx, ctxCancel := context.WithCancel(context.Background()) + output := zerolog.ConsoleWriter{Out: os.Stderr} + log := zerolog.New(output). With(). Logger() - ctx = log.WithContext(ctx) + ctx = log.WithContext(ctx) + defer ctxCancel() - defer ctxCancel() + if err := app.mqtt_hub.Connect(ctx); err != nil { + log.Fatal().Err(err) + } - if err := app.mqtt_hub.Connect(ctx); err != nil { - log.Fatal().Err(err) - } + app.ctx = ctx - app.ctx = ctx + app.subscribe("heater/+/settings", 2, app.onSettingsMessage) + app.subscribe("heater/+/state/set", 2, app.onSetStateMessage) - app.subscribe("heater/+/settings", 2, app.onSettingsMessage) - app.subscribe("heater/+/state/set", 2, app.onSetStateMessage) + go func(ctx context.Context, pub *mqtt.Publisher) { + log := zerolog.Ctx(ctx).With(). + Str("action", "publisher"). + Logger() + defer log.Error().Msg("publisher stoped") + for { + select { - go func(ctx context.Context, pub *mqtt.Publisher) { - log := zerolog.Ctx(ctx).With(). - Str("action", "publisher"). - Logger() - defer log.Error().Msg("publisher stoped") - for { - select { + case msg, ok := <-app.PubChan: + if !ok { + log.Error().Msg("publish PubChan Not OK") + return + } + log.Debug(). + Str("topic", msg.Topic). + Str("payload", string(msg.Payload)). + Msg("publish") - case msg, ok := <-app.PubChan: - if !ok { - log.Error().Msg("publish PubChan Not OK") - return - } - log.Debug(). - Str("topic", msg.Topic). - Str("payload", string(msg.Payload)). - Msg("publish") + pub.Publish(msg.Topic, 1, msg.Payload, msg.Retain) - pub.Publish(msg.Topic, 1 ,msg.Payload, msg.Retain) + case err, ok := <-pub.OnError: + if !ok { + log.Error().Msg("publish OnError Not OK") + return + } + log.Error().Err(err).Msg("publish OnError") - case err, ok := <-pub.OnError: - if !ok { - log.Error().Msg("publish OnError Not OK") - return - } - log.Error().Err(err).Msg("publish OnError") + case <-ctx.Done(): + return + } + } + }(ctx, app.mqtt_hub.Publisher(ctx)) - case <-ctx.Done(): - return - } - } - }(ctx, app.mqtt_hub.Publisher(ctx)) + app.runTicker() - app.runTicker() - - for { - select { - case <-ctx.Done(): - return - } - } + for { + select { + case <-ctx.Done(): + return + } + } } func main() { - host := flag.String("host", "localhost", "mqtt host") - port := flag.String("port", "1883", "mqtt port") - username := flag.String("username", "", "mqtt username") - password := flag.String("password", "", "mqtt password") - clientid := flag.String("clientid", "goheater", "mqtt client id") + host := flag.String("host", "localhost", "mqtt host") + port := flag.String("port", "1883", "mqtt port") + username := flag.String("username", "", "mqtt username") + password := flag.String("password", "", "mqtt password") + clientid := flag.String("clientid", "goheater", "mqtt client id") - flag.Parse() + flag.Parse() - config := Config{ - Host: *host, - Port: *port, - Username: *username, - Password: *password, - Clientid: *clientid, - } + config := Config{ + Host: *host, + Port: *port, + Username: *username, + Password: *password, + Clientid: *clientid, + } - app := NewApp(config) - go app.Run() - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit + app := NewApp(config) + go app.Run() + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit } - diff --git a/pkg/device/device.go b/pkg/device/device.go index 10f5b9a..c30a7a0 100644 --- a/pkg/device/device.go +++ b/pkg/device/device.go @@ -1,376 +1,364 @@ package device import ( - "github.com/eclipse/paho.mqtt.golang" - "github.com/ohler55/ojg/jp" - "github.com/ohler55/ojg/oj" - "fmt" - "time" - "encoding/json" - "context" - "github.com/rs/zerolog" - // "reflect" + "context" + "encoding/json" + "fmt" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/ohler55/ojg/jp" + "github.com/ohler55/ojg/oj" + "github.com/rs/zerolog" + // "reflect" ) var timeNow = func() time.Time { - return time.Now() + return time.Now() } - type Error string + func (e Error) Error() string { return string(e) } - type DeviceState struct { - Mode string `json:"mode"` - Setpoint int `json:"setpoint"` - Time time.Time `json:"time"` - Program_name string `json:"program_name"` - Until_time time.Time `json:"until_time"` + Mode string `json:"mode"` + Setpoint int `json:"setpoint"` + Time time.Time `json:"time"` + Program_name string `json:"program_name"` + Until_time time.Time `json:"until_time"` } func (s *DeviceState) Equivalent(state DeviceState) bool { - if state.Mode != s.Mode { - return false - } - if !state.Time.Equal(s.Time) { - return false - } + if state.Mode != s.Mode { + return false + } + if !state.Time.Equal(s.Time) { + return false + } - switch state.Mode { - case "always": - if state.Setpoint != s.Setpoint { - return false - } - case "until_next": - if state.Setpoint != s.Setpoint { - return false - } - case "until_time": - if state.Setpoint != s.Setpoint { - return false - } - if !state.Until_time.Equal(s.Until_time) { - return false - } - case "program": - if state.Program_name != s.Program_name { - return false - } - } + switch state.Mode { + case "always": + if state.Setpoint != s.Setpoint { + return false + } + case "until_next": + if state.Setpoint != s.Setpoint { + return false + } + case "until_time": + if state.Setpoint != s.Setpoint { + return false + } + if !state.Until_time.Equal(s.Until_time) { + return false + } + case "program": + if state.Program_name != s.Program_name { + return false + } + } - return true + return true } - // Internal state type Device struct { - Name string - Settings DeviceSettings - CurrentSetpoint int - State DeviceState + Name string + Settings DeviceSettings + CurrentSetpoint int + State DeviceState } - func (d *Device) StateTopic() string { - return fmt.Sprintf("heater/%s/state", d.Name) + return fmt.Sprintf("heater/%s/state", d.Name) } func (d Device) ListenTopic() (string, error) { - return d.Settings.TVR.FormatTopicState(d.Name) + return d.Settings.TVR.FormatTopicState(d.Name) } - func (d *Device) Program() (WeekProgram, error) { - // return current device program if specified or default one - prog_name := "default" - if d.State.Program_name != "" { - prog_name = d.State.Program_name - } + // return current device program if specified or default one + prog_name := "default" + if d.State.Program_name != "" { + prog_name = d.State.Program_name + } - program, ok := d.Settings.Programs[prog_name] - if !ok { - return WeekProgram{}, Error(fmt.Sprintf("device %s don't have %s program", d.Name, prog_name)) - } + program, ok := d.Settings.Programs[prog_name] + if !ok { + return WeekProgram{}, Error(fmt.Sprintf("device %s don't have %s program", d.Name, prog_name)) + } - return program, nil + return program, nil } - -func (d *Device) ProgramName() (string) { - prog_name := "default" - if d.State.Program_name != "" { - prog_name = d.State.Program_name - } - return prog_name +func (d *Device) ProgramName() string { + prog_name := "default" + if d.State.Program_name != "" { + prog_name = d.State.Program_name + } + return prog_name } - func (d *Device) publishState(pubchan chan Message) error { - payload, err := json.Marshal(d.State) - if err != nil { - return err - } + payload, err := json.Marshal(d.State) + if err != nil { + return err + } - pubchan <- Message { - Topic: d.StateTopic(), - Payload: payload, - Retain: true, - } + pubchan <- Message{ + Topic: d.StateTopic(), + Payload: payload, + Retain: true, + } - return nil + return nil } - func (d *Device) SetSetpoint(value int, pubchan chan Message) error { - topic, err := d.Settings.TVR.FormatTopic(d.Name) - if err != nil { - return err - } + topic, err := d.Settings.TVR.FormatTopic(d.Name) + if err != nil { + return err + } - payload, err := d.Settings.TVR.FormatPayload(value) - if err != nil { - return err - } + payload, err := d.Settings.TVR.FormatPayload(value) + if err != nil { + return err + } - pubchan <- Message{ - Topic: topic, - Payload: []byte(payload), - Retain: false, - } - return nil + pubchan <- Message{ + Topic: topic, + Payload: []byte(payload), + Retain: false, + } + return nil } - func (d *Device) SetState(log *zerolog.Logger, state DeviceState, pubchan chan Message) error { - // If same state do nothing - // else use checksetpoint for changing state - if d.State.Equivalent(state) { - log.Debug().Msg("same state no change") - return nil - } + // If same state do nothing + // else use checksetpoint for changing state + if d.State.Equivalent(state) { + log.Debug().Msg("same state no change") + return nil + } - d.State = state + d.State = state - // ignore change info, we already known - _, err := d.update(log, pubchan) - if err != nil { - return err - } + // ignore change info, we already known + _, err := d.update(log, pubchan) + if err != nil { + return err + } - if err := d.publishState(pubchan); err != nil { - return err - } + if err := d.publishState(pubchan); err != nil { + return err + } - return nil + return nil } - func (d *Device) CheckSetpoint(log *zerolog.Logger, pubchan chan Message) error { - change, err := d.update(log, pubchan) - if err != nil { - return err - } + change, err := d.update(log, pubchan) + if err != nil { + return err + } - if change { - if err := d.publishState(pubchan); err != nil { - return err - } - } + if change { + if err := d.publishState(pubchan); err != nil { + return err + } + } - return nil + return nil } - func (d *Device) update(log *zerolog.Logger, pubchan chan Message) (bool, error) { - // Handle all the update setpoint logic - // push in pubChan a Message if setpoint need to be update - *log = log.With(). - Str("device", d.Name). - Int("current_setpoint", d.CurrentSetpoint). - Str("State.Mode", d.State.Mode). - Int("State.Setpoint", d.State.Setpoint). - Str("State.Program_name", d.State.Program_name). - Logger() - log.Info().Msg("Check if setpoint need an update") + // Handle all the update setpoint logic + // push in pubChan a Message if setpoint need to be update + *log = log.With(). + Str("device", d.Name). + Int("current_setpoint", d.CurrentSetpoint). + Str("State.Mode", d.State.Mode). + Int("State.Setpoint", d.State.Setpoint). + Str("State.Program_name", d.State.Program_name). + Logger() + log.Debug().Msg("check if setpoint need an update") - switch d.State.Mode { - case "always": - return d.handle_always(log, pubchan) + switch d.State.Mode { + case "always": + return d.handle_always(log, pubchan) - case "until_time": - return d.handle_until_time(log, pubchan) + case "until_time": + return d.handle_until_time(log, pubchan) - case "until_next": - return d.handle_until_next(log, pubchan) - case "program": - return d.handle_program(log, pubchan) - default: - log.Info().Msg("Use default mode") - return d.handle_reset_state(log, pubchan) - } + case "until_next": + return d.handle_until_next(log, pubchan) + case "program": + return d.handle_program(log, pubchan) + default: + log.Info().Msg("Use default mode") + return d.handle_reset_state(log, pubchan) + } } - func (d *Device) onMessage(ctx context.Context, msg mqtt.Message) { - log := zerolog.Ctx(ctx).With(). - Str("action", "device state receive"). - Str("device", d.Name). - Logger() + log := zerolog.Ctx(ctx).With(). + Str("action", "device state receive"). + Str("device", d.Name). + Logger() - log.Debug(). - Str("topic", msg.Topic()). - Str("payload", string(msg.Payload())). - Msg("Message get") + log.Debug(). + Str("topic", msg.Topic()). + Str("payload", string(msg.Payload())). + Msg("Message get") - obj, err := oj.ParseString(string(msg.Payload())) - if err != nil { - log.Error().Err(err).Msg("during payload parse") - return - } + obj, err := oj.ParseString(string(msg.Payload())) + if err != nil { + log.Error().Err(err).Msg("during payload parse") + return + } - x, err := jp.ParseString(d.Settings.TVR.Setpoint_state_jp) - if err != nil { - log.Error().Err(err).Msg("while parsing payload") - return - } + x, err := jp.ParseString(d.Settings.TVR.Setpoint_state_jp) + if err != nil { + log.Error().Err(err).Msg("while parsing payload") + return + } - r := x.First(obj) + r := x.First(obj) - if v, ok := r.(int64); ok{ - d.CurrentSetpoint = int(v) - } else { - log.Error().Err(err).Interface("parsing payload", r).Msg("while parsing payload") - } + if v, ok := r.(int64); ok { + d.CurrentSetpoint = int(v) + } else { + log.Error().Err(err).Interface("parsing payload", r).Msg("while parsing payload") + } } - func (d *Device) handle_reset_state(log *zerolog.Logger, pubchan chan Message) (bool, error) { - // called when need to fallback to program mode previous or default - program, err := d.Program() - if err != nil { - return false, err - } + // called when need to fallback to program mode previous or default + program, err := d.Program() + if err != nil { + return false, err + } - current_setpoint := program.Current() + current_setpoint := program.Current() - value, err := current_setpoint.Value(d.Settings.Presets) - if err != nil { - return false, err - } + value, err := current_setpoint.Value(d.Settings.Presets) + if err != nil { + return false, err + } - d.State = DeviceState{ - Setpoint: value, - Mode: "program", - Program_name: d.ProgramName(), - Time: timeNow(), - } + d.State = DeviceState{ + Setpoint: value, + Mode: "program", + Program_name: d.ProgramName(), + Time: timeNow(), + } - if d.CurrentSetpoint != value { - log.Info().Msg("publish setpoint update") - err = d.SetSetpoint(value, pubchan) - if err != nil { - return false, err - } - } + if d.CurrentSetpoint != value { + log.Info().Msg("publish setpoint update") + err = d.SetSetpoint(value, pubchan) + if err != nil { + return false, err + } + } - return true, nil + return true, nil } - func (d *Device) handle_always(log *zerolog.Logger, pubchan chan Message) (bool, error) { - // return true if change made + // return true if change made - if d.State.Setpoint != d.CurrentSetpoint { - if err:= d.SetSetpoint(d.State.Setpoint, pubchan); err != nil { - return false, err - } - return true, nil - } + if d.State.Setpoint != d.CurrentSetpoint { + if err := d.SetSetpoint(d.State.Setpoint, pubchan); err != nil { + return false, err + } + return true, nil + } - log.Info().Msg("no setpoint update") - return false, nil + log.Info().Msg("no setpoint update") + return false, nil } func (d *Device) handle_until_time(log *zerolog.Logger, pubchan chan Message) (bool, error) { - *log = log.With().Time("until_time", d.State.Until_time).Logger() + *log = log.With().Time("until_time", d.State.Until_time).Logger() - if d.State.Until_time.Before(timeNow()) { - log.Info().Msg("until_time passed, reset") - return d.handle_reset_state(log, pubchan) - } + if d.State.Until_time.Before(timeNow()) { + log.Info().Msg("until_time passed, reset") + return d.handle_reset_state(log, pubchan) + } - if d.State.Setpoint != d.CurrentSetpoint { - log.Info().Msg("need setpoint update") - if err := d.SetSetpoint(d.State.Setpoint, pubchan); err != nil { - return false, err - } - return true, nil - } + if d.State.Setpoint != d.CurrentSetpoint { + log.Info().Msg("need setpoint update") + if err := d.SetSetpoint(d.State.Setpoint, pubchan); err != nil { + return false, err + } + return true, nil + } - log.Info().Msg("no setpoint update") - return false, nil + log.Info().Msg("no setpoint update") + return false, nil } func (d *Device) handle_until_next(log *zerolog.Logger, pubchan chan Message) (bool, error) { - *log = log.With().Time("until_next", d.State.Time).Logger() + *log = log.With().Time("until_next", d.State.Time).Logger() - program, err := d.Program() - if err != nil { - return false, err - } + program, err := d.Program() + if err != nil { + return false, err + } - next, err := program.NextTime(d.State.Time) - if err!= nil { - return false, err - } + next, err := program.NextTime(d.State.Time) + if err != nil { + return false, err + } - if timeNow().After(next) { - // force current program - // reset state - log.Info().Time("now", timeNow()).Time("next", next).Msg("until_next expired") - return d.handle_reset_state(log, pubchan) - } + if timeNow().After(next) { + // force current program + // reset state + log.Info().Time("now", timeNow()).Time("next", next).Msg("until_next expired") + return d.handle_reset_state(log, pubchan) + } - if d.State.Setpoint != d.CurrentSetpoint { - log.Info().Msg("need setpoint update") - if err := d.SetSetpoint(d.State.Setpoint, pubchan); err != nil { - return false, err - } - return true, nil - } + if d.State.Setpoint != d.CurrentSetpoint { + log.Info().Msg("need setpoint update") + if err := d.SetSetpoint(d.State.Setpoint, pubchan); err != nil { + return false, err + } + return true, nil + } - log.Info().Msg("no setpoint update") - return false, nil + log.Info().Msg("no setpoint update") + return false, nil } func (d *Device) handle_program(log *zerolog.Logger, pubchan chan Message) (bool, error) { - *log = log.With().Str("program", d.State.Program_name).Logger() + *log = log.With().Str("program", d.State.Program_name).Logger() - program, err := d.Program() - if err != nil { - return false, err - } + program, err := d.Program() + if err != nil { + return false, err + } - current_setpoint := program.Current() + current_setpoint := program.Current() - value, err := current_setpoint.Value(d.Settings.Presets) - if err != nil { - return false, err - } + value, err := current_setpoint.Value(d.Settings.Presets) + if err != nil { + return false, err + } - if d.CurrentSetpoint != value { - log.Info().Msg("publish setpoint update") - if err := d.SetSetpoint(value, pubchan); err != nil { - return false, err - } - d.State.Setpoint = value - return true, nil - } + if d.CurrentSetpoint != value { + log.Info().Msg("publish setpoint update") + if err := d.SetSetpoint(value, pubchan); err != nil { + return false, err + } + d.State.Setpoint = value + return true, nil + } - log.Info().Msg("no setpoint update") - return false, nil + log.Info().Msg("no setpoint update") + return false, nil } diff --git a/pkg/device/device_manager.go b/pkg/device/device_manager.go index 0cea6da..44239f4 100644 --- a/pkg/device/device_manager.go +++ b/pkg/device/device_manager.go @@ -1,99 +1,100 @@ package device import ( - "fmt" - "context" - "github.com/eclipse/paho.mqtt.golang" - "github.com/rs/zerolog" + "context" + "fmt" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/rs/zerolog" ) type Subscriber func(string, int, func(context.Context, mqtt.Message)) context.CancelFunc type DeviceManager struct { - devices map[string]*Device - subscriber Subscriber - sub_cancel map[string]context.CancelFunc - PubChan chan Message + devices map[string]*Device + subscriber Subscriber + sub_cancel map[string]context.CancelFunc + PubChan chan Message } func NewDeviceManager(pubchan chan Message, subscriber Subscriber) *DeviceManager { - return &DeviceManager{ - devices: make(map[string]*Device), - sub_cancel: make(map[string]context.CancelFunc), - subscriber: subscriber, - PubChan: pubchan, - } + return &DeviceManager{ + devices: make(map[string]*Device), + sub_cancel: make(map[string]context.CancelFunc), + subscriber: subscriber, + PubChan: pubchan, + } } func (m *DeviceManager) Get(name string) (*Device, bool) { - device, ok := m.devices[name] - return device, ok + device, ok := m.devices[name] + return device, ok } func (m *DeviceManager) Add(device Device) error { - if _, prs := m.devices[device.Name]; prs { - return fmt.Errorf("device %s already exist", device.Name) - } - m.devices[device.Name] = &device + if _, prs := m.devices[device.Name]; prs { + return fmt.Errorf("device %s already exist", device.Name) + } + m.devices[device.Name] = &device - // subscribe to state topic - topic, err := device.ListenTopic() - if err != nil { - return err - } - if topic != "" { - cancel := m.subscriber(topic, 2, device.onMessage) - m.sub_cancel[device.Name] = cancel - } - return nil + // subscribe to state topic + topic, err := device.ListenTopic() + if err != nil { + return err + } + if topic != "" { + cancel := m.subscriber(topic, 2, device.onMessage) + m.sub_cancel[device.Name] = cancel + } + return nil } func (m *DeviceManager) Delete(name string) error { - return nil + return nil } func (m *DeviceManager) SetSettings(name string, settings DeviceSettings) error { - device, ok := m.devices[name] - if !ok { - return fmt.Errorf("Not existings device %s", name) - } - device.Settings = settings - return nil + device, ok := m.devices[name] + if !ok { + return fmt.Errorf("Not existings device %s", name) + } + device.Settings = settings + return nil } func (m *DeviceManager) SetState(name string, state DeviceState) error { - device, ok := m.devices[name] - if !ok { - return fmt.Errorf("Not existings device %s", name) - } - device.State = state - return nil + device, ok := m.devices[name] + if !ok { + return fmt.Errorf("Not existings device %s", name) + } + device.State = state + return nil } func (m *DeviceManager) Check(ctx context.Context, name string) error { - logger := zerolog.Ctx(ctx) + logger := zerolog.Ctx(ctx) - device, ok := m.devices[name] - if !ok { - return fmt.Errorf("Device %s don't exist", name) - } - err := device.CheckSetpoint(logger, m.PubChan) - if err != nil { - logger.Error().Err(err).Msg("") - return err - } - return nil + device, ok := m.devices[name] + if !ok { + return fmt.Errorf("Device %s don't exist", name) + } + err := device.CheckSetpoint(logger, m.PubChan) + if err != nil { + logger.Error().Err(err).Msg("") + return err + } + return nil } func (m *DeviceManager) CheckAll(ctx context.Context) error { - logger := zerolog.Ctx(ctx) + logger := zerolog.Ctx(ctx) - for _, device := range m.devices { - err := device.CheckSetpoint(logger, m.PubChan) - if err != nil { - logger.Error().Err(err).Msg("") - return err - } - } - return nil + for _, device := range m.devices { + err := device.CheckSetpoint(logger, m.PubChan) + if err != nil { + logger.Error().Err(err).Msg("") + return err + } + } + return nil } diff --git a/pkg/device/device_test.go b/pkg/device/device_test.go index a036031..96e809e 100644 --- a/pkg/device/device_test.go +++ b/pkg/device/device_test.go @@ -1,459 +1,455 @@ package device import ( - "testing" - "time" - "fmt" - "reflect" - "github.com/rs/zerolog" - "io/ioutil" + "fmt" + "io/ioutil" + "reflect" + "testing" + "time" + + "github.com/rs/zerolog" ) // monday var test_time = time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local) - var test_presets = []Preset{ - {Label: "default", Value: 17, Color: "#012a36"}, - {Label: "normal", Value: 19, Color: "#b6244f"}, + {Label: "default", Value: 17, Color: "#012a36"}, + {Label: "normal", Value: 19, Color: "#b6244f"}, } var test_setpoints = []Setpoint{ - {Start: 7*60, Preset_id: 1}, - {Start: 8*60, Preset_id: 0}, - {Start: 16*60, Preset_id: 1}, - {Start: 22*60, Preset_id: 0}, + {Start: 7 * 60, Preset_id: 1}, + {Start: 8 * 60, Preset_id: 0}, + {Start: 16 * 60, Preset_id: 1}, + {Start: 22 * 60, Preset_id: 0}, } var test_weekprogram = WeekProgram{ - Monday: test_setpoints, - Thuesday: test_setpoints, - Wednesday: test_setpoints, - Thursday : test_setpoints, - Friday: test_setpoints, - Saturday: test_setpoints, - Sunday: test_setpoints, + Monday: test_setpoints, + Thuesday: test_setpoints, + Wednesday: test_setpoints, + Thursday: test_setpoints, + Friday: test_setpoints, + Saturday: test_setpoints, + Sunday: test_setpoints, } var test_programs = Programs{ - "default": test_weekprogram, + "default": test_weekprogram, } -var test_device = Device { - Name: "valid", - Settings: DeviceSettings{ - Programs: test_programs, - Presets: test_presets, - TVR: DefaultTVRSettings, - }, - CurrentSetpoint: 0, - State: DeviceState{ - Mode: "program", - Setpoint: 14, - Time: test_time, - Program_name: "default", - }, +var test_device = Device{ + Name: "valid", + Settings: DeviceSettings{ + Programs: test_programs, + Presets: test_presets, + TVR: DefaultTVRSettings, + }, + CurrentSetpoint: 0, + State: DeviceState{ + Mode: "program", + Setpoint: 14, + Time: test_time, + Program_name: "default", + }, } func TestStateEquivalent(t *testing.T) { - var tests = []struct{ - state1 DeviceState - state2 DeviceState - want bool - }{ - { - DeviceState{ - Mode: "program", - Setpoint: 14, - Time: test_time, - Program_name: "default", - }, - DeviceState{ - Mode: "always", - Setpoint: 13, - Time: test_time, - }, - false, - }, - { - DeviceState{ - Mode: "program", - Setpoint: 14, - Time: test_time, - Program_name: "default", - }, - DeviceState{ - Mode: "program", - Setpoint: 14, - Time: test_time.Add(1*time.Minute), - Program_name: "default", - }, - false, - }, - { - DeviceState{ - Mode: "program", - Setpoint: 14, - Time: test_time, - Program_name: "default", - }, - DeviceState{ - Mode: "program", - Setpoint: 13, - Time: test_time, - Program_name: "default", - }, - true, - }, - { - DeviceState{ - Mode: "program", - Setpoint: 14, - Time: test_time, - Program_name: "default", - }, - DeviceState{ - Mode: "program", - Setpoint: 13, - Time: test_time, - Program_name: "other", - }, - false, - }, - { - DeviceState{ - Mode: "always", - Setpoint: 14, - Time: test_time, - }, - DeviceState{ - Mode: "always", - Setpoint: 14, - Time: test_time, - Program_name: "other", - Until_time: test_time, - }, - true, - }, - { - DeviceState{ - Mode: "always", - Setpoint: 14, - Time: test_time, - }, - DeviceState{ - Mode: "always", - Setpoint: 15, - Time: test_time, - Program_name: "other", - Until_time: test_time, - }, - false, - }, - { - DeviceState{ - Mode: "until_next", - Setpoint: 14, - Time: test_time, - }, - DeviceState{ - Mode: "until_next", - Setpoint: 14, - Time: test_time, - Program_name: "other", - Until_time: test_time, - }, - true, - }, - { - DeviceState{ - Mode: "until_next", - Setpoint: 14, - Time: test_time, - }, - DeviceState{ - Mode: "until_next", - Setpoint: 13, - Time: test_time, - Program_name: "other", - Until_time: test_time, - }, - false, - }, - { - DeviceState{ - Mode: "until_time", - Setpoint: 14, - Time: test_time, - Until_time: test_time.Add(1*time.Hour), - }, - DeviceState{ - Mode: "until_time", - Setpoint: 14, - Time: test_time, - Program_name: "other", - Until_time: test_time.Add(1*time.Hour), - }, - true, - }, - { - DeviceState{ - Mode: "until_time", - Setpoint: 14, - Time: test_time, - Until_time: test_time.Add(1*time.Hour), - }, - DeviceState{ - Mode: "until_time", - Setpoint: 13, - Time: test_time, - Program_name: "other", - Until_time: test_time.Add(1*time.Hour), - }, - false, - }, - { - DeviceState{ - Mode: "until_time", - Setpoint: 14, - Time: test_time, - Until_time: test_time.Add(1*time.Hour), - }, - DeviceState{ - Mode: "until_time", - Setpoint: 14, - Time: test_time, - Program_name: "other", - Until_time: test_time.Add(2*time.Hour), - }, - false, - }, - } - for i, tt := range tests { - testname := fmt.Sprintf("%d", i ) - t.Run(testname, func(t *testing.T) { - r := tt.state1.Equivalent(tt.state2) - if r != tt.want { - t.Errorf("got %t, want %t", r, tt.want) - } - }) - } + var tests = []struct { + state1 DeviceState + state2 DeviceState + want bool + }{ + { + DeviceState{ + Mode: "program", + Setpoint: 14, + Time: test_time, + Program_name: "default", + }, + DeviceState{ + Mode: "always", + Setpoint: 13, + Time: test_time, + }, + false, + }, + { + DeviceState{ + Mode: "program", + Setpoint: 14, + Time: test_time, + Program_name: "default", + }, + DeviceState{ + Mode: "program", + Setpoint: 14, + Time: test_time.Add(1 * time.Minute), + Program_name: "default", + }, + false, + }, + { + DeviceState{ + Mode: "program", + Setpoint: 14, + Time: test_time, + Program_name: "default", + }, + DeviceState{ + Mode: "program", + Setpoint: 13, + Time: test_time, + Program_name: "default", + }, + true, + }, + { + DeviceState{ + Mode: "program", + Setpoint: 14, + Time: test_time, + Program_name: "default", + }, + DeviceState{ + Mode: "program", + Setpoint: 13, + Time: test_time, + Program_name: "other", + }, + false, + }, + { + DeviceState{ + Mode: "always", + Setpoint: 14, + Time: test_time, + }, + DeviceState{ + Mode: "always", + Setpoint: 14, + Time: test_time, + Program_name: "other", + Until_time: test_time, + }, + true, + }, + { + DeviceState{ + Mode: "always", + Setpoint: 14, + Time: test_time, + }, + DeviceState{ + Mode: "always", + Setpoint: 15, + Time: test_time, + Program_name: "other", + Until_time: test_time, + }, + false, + }, + { + DeviceState{ + Mode: "until_next", + Setpoint: 14, + Time: test_time, + }, + DeviceState{ + Mode: "until_next", + Setpoint: 14, + Time: test_time, + Program_name: "other", + Until_time: test_time, + }, + true, + }, + { + DeviceState{ + Mode: "until_next", + Setpoint: 14, + Time: test_time, + }, + DeviceState{ + Mode: "until_next", + Setpoint: 13, + Time: test_time, + Program_name: "other", + Until_time: test_time, + }, + false, + }, + { + DeviceState{ + Mode: "until_time", + Setpoint: 14, + Time: test_time, + Until_time: test_time.Add(1 * time.Hour), + }, + DeviceState{ + Mode: "until_time", + Setpoint: 14, + Time: test_time, + Program_name: "other", + Until_time: test_time.Add(1 * time.Hour), + }, + true, + }, + { + DeviceState{ + Mode: "until_time", + Setpoint: 14, + Time: test_time, + Until_time: test_time.Add(1 * time.Hour), + }, + DeviceState{ + Mode: "until_time", + Setpoint: 13, + Time: test_time, + Program_name: "other", + Until_time: test_time.Add(1 * time.Hour), + }, + false, + }, + { + DeviceState{ + Mode: "until_time", + Setpoint: 14, + Time: test_time, + Until_time: test_time.Add(1 * time.Hour), + }, + DeviceState{ + Mode: "until_time", + Setpoint: 14, + Time: test_time, + Program_name: "other", + Until_time: test_time.Add(2 * time.Hour), + }, + false, + }, + } + for i, tt := range tests { + testname := fmt.Sprintf("%d", i) + t.Run(testname, func(t *testing.T) { + r := tt.state1.Equivalent(tt.state2) + if r != tt.want { + t.Errorf("got %t, want %t", r, tt.want) + } + }) + } } - func TestStateTopic(t *testing.T) { - topic := test_device.StateTopic() - if topic != "heater/valid/state" { - t.Errorf("Got %s; want heater/valid/state", topic) - } + topic := test_device.StateTopic() + if topic != "heater/valid/state" { + t.Errorf("Got %s; want heater/valid/state", topic) + } } func TestListenTopic(t *testing.T) { - topic, err := test_device.ListenTopic() - want := "zigbee2mqtt/TVR/valid" - if err != nil { - t.Errorf("Got %s", err.Error()) - } - if topic != want { - t.Errorf("Got %s; want %s", topic, want) - } + topic, err := test_device.ListenTopic() + want := "zigbee2mqtt/TVR/valid" + if err != nil { + t.Errorf("Got %s", err.Error()) + } + if topic != want { + t.Errorf("Got %s; want %s", topic, want) + } } func TestProgram(t *testing.T) { - //case 1: no program set in state return default - case1_device := test_device - case1_device.State.Program_name = "" + //case 1: no program set in state return default + case1_device := test_device + case1_device.State.Program_name = "" - //case 2: program set "confort" must return it - var test_confort_weekprogram = WeekProgram{ - Monday: test_setpoints, - Thuesday: test_setpoints, - Wednesday: test_setpoints, - Thursday : test_setpoints, - Friday: test_setpoints, - Saturday: test_setpoints, - Sunday: test_setpoints, - } - case2_device := test_device - case2_device.Settings.Programs = Programs{ - "default": test_weekprogram, - "confort": test_confort_weekprogram, - } - case2_device.State.Program_name = "confort" + //case 2: program set "confort" must return it + var test_confort_weekprogram = WeekProgram{ + Monday: test_setpoints, + Thuesday: test_setpoints, + Wednesday: test_setpoints, + Thursday: test_setpoints, + Friday: test_setpoints, + Saturday: test_setpoints, + Sunday: test_setpoints, + } + case2_device := test_device + case2_device.Settings.Programs = Programs{ + "default": test_weekprogram, + "confort": test_confort_weekprogram, + } + case2_device.State.Program_name = "confort" - //case 3: program set "confort" but not exist - case3_device := test_device - case3_device.State.Program_name = "confort" + //case 3: program set "confort" but not exist + case3_device := test_device + case3_device.State.Program_name = "confort" - var tests = []struct { - name string - device Device - result WeekProgram - err error - }{ - {"case 1 no program set use default", case1_device, DefaultWeekProgram, nil}, - {"case 2 program confort", case2_device, test_confort_weekprogram, nil}, - {"case 3 program confort no defined", case3_device, WeekProgram{}, Error("device valid don't have confort program")}, - } - for _, tt := range tests { - testname := fmt.Sprintf("%s", tt.name ) - t.Run(testname, func(t *testing.T) { - prog, err := tt.device.Program() - if err != tt.err { - t.Errorf("got %s, want %s", err, tt.err) - } - if !reflect.DeepEqual(prog,tt.result) { - t.Errorf("got %v, want %v", prog, tt.result) - } - }) - } + var tests = []struct { + name string + device Device + result WeekProgram + err error + }{ + {"case 1 no program set use default", case1_device, DefaultWeekProgram, nil}, + {"case 2 program confort", case2_device, test_confort_weekprogram, nil}, + {"case 3 program confort no defined", case3_device, WeekProgram{}, Error("device valid don't have confort program")}, + } + for _, tt := range tests { + testname := fmt.Sprintf("%s", tt.name) + t.Run(testname, func(t *testing.T) { + prog, err := tt.device.Program() + if err != tt.err { + t.Errorf("got %s, want %s", err, tt.err) + } + if !reflect.DeepEqual(prog, tt.result) { + t.Errorf("got %v, want %v", prog, tt.result) + } + }) + } } - func TestUpdate(t *testing.T) { - // device 1: currentSetpoint 0 program, not specified expect default - // device 2: currentSetpoint 0 program, unknown one, expect error - // device 3: currentSetpoint 0 always, 22 - // device 4: currentSetpoint 22 until_time, fixed at timeNow, for 2h 22 - // device 5: currentSetpoint 17 indem 4 but fixed timeNow-2h - // device 6: currentSetpoint 17, until_next set a timeNow, test time time now +1h : no change + // device 1: currentSetpoint 0 program, not specified expect default + // device 2: currentSetpoint 0 program, unknown one, expect error + // device 3: currentSetpoint 0 always, 22 + // device 4: currentSetpoint 22 until_time, fixed at timeNow, for 2h 22 + // device 5: currentSetpoint 17 indem 4 but fixed timeNow-2h + // device 6: currentSetpoint 17, until_next set a timeNow, test time time now +1h : no change + timeNow = func() time.Time { + return test_time + } - timeNow = func() time.Time { - return test_time - } + device1 := test_device + device1.Name = "1" + device1.State = DeviceState{ + Mode: "program", + Setpoint: 0, + Time: test_time, + Program_name: "", + } - device1 := test_device - device1.Name = "1" - device1.State = DeviceState{ - Mode: "program", - Setpoint: 0, - Time: test_time, - Program_name: "", - } + device2 := test_device + device2.Name = "2" + device2.State.Program_name = "unknown" - device2 := test_device - device2.Name = "2" - device2.State.Program_name = "unknown" + device3 := test_device + device3.Name = "3" + device3.State = DeviceState{ + Mode: "always", + Setpoint: 22, + } - device3 := test_device - device3.Name = "3" - device3.State = DeviceState{ - Mode: "always", - Setpoint: 22, - } + device4 := test_device + device4.Name = "4" + device4.CurrentSetpoint = 22 + device4.State = DeviceState{ + Mode: "until_time", + Setpoint: 22, + Time: timeNow(), + Until_time: timeNow().Add(2 * time.Hour), + } - device4 := test_device - device4.Name = "4" - device4.CurrentSetpoint = 22 - device4.State = DeviceState{ - Mode: "until_time", - Setpoint: 22, - Time: timeNow(), - Until_time: timeNow().Add(2*time.Hour), - } + device5 := test_device + device5.Name = "5" + device5.CurrentSetpoint = 17 + device5.State = DeviceState{ + Mode: "until_time", + Setpoint: 22, + Time: timeNow().Add(-2 * time.Hour), + Until_time: timeNow().Add(-1 * time.Minute), + } - device5 := test_device - device5.Name = "5" - device5.CurrentSetpoint = 17 - device5.State = DeviceState{ - Mode: "until_time", - Setpoint: 22, - Time: timeNow().Add(-2*time.Hour), - Until_time: timeNow().Add(-1*time.Minute), - } + device6 := test_device + device6.Name = "6" + device6.CurrentSetpoint = 22 + device6.State = DeviceState{ + Mode: "until_next", + Setpoint: 22, + Time: timeNow(), + } - device6 := test_device - device6.Name = "6" - device6.CurrentSetpoint = 22 - device6.State = DeviceState{ - Mode: "until_next", - Setpoint: 22, - Time: timeNow(), - } + var tests = []struct { + getTime func() time.Time + device Device + want []Message + change bool + err error + }{ + {timeNow, device1, []Message{ + { + Payload: []byte("{\"current_heating_setpoint\": 17}"), + Topic: "zigbee2mqtt/TVR/1/set", + Retain: false, + }, + }, true, nil}, + {timeNow, device2, []Message{}, false, Error("device 2 don't have unknown program")}, + {timeNow, device3, []Message{ + { + Payload: []byte("{\"current_heating_setpoint\": 22}"), + Topic: "zigbee2mqtt/TVR/3/set", + Retain: false, + }, + }, true, nil}, + {timeNow, device4, []Message{}, false, nil}, + {timeNow, device5, []Message{}, true, nil}, + {timeNow, device6, []Message{}, false, nil}, + {func() time.Time { return test_time.Add(7*time.Hour + 30*time.Minute) }, device6, []Message{ + { + Payload: []byte("{\"current_heating_setpoint\": 19}"), + Topic: "zigbee2mqtt/TVR/6/set", + Retain: false, + }, + }, true, nil}, + } + for _, tt := range tests { + testname := fmt.Sprintf("%s", tt.device.Name) + t.Run(testname, func(t *testing.T) { - var tests = []struct{ - getTime func() time.Time - device Device - want []Message - change bool - err error - }{ - {timeNow, device1, []Message{ - Message{ - Payload: []byte("{\"current_heating_setpoint\": 17}"), - Topic: "zigbee2mqtt/TVR/1/set", - Retain: false, - }, - }, true, nil} , - {timeNow, device2, []Message{}, false, Error("device 2 don't have unknown program")}, - {timeNow, device3, []Message{ - Message{ - Payload: []byte("{\"current_heating_setpoint\": 22}"), - Topic: "zigbee2mqtt/TVR/3/set", - Retain: false, - }, - }, true, nil} , - {timeNow, device4, []Message{}, false, nil} , - {timeNow, device5, []Message{}, true, nil} , - {timeNow, device6, []Message{}, false, nil} , - {func()time.Time{return test_time.Add(7*time.Hour+30*time.Minute)}, device6, []Message{ - Message{ - Payload: []byte("{\"current_heating_setpoint\": 19}"), - Topic: "zigbee2mqtt/TVR/6/set", - Retain: false, - }, - }, true, nil} , - } + timeNow = tt.getTime + result := []Message{} - for _, tt := range tests { - testname := fmt.Sprintf("%s", tt.device.Name ) - t.Run(testname, func(t *testing.T) { + rchan := make(chan Message, 10) + errchan := make(chan error, 1) + changechan := make(chan bool, 1) - timeNow = tt.getTime - result := []Message{} + go func(result chan Message, changechan chan bool, errchan chan error) { + logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger() + change, err := tt.device.update(&logger, result) + errchan <- err + changechan <- change + close(rchan) + close(changechan) + close(errchan) + }(rchan, changechan, errchan) - rchan := make(chan Message, 10) - errchan := make(chan error, 1) - changechan := make(chan bool, 1) + for msg := range rchan { + result = append(result, msg) + } - go func(result chan Message, changechan chan bool, errchan chan error) { - logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger() - change, err := tt.device.update(&logger, result) - errchan <- err - changechan <- change - close(rchan) - close(changechan) - close(errchan) - }(rchan, changechan, errchan) + err, ok := <-errchan + if !ok { + t.Fatal(err) + } - for msg := range rchan { - result = append(result, msg) - } + if err != tt.err { + t.Errorf("got %s, want %s", err, tt.err) + } - err, ok := <- errchan - if !ok { - t.Fatal(err) - } + change, ok := <-changechan + if !ok { + t.Fatal(err) + } - if err != tt.err { - t.Errorf("got %s, want %s", err, tt.err) - } + if change != tt.change { + t.Errorf("got %s, want %s", err, tt.err) + } - change, ok := <- changechan - if !ok { - t.Fatal(err) - } - - if change != tt.change { - t.Errorf("got %s, want %s", err, tt.err) - } - - if !reflect.DeepEqual(result,tt.want) { - t.Errorf("got %v, want %v", result, tt.want) - } - }) - } + if !reflect.DeepEqual(result, tt.want) { + t.Errorf("got %v, want %v", result, tt.want) + } + }) + } } diff --git a/pkg/device/settings.go b/pkg/device/settings.go index f21650b..7dd1b73 100644 --- a/pkg/device/settings.go +++ b/pkg/device/settings.go @@ -1,256 +1,255 @@ package device import ( - "time" - "bytes" - "text/template" - "fmt" + "bytes" + "fmt" + "text/template" + "time" ) type DayOfWeek int func (d DayOfWeek) Next() DayOfWeek { - if d == Sunday { - return Monday - } - return d+1 + if d == Sunday { + return Monday + } + return d + 1 } func (d DayOfWeek) Previous() DayOfWeek { - if d == Monday { - return Sunday - } - return d-1 + if d == Monday { + return Sunday + } + return d - 1 } func (d DayOfWeek) DaysBetween(n DayOfWeek) int { - var between int - if (n < d) { - between = 7 - int(d-n) - } else { - between = int(n-d) - } - return between + var between int + if n < d { + between = 7 - int(d-n) + } else { + between = int(n - d) + } + return between } const ( - Monday DayOfWeek = 0 - Thuesday DayOfWeek = 1 - Wednesday DayOfWeek = 2 - Thursday DayOfWeek = 3 - Friday DayOfWeek = 4 - Saturday DayOfWeek = 5 - Sunday DayOfWeek = 6 + Monday DayOfWeek = 0 + Thuesday DayOfWeek = 1 + Wednesday DayOfWeek = 2 + Thursday DayOfWeek = 3 + Friday DayOfWeek = 4 + Saturday DayOfWeek = 5 + Sunday DayOfWeek = 6 ) func WeekDayEnToFr(weekday time.Weekday) DayOfWeek { - // translate weekday to french week, start by Monday - return map[time.Weekday]DayOfWeek { - time.Monday : Monday, - time.Tuesday : Thuesday, - time.Wednesday: Wednesday, - time.Thursday : Thursday, - time.Friday: Friday, - time.Saturday: Saturday, - time.Sunday: Sunday, - }[weekday] + // translate weekday to french week, start by Monday + return map[time.Weekday]DayOfWeek{ + time.Monday: Monday, + time.Tuesday: Thuesday, + time.Wednesday: Wednesday, + time.Thursday: Thursday, + time.Friday: Friday, + time.Saturday: Saturday, + time.Sunday: Sunday, + }[weekday] } func daytime(t time.Time) int { - return t.Hour()*60 + t.Minute() + return t.Hour()*60 + t.Minute() } func weekday(t time.Time) DayOfWeek { - return WeekDayEnToFr(t.Weekday()) + return WeekDayEnToFr(t.Weekday()) } type Message struct { - Payload []byte - Topic string - Retain bool + Payload []byte + Topic string + Retain bool } type Setpoint struct { - Start int `json:"start"` - Preset_id int `json:"preset_id"` + Start int `json:"start"` + Preset_id int `json:"preset_id"` } func (s Setpoint) Value(presets []Preset) (int, error) { - //TODO need test - if len(presets) < s.Preset_id + 1 { - return 0, Error(fmt.Sprintf("preset id %d not found", s.Preset_id)) - } - return presets[s.Preset_id].Value, nil + //TODO need test + if len(presets) < s.Preset_id+1 { + return 0, Error(fmt.Sprintf("preset id %d not found", s.Preset_id)) + } + return presets[s.Preset_id].Value, nil } - type WeekProgram map[DayOfWeek][]Setpoint func (p WeekProgram) Current() Setpoint { - // TODO: need test - // return current Setpoint - now := timeNow() - weekday := weekday(now) - daytime := daytime(now) - setpoint := Setpoint{} - for _, sp := range p[weekday] { - if daytime < sp.Start { - break - } - setpoint = sp - } - return setpoint + // TODO: need test + // return current Setpoint + now := timeNow() + weekday := weekday(now) + daytime := daytime(now) + setpoint := Setpoint{} + for _, sp := range p[weekday] { + if daytime < sp.Start { + break + } + setpoint = sp + } + return setpoint } func (p WeekProgram) NextTime(t time.Time) (time.Time, error) { - // return next program change + // return next program change - weekday := weekday(t) - daytime := daytime(t) + weekday := weekday(t) + daytime := daytime(t) - // Recursive func to find setpoint on weekday - get := func (weekday DayOfWeek, daytime int) (Setpoint, bool) { - for _, sp := range p[weekday] { - if daytime <= sp.Start { - return sp, true - } - } - return Setpoint{}, false - } + // Recursive func to find setpoint on weekday + get := func(weekday DayOfWeek, daytime int) (Setpoint, bool) { + for _, sp := range p[weekday] { + if daytime <= sp.Start { + return sp, true + } + } + return Setpoint{}, false + } - startweekday := weekday - for { - if setpoint, ok := get(weekday, daytime); ok { - next := time.Date(t.Year(), - t.Month(), - t.Day() + startweekday.DaysBetween(weekday), - 0, 0, 0, 0, - time.Local) + startweekday := weekday + for { + if setpoint, ok := get(weekday, daytime); ok { + next := time.Date(t.Year(), + t.Month(), + t.Day()+startweekday.DaysBetween(weekday), + 0, 0, 0, 0, + time.Local) - next = next.Add( - time.Duration( (setpoint.Start) * int(time.Minute) )) - return next, nil - } + next = next.Add( + time.Duration((setpoint.Start) * int(time.Minute))) + return next, nil + } - weekday = weekday.Next() - daytime = 0 - if weekday == startweekday { - return time.Time{}, fmt.Errorf("Shouldn't happen no setpoint found over the week") - } - } + weekday = weekday.Next() + daytime = 0 + if weekday == startweekday { + return time.Time{}, Error("shouldn't happen no setpoint found over the week") + } + } } type Programs map[string]WeekProgram type Preset struct { - Label string `json:"label"` - Value int `json:"value"` - Color string `json:"color"` + Label string `json:"label"` + Value int `json:"value"` + Color string `json:"color"` } type TVRSettings struct { - Setpoint_topic string `json:"setpoint_topic"` - Setpoint_payload string `json:"setpoint_payload"` - Setpoint_state_topic string `json:"setpoint_state_topic"` - Setpoint_state_jp string `json:"setpoint_state_jp"` + Setpoint_topic string `json:"setpoint_topic"` + Setpoint_payload string `json:"setpoint_payload"` + Setpoint_state_topic string `json:"setpoint_state_topic"` + Setpoint_state_jp string `json:"setpoint_state_jp"` } func (s TVRSettings) FormatTopicState(device_name string) (string, error) { - type Variable struct { - Device string - } - variables := Variable{device_name} - t, err := template.New("topic").Parse(s.Setpoint_state_topic) - if err != nil { - return "", err - } - buf := new(bytes.Buffer) - err = t.Execute(buf, variables) - if err != nil { - return "", err - } - return buf.String(), nil + type Variable struct { + Device string + } + variables := Variable{device_name} + t, err := template.New("topic").Parse(s.Setpoint_state_topic) + if err != nil { + return "", err + } + buf := new(bytes.Buffer) + err = t.Execute(buf, variables) + if err != nil { + return "", err + } + return buf.String(), nil } func (s TVRSettings) FormatTopic(device_name string) (string, error) { - type Variable struct { - Device string - } - variables := Variable{device_name} - t, err := template.New("topic").Parse(s.Setpoint_topic) - if err != nil { - return "", err - } - buf := new(bytes.Buffer) - err = t.Execute(buf, variables) - if err != nil { - return "", err - } - return buf.String(), nil + type Variable struct { + Device string + } + variables := Variable{device_name} + t, err := template.New("topic").Parse(s.Setpoint_topic) + if err != nil { + return "", err + } + buf := new(bytes.Buffer) + err = t.Execute(buf, variables) + if err != nil { + return "", err + } + return buf.String(), nil } func (s TVRSettings) FormatPayload(setpoint int) (string, error) { - type Variable struct { - Setpoint int - } - variables := Variable{setpoint} - t, err := template.New("payload").Parse(s.Setpoint_payload) - if err != nil { - return "", err - } - buf := new(bytes.Buffer) - err = t.Execute(buf, variables) - if err != nil { - return "", err - } - return buf.String(), nil + type Variable struct { + Setpoint int + } + variables := Variable{setpoint} + t, err := template.New("payload").Parse(s.Setpoint_payload) + if err != nil { + return "", err + } + buf := new(bytes.Buffer) + err = t.Execute(buf, variables) + if err != nil { + return "", err + } + return buf.String(), nil } type DeviceSettings struct { - Programs Programs `json:"programs"` - Presets []Preset `json:"presets"` - TVR TVRSettings `json:"TVR"` + Programs Programs `json:"programs"` + Presets []Preset `json:"presets"` + TVR TVRSettings `json:"TVR"` } ////////////////////////////////////////////// // Defaults var DefaultPresets = []Preset{ - {Label: "default", Value: 17, Color: "#012a36"}, - {Label: "normal", Value: 19, Color: "#b6244f"}, + {Label: "default", Value: 17, Color: "#012a36"}, + {Label: "normal", Value: 19, Color: "#b6244f"}, } var defaultSetpoints = []Setpoint{ - {Start: 7*60, Preset_id: 1}, - {Start: 8*60, Preset_id: 0}, - {Start: 16*60, Preset_id: 1}, - {Start: 22*60, Preset_id: 0}, + {Start: 7 * 60, Preset_id: 1}, + {Start: 8 * 60, Preset_id: 0}, + {Start: 16 * 60, Preset_id: 1}, + {Start: 22 * 60, Preset_id: 0}, } var DefaultWeekProgram = WeekProgram{ - Monday: defaultSetpoints, - Thuesday: defaultSetpoints, - Wednesday: defaultSetpoints, - Thursday : defaultSetpoints, - Friday: defaultSetpoints, - Saturday: defaultSetpoints, - Sunday: defaultSetpoints, + Monday: defaultSetpoints, + Thuesday: defaultSetpoints, + Wednesday: defaultSetpoints, + Thursday: defaultSetpoints, + Friday: defaultSetpoints, + Saturday: defaultSetpoints, + Sunday: defaultSetpoints, } var DefaultPrograms = Programs{ - "default": DefaultWeekProgram, + "default": DefaultWeekProgram, } -var DefaultTVRSettings = TVRSettings { - Setpoint_topic : "zigbee2mqtt/TVR/{{.Device}}/set", - Setpoint_payload : "{\"current_heating_setpoint\": {{.Setpoint}}}", - Setpoint_state_topic : "zigbee2mqtt/TVR/{{.Device}}", - Setpoint_state_jp : "$.current_heating_setpoint", +var DefaultTVRSettings = TVRSettings{ + Setpoint_topic: "zigbee2mqtt/TVR/{{.Device}}/set", + Setpoint_payload: "{\"current_heating_setpoint\": {{.Setpoint}}}", + Setpoint_state_topic: "zigbee2mqtt/TVR/{{.Device}}", + Setpoint_state_jp: "$.current_heating_setpoint", } var DefaultDeviceSettings = DeviceSettings{ - Programs: DefaultPrograms, - Presets: DefaultPresets, - TVR: DefaultTVRSettings, + Programs: DefaultPrograms, + Presets: DefaultPresets, + TVR: DefaultTVRSettings, } diff --git a/pkg/device/settings_test.go b/pkg/device/settings_test.go index 684b5a7..5f65499 100644 --- a/pkg/device/settings_test.go +++ b/pkg/device/settings_test.go @@ -1,107 +1,104 @@ package device import ( - "testing" - "time" - "fmt" + "fmt" + "testing" + "time" ) func TestDaysBetween(t *testing.T) { - var tests = []struct{ - day1 DayOfWeek - day2 DayOfWeek - want int - }{ - { Monday, Thuesday, 1 }, - { Thuesday, Monday, 6 }, - { Sunday, Monday, 1 }, - { Sunday, Thuesday, 2 }, - { Friday, Friday, 0 }, - } - for _, tt := range tests { - testname := fmt.Sprintf("Days between %d and %d", tt.day1, tt.day2 ) - t.Run(testname, func(t *testing.T) { - between := tt.day1.DaysBetween(tt.day2) - if between != tt.want { - t.Errorf("got %d, want %d", between, tt.want) - } - }) - } + var tests = []struct { + day1 DayOfWeek + day2 DayOfWeek + want int + }{ + {Monday, Thuesday, 1}, + {Thuesday, Monday, 6}, + {Sunday, Monday, 1}, + {Sunday, Thuesday, 2}, + {Friday, Friday, 0}, + } + for _, tt := range tests { + testname := fmt.Sprintf("Days between %d and %d", tt.day1, tt.day2) + t.Run(testname, func(t *testing.T) { + between := tt.day1.DaysBetween(tt.day2) + if between != tt.want { + t.Errorf("got %d, want %d", between, tt.want) + } + }) + } } - func TestNextTime(t *testing.T) { - defaultSetpoints := []Setpoint{ - {Start: 7*60, Preset_id: 1}, - {Start: 8*60, Preset_id: 0}, - {Start: 16*60, Preset_id: 1}, - {Start: 22*60, Preset_id: 0}, - } + defaultSetpoints := []Setpoint{ + {Start: 7 * 60, Preset_id: 1}, + {Start: 8 * 60, Preset_id: 0}, + {Start: 16 * 60, Preset_id: 1}, + {Start: 22 * 60, Preset_id: 0}, + } - zeroSetpoints := []Setpoint{ - {Start: 0, Preset_id: 0}, - } + zeroSetpoints := []Setpoint{ + {Start: 0, Preset_id: 0}, + } + default_program := WeekProgram{ + Monday: defaultSetpoints, + Thuesday: defaultSetpoints, + Wednesday: defaultSetpoints, + Thursday: defaultSetpoints, + Friday: defaultSetpoints, + Saturday: defaultSetpoints, + Sunday: defaultSetpoints, + } - default_program := WeekProgram{ - Monday: defaultSetpoints, - Thuesday: defaultSetpoints, - Wednesday: defaultSetpoints, - Thursday : defaultSetpoints, - Friday: defaultSetpoints, - Saturday: defaultSetpoints, - Sunday: defaultSetpoints, - } + zero_program := WeekProgram{ + Monday: zeroSetpoints, + Thuesday: zeroSetpoints, + Wednesday: zeroSetpoints, + Thursday: zeroSetpoints, + Friday: zeroSetpoints, + Saturday: zeroSetpoints, + Sunday: zeroSetpoints, + } - zero_program := WeekProgram{ - Monday: zeroSetpoints, - Thuesday: zeroSetpoints, - Wednesday: zeroSetpoints, - Thursday : zeroSetpoints, - Friday: zeroSetpoints, - Saturday: zeroSetpoints, - Sunday: zeroSetpoints, - } - - - var tests = []struct{ - prog WeekProgram - time time.Time - want time.Time - }{ - { - default_program, - time.Date(2022, time.October, 23, 9, 0, 0, 0, time.Local), - time.Date(2022, time.October, 23, 16, 0, 0, 0, time.Local), - }, - { - default_program, - time.Date(2022, time.October, 24, 5, 0, 0, 0, time.Local), - time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local), - }, - { - default_program, - time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local), - time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local), - }, - { - zero_program, - time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local), - time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local), - }, - } - for _, tt := range tests { - testname := fmt.Sprintf("%s", tt.time.String() ) - t.Run(testname, func(t *testing.T) { - next, err := tt.prog.NextTime(tt.time) - if err != nil { - t.Fatalf(err.Error()) - } - if !next.Equal(tt.want) { - t.Errorf("got %s, want %s", next.String(), tt.want.String()) - } - }) - } + var tests = []struct { + prog WeekProgram + time time.Time + want time.Time + }{ + { + default_program, + time.Date(2022, time.October, 23, 9, 0, 0, 0, time.Local), + time.Date(2022, time.October, 23, 16, 0, 0, 0, time.Local), + }, + { + default_program, + time.Date(2022, time.October, 24, 5, 0, 0, 0, time.Local), + time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local), + }, + { + default_program, + time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local), + time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local), + }, + { + zero_program, + time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local), + time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local), + }, + } + for _, tt := range tests { + testname := fmt.Sprintf("%s", tt.time.String()) + t.Run(testname, func(t *testing.T) { + next, err := tt.prog.NextTime(tt.time) + if err != nil { + t.Fatalf(err.Error()) + } + if !next.Equal(tt.want) { + t.Errorf("got %s, want %s", next.String(), tt.want.String()) + } + }) + } }