This commit is contained in:
Nicolas Duhamel 2022-10-28 19:20:04 +02:00
parent 11b3622abe
commit 6837a07beb
6 changed files with 1171 additions and 1194 deletions

56
main.go
View File

@ -1,18 +1,19 @@
package main package main
import ( import (
"citadel/heater/pkg/device"
"citadel/heater/mqtt" "citadel/heater/mqtt"
mqttpaho "github.com/eclipse/paho.mqtt.golang" "citadel/heater/pkg/device"
"context" "context"
"encoding/json" "encoding/json"
"flag"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
"strings"
mqttpaho "github.com/eclipse/paho.mqtt.golang"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"flag"
) )
type Config struct { type Config struct {
@ -58,7 +59,7 @@ func (app *App) subscribe(
topic string, topic string,
qos int, qos int,
callback func(context.Context, mqttpaho.Message), callback func(context.Context, mqttpaho.Message),
) context.CancelFunc { ) context.CancelFunc {
ctx, cancelFunc := context.WithCancel(app.ctx) ctx, cancelFunc := context.WithCancel(app.ctx)
@ -95,7 +96,7 @@ func (app *App) runTicker() {
go func() { go func() {
for { for {
select { select {
case <- app.ctx.Done(): case <-app.ctx.Done():
return return
case t := <-ticker.C: case t := <-ticker.C:
logger.Info().Time("at", t).Msg("Tick") logger.Info().Time("at", t).Msg("Tick")
@ -115,9 +116,13 @@ func (app *App) onSettingsMessage(ctx context.Context, msg mqttpaho.Message) {
Str("device", device_name). Str("device", device_name).
Logger() Logger()
logger.Debug().Str("topic", msg.Topic()).Str("payload", string(msg.Payload())).Msg("")
ctx = logger.WithContext(ctx) ctx = logger.WithContext(ctx)
logger.Debug().
Str("topic", msg.Topic()).
Str("payload", string(msg.Payload())).
Msg("")
var device_settings device.DeviceSettings var device_settings device.DeviceSettings
if err := json.Unmarshal(msg.Payload(), &device_settings); err != nil { if err := json.Unmarshal(msg.Payload(), &device_settings); err != nil {
logger.Error().Err(err).Msg("Parsing payload") logger.Error().Err(err).Msg("Parsing payload")
@ -129,21 +134,19 @@ func (app *App) onSettingsMessage(ctx context.Context, msg mqttpaho.Message) {
} }
if _, ok := app.DeviceManager.Get(device_name); !ok { if _, ok := app.DeviceManager.Get(device_name); !ok {
err := app.DeviceManager.Add(tvr) if err := app.DeviceManager.Add(tvr); err != nil {
if err != nil { logger.Error().Err(err).Msg("unexpected, abord")
logger.Error().Err(err).Msg("Unexpected")
return return
} }
} else { } else {
err := app.DeviceManager.SetSettings(device_name, device_settings) if err := app.DeviceManager.
if err != nil { SetSettings(device_name, device_settings); err != nil {
logger.Error().Err(err).Msg("Unexpected") logger.Error().Err(err).Msg("unexpected, abord")
return return
} }
} }
err := app.DeviceManager.Check(ctx, device_name) if err := app.DeviceManager.Check(ctx, device_name); err != nil {
if err != nil {
logger.Error().Err(err).Msg("During device `Check`") logger.Error().Err(err).Msg("During device `Check`")
} }
} }
@ -159,29 +162,24 @@ func (app *App) onSetStateMessage(ctx context.Context, msg mqttpaho.Message) {
Logger() Logger()
ctx = logger.WithContext(ctx) ctx = logger.WithContext(ctx)
var state device.DeviceState var state device.DeviceState
if err := json.Unmarshal(msg.Payload(), &state); err != nil { if err := json.Unmarshal(msg.Payload(), &state); err != nil {
logger.Error().Err(err).Msg("Error while parsing payload") 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 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 { if err := device.SetState(&logger, state, app.PubChan); err != nil {
logger.Error().Err(err).Msg("") logger.Error().Err(err).Msg("unexpected")
return return
} }
} else {
logger.Error().Msg("Device not found, abord")
}
} }
func (app *App) Run() { func (app *App) Run() {
ctx, ctxCancel := context.WithCancel(context.Background()) ctx, ctxCancel := context.WithCancel(context.Background())
output := zerolog.ConsoleWriter{Out: os.Stderr} output := zerolog.ConsoleWriter{Out: os.Stderr}
@ -191,7 +189,6 @@ func (app *App) Run() {
ctx = log.WithContext(ctx) ctx = log.WithContext(ctx)
defer ctxCancel() defer ctxCancel()
if err := app.mqtt_hub.Connect(ctx); err != nil { if err := app.mqtt_hub.Connect(ctx); err != nil {
@ -221,7 +218,7 @@ func (app *App) Run() {
Str("payload", string(msg.Payload)). Str("payload", string(msg.Payload)).
Msg("publish") 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: case err, ok := <-pub.OnError:
if !ok { if !ok {
@ -271,4 +268,3 @@ func main() {
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
} }

View File

@ -1,13 +1,14 @@
package device package device
import ( import (
"github.com/eclipse/paho.mqtt.golang" "context"
"github.com/ohler55/ojg/jp" "encoding/json"
"github.com/ohler55/ojg/oj"
"fmt" "fmt"
"time" "time"
"encoding/json"
"context" mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
"github.com/rs/zerolog" "github.com/rs/zerolog"
// "reflect" // "reflect"
) )
@ -16,10 +17,9 @@ var timeNow = func() time.Time {
return time.Now() return time.Now()
} }
type Error string type Error string
func (e Error) Error() string { return string(e) }
func (e Error) Error() string { return string(e) }
type DeviceState struct { type DeviceState struct {
Mode string `json:"mode"` Mode string `json:"mode"`
@ -62,7 +62,6 @@ func (s *DeviceState) Equivalent(state DeviceState) bool {
return true return true
} }
// Internal state // Internal state
type Device struct { type Device struct {
Name string Name string
@ -71,7 +70,6 @@ type Device struct {
State DeviceState State DeviceState
} }
func (d *Device) StateTopic() string { func (d *Device) StateTopic() string {
return fmt.Sprintf("heater/%s/state", d.Name) return fmt.Sprintf("heater/%s/state", d.Name)
} }
@ -80,7 +78,6 @@ 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) { func (d *Device) Program() (WeekProgram, error) {
// return current device program if specified or default one // return current device program if specified or default one
prog_name := "default" prog_name := "default"
@ -96,8 +93,7 @@ func (d *Device) Program() (WeekProgram, error) {
return program, nil return program, nil
} }
func (d *Device) ProgramName() string {
func (d *Device) ProgramName() (string) {
prog_name := "default" prog_name := "default"
if d.State.Program_name != "" { if d.State.Program_name != "" {
prog_name = d.State.Program_name prog_name = d.State.Program_name
@ -105,7 +101,6 @@ func (d *Device) ProgramName() (string) {
return prog_name return prog_name
} }
func (d *Device) publishState(pubchan chan Message) error { func (d *Device) publishState(pubchan chan Message) error {
payload, err := json.Marshal(d.State) payload, err := json.Marshal(d.State)
@ -113,7 +108,7 @@ func (d *Device) publishState(pubchan chan Message) error {
return err return err
} }
pubchan <- Message { pubchan <- Message{
Topic: d.StateTopic(), Topic: d.StateTopic(),
Payload: payload, Payload: payload,
Retain: true, Retain: true,
@ -122,7 +117,6 @@ func (d *Device) publishState(pubchan chan Message) error {
return nil return nil
} }
func (d *Device) SetSetpoint(value int, pubchan chan Message) error { func (d *Device) SetSetpoint(value int, pubchan chan Message) error {
topic, err := d.Settings.TVR.FormatTopic(d.Name) topic, err := d.Settings.TVR.FormatTopic(d.Name)
if err != nil { if err != nil {
@ -142,7 +136,6 @@ func (d *Device) SetSetpoint(value int, pubchan chan Message) error {
return nil return nil
} }
func (d *Device) SetState(log *zerolog.Logger, state DeviceState, pubchan chan Message) error { func (d *Device) SetState(log *zerolog.Logger, state DeviceState, pubchan chan Message) error {
// If same state do nothing // If same state do nothing
// else use checksetpoint for changing state // else use checksetpoint for changing state
@ -166,7 +159,6 @@ func (d *Device) SetState(log *zerolog.Logger, state DeviceState, pubchan chan M
return nil return nil
} }
func (d *Device) CheckSetpoint(log *zerolog.Logger, pubchan chan Message) error { func (d *Device) CheckSetpoint(log *zerolog.Logger, pubchan chan Message) error {
change, err := d.update(log, pubchan) change, err := d.update(log, pubchan)
if err != nil { if err != nil {
@ -182,7 +174,6 @@ func (d *Device) CheckSetpoint(log *zerolog.Logger, pubchan chan Message) error
return nil return nil
} }
func (d *Device) update(log *zerolog.Logger, pubchan chan Message) (bool, error) { func (d *Device) update(log *zerolog.Logger, pubchan chan Message) (bool, error) {
// Handle all the update setpoint logic // Handle all the update setpoint logic
// push in pubChan a Message if setpoint need to be update // push in pubChan a Message if setpoint need to be update
@ -193,7 +184,7 @@ func (d *Device) update(log *zerolog.Logger, pubchan chan Message) (bool, error)
Int("State.Setpoint", d.State.Setpoint). Int("State.Setpoint", d.State.Setpoint).
Str("State.Program_name", d.State.Program_name). Str("State.Program_name", d.State.Program_name).
Logger() Logger()
log.Info().Msg("Check if setpoint need an update") log.Debug().Msg("check if setpoint need an update")
switch d.State.Mode { switch d.State.Mode {
case "always": case "always":
@ -213,7 +204,6 @@ func (d *Device) update(log *zerolog.Logger, pubchan chan Message) (bool, error)
} }
func (d *Device) onMessage(ctx context.Context, msg mqtt.Message) { func (d *Device) onMessage(ctx context.Context, msg mqtt.Message) {
log := zerolog.Ctx(ctx).With(). log := zerolog.Ctx(ctx).With().
Str("action", "device state receive"). Str("action", "device state receive").
@ -239,7 +229,7 @@ func (d *Device) onMessage(ctx context.Context, msg mqtt.Message) {
r := x.First(obj) r := x.First(obj)
if v, ok := r.(int64); ok{ if v, ok := r.(int64); ok {
d.CurrentSetpoint = int(v) d.CurrentSetpoint = int(v)
} else { } else {
log.Error().Err(err).Interface("parsing payload", r).Msg("while parsing payload") log.Error().Err(err).Interface("parsing payload", r).Msg("while parsing payload")
@ -247,7 +237,6 @@ func (d *Device) onMessage(ctx context.Context, msg mqtt.Message) {
} }
func (d *Device) handle_reset_state(log *zerolog.Logger, pubchan chan Message) (bool, error) { 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 // called when need to fallback to program mode previous or default
program, err := d.Program() program, err := d.Program()
@ -280,12 +269,11 @@ func (d *Device) handle_reset_state(log *zerolog.Logger, pubchan chan Message) (
return true, nil return true, nil
} }
func (d *Device) handle_always(log *zerolog.Logger, pubchan chan Message) (bool, error) { 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 d.State.Setpoint != d.CurrentSetpoint {
if err:= d.SetSetpoint(d.State.Setpoint, pubchan); err != nil { if err := d.SetSetpoint(d.State.Setpoint, pubchan); err != nil {
return false, err return false, err
} }
return true, nil return true, nil
@ -324,7 +312,7 @@ func (d *Device) handle_until_next(log *zerolog.Logger, pubchan chan Message) (b
} }
next, err := program.NextTime(d.State.Time) next, err := program.NextTime(d.State.Time)
if err!= nil { if err != nil {
return false, err return false, err
} }

View File

@ -1,9 +1,10 @@
package device package device
import ( import (
"fmt"
"context" "context"
"github.com/eclipse/paho.mqtt.golang" "fmt"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )

View File

@ -1,35 +1,35 @@
package device package device
import ( import (
"fmt"
"io/ioutil"
"reflect"
"testing" "testing"
"time" "time"
"fmt"
"reflect"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"io/ioutil"
) )
// monday // monday
var test_time = time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local) var test_time = time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local)
var test_presets = []Preset{ var test_presets = []Preset{
{Label: "default", Value: 17, Color: "#012a36"}, {Label: "default", Value: 17, Color: "#012a36"},
{Label: "normal", Value: 19, Color: "#b6244f"}, {Label: "normal", Value: 19, Color: "#b6244f"},
} }
var test_setpoints = []Setpoint{ var test_setpoints = []Setpoint{
{Start: 7*60, Preset_id: 1}, {Start: 7 * 60, Preset_id: 1},
{Start: 8*60, Preset_id: 0}, {Start: 8 * 60, Preset_id: 0},
{Start: 16*60, Preset_id: 1}, {Start: 16 * 60, Preset_id: 1},
{Start: 22*60, Preset_id: 0}, {Start: 22 * 60, Preset_id: 0},
} }
var test_weekprogram = WeekProgram{ var test_weekprogram = WeekProgram{
Monday: test_setpoints, Monday: test_setpoints,
Thuesday: test_setpoints, Thuesday: test_setpoints,
Wednesday: test_setpoints, Wednesday: test_setpoints,
Thursday : test_setpoints, Thursday: test_setpoints,
Friday: test_setpoints, Friday: test_setpoints,
Saturday: test_setpoints, Saturday: test_setpoints,
Sunday: test_setpoints, Sunday: test_setpoints,
@ -39,7 +39,7 @@ var test_programs = Programs{
"default": test_weekprogram, "default": test_weekprogram,
} }
var test_device = Device { var test_device = Device{
Name: "valid", Name: "valid",
Settings: DeviceSettings{ Settings: DeviceSettings{
Programs: test_programs, Programs: test_programs,
@ -57,7 +57,7 @@ var test_device = Device {
func TestStateEquivalent(t *testing.T) { func TestStateEquivalent(t *testing.T) {
var tests = []struct{ var tests = []struct {
state1 DeviceState state1 DeviceState
state2 DeviceState state2 DeviceState
want bool want bool
@ -86,7 +86,7 @@ func TestStateEquivalent(t *testing.T) {
DeviceState{ DeviceState{
Mode: "program", Mode: "program",
Setpoint: 14, Setpoint: 14,
Time: test_time.Add(1*time.Minute), Time: test_time.Add(1 * time.Minute),
Program_name: "default", Program_name: "default",
}, },
false, false,
@ -186,14 +186,14 @@ func TestStateEquivalent(t *testing.T) {
Mode: "until_time", Mode: "until_time",
Setpoint: 14, Setpoint: 14,
Time: test_time, Time: test_time,
Until_time: test_time.Add(1*time.Hour), Until_time: test_time.Add(1 * time.Hour),
}, },
DeviceState{ DeviceState{
Mode: "until_time", Mode: "until_time",
Setpoint: 14, Setpoint: 14,
Time: test_time, Time: test_time,
Program_name: "other", Program_name: "other",
Until_time: test_time.Add(1*time.Hour), Until_time: test_time.Add(1 * time.Hour),
}, },
true, true,
}, },
@ -202,14 +202,14 @@ func TestStateEquivalent(t *testing.T) {
Mode: "until_time", Mode: "until_time",
Setpoint: 14, Setpoint: 14,
Time: test_time, Time: test_time,
Until_time: test_time.Add(1*time.Hour), Until_time: test_time.Add(1 * time.Hour),
}, },
DeviceState{ DeviceState{
Mode: "until_time", Mode: "until_time",
Setpoint: 13, Setpoint: 13,
Time: test_time, Time: test_time,
Program_name: "other", Program_name: "other",
Until_time: test_time.Add(1*time.Hour), Until_time: test_time.Add(1 * time.Hour),
}, },
false, false,
}, },
@ -218,20 +218,20 @@ func TestStateEquivalent(t *testing.T) {
Mode: "until_time", Mode: "until_time",
Setpoint: 14, Setpoint: 14,
Time: test_time, Time: test_time,
Until_time: test_time.Add(1*time.Hour), Until_time: test_time.Add(1 * time.Hour),
}, },
DeviceState{ DeviceState{
Mode: "until_time", Mode: "until_time",
Setpoint: 14, Setpoint: 14,
Time: test_time, Time: test_time,
Program_name: "other", Program_name: "other",
Until_time: test_time.Add(2*time.Hour), Until_time: test_time.Add(2 * time.Hour),
}, },
false, false,
}, },
} }
for i, tt := range tests { for i, tt := range tests {
testname := fmt.Sprintf("%d", i ) testname := fmt.Sprintf("%d", i)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
r := tt.state1.Equivalent(tt.state2) r := tt.state1.Equivalent(tt.state2)
if r != tt.want { if r != tt.want {
@ -241,7 +241,6 @@ func TestStateEquivalent(t *testing.T) {
} }
} }
func TestStateTopic(t *testing.T) { func TestStateTopic(t *testing.T) {
topic := test_device.StateTopic() topic := test_device.StateTopic()
if topic != "heater/valid/state" { if topic != "heater/valid/state" {
@ -270,7 +269,7 @@ func TestProgram(t *testing.T) {
Monday: test_setpoints, Monday: test_setpoints,
Thuesday: test_setpoints, Thuesday: test_setpoints,
Wednesday: test_setpoints, Wednesday: test_setpoints,
Thursday : test_setpoints, Thursday: test_setpoints,
Friday: test_setpoints, Friday: test_setpoints,
Saturday: test_setpoints, Saturday: test_setpoints,
Sunday: test_setpoints, Sunday: test_setpoints,
@ -297,20 +296,19 @@ func TestProgram(t *testing.T) {
{"case 3 program confort no defined", case3_device, WeekProgram{}, Error("device valid don't have confort program")}, {"case 3 program confort no defined", case3_device, WeekProgram{}, Error("device valid don't have confort program")},
} }
for _, tt := range tests { for _, tt := range tests {
testname := fmt.Sprintf("%s", tt.name ) testname := fmt.Sprintf("%s", tt.name)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
prog, err := tt.device.Program() prog, err := tt.device.Program()
if err != tt.err { if err != tt.err {
t.Errorf("got %s, want %s", err, tt.err) t.Errorf("got %s, want %s", err, tt.err)
} }
if !reflect.DeepEqual(prog,tt.result) { if !reflect.DeepEqual(prog, tt.result) {
t.Errorf("got %v, want %v", prog, tt.result) t.Errorf("got %v, want %v", prog, tt.result)
} }
}) })
} }
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
// device 1: currentSetpoint 0 program, not specified expect default // device 1: currentSetpoint 0 program, not specified expect default
// device 2: currentSetpoint 0 program, unknown one, expect error // device 2: currentSetpoint 0 program, unknown one, expect error
@ -319,7 +317,6 @@ func TestUpdate(t *testing.T) {
// device 5: currentSetpoint 17 indem 4 but fixed timeNow-2h // 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 6: currentSetpoint 17, until_next set a timeNow, test time time now +1h : no change
timeNow = func() time.Time { timeNow = func() time.Time {
return test_time return test_time
} }
@ -351,7 +348,7 @@ func TestUpdate(t *testing.T) {
Mode: "until_time", Mode: "until_time",
Setpoint: 22, Setpoint: 22,
Time: timeNow(), Time: timeNow(),
Until_time: timeNow().Add(2*time.Hour), Until_time: timeNow().Add(2 * time.Hour),
} }
device5 := test_device device5 := test_device
@ -360,8 +357,8 @@ func TestUpdate(t *testing.T) {
device5.State = DeviceState{ device5.State = DeviceState{
Mode: "until_time", Mode: "until_time",
Setpoint: 22, Setpoint: 22,
Time: timeNow().Add(-2*time.Hour), Time: timeNow().Add(-2 * time.Hour),
Until_time: timeNow().Add(-1*time.Minute), Until_time: timeNow().Add(-1 * time.Minute),
} }
device6 := test_device device6 := test_device
@ -373,8 +370,7 @@ func TestUpdate(t *testing.T) {
Time: timeNow(), Time: timeNow(),
} }
var tests = []struct {
var tests = []struct{
getTime func() time.Time getTime func() time.Time
device Device device Device
want []Message want []Message
@ -382,34 +378,34 @@ func TestUpdate(t *testing.T) {
err error err error
}{ }{
{timeNow, device1, []Message{ {timeNow, device1, []Message{
Message{ {
Payload: []byte("{\"current_heating_setpoint\": 17}"), Payload: []byte("{\"current_heating_setpoint\": 17}"),
Topic: "zigbee2mqtt/TVR/1/set", Topic: "zigbee2mqtt/TVR/1/set",
Retain: false, Retain: false,
}, },
}, true, nil} , }, true, nil},
{timeNow, device2, []Message{}, false, Error("device 2 don't have unknown program")}, {timeNow, device2, []Message{}, false, Error("device 2 don't have unknown program")},
{timeNow, device3, []Message{ {timeNow, device3, []Message{
Message{ {
Payload: []byte("{\"current_heating_setpoint\": 22}"), Payload: []byte("{\"current_heating_setpoint\": 22}"),
Topic: "zigbee2mqtt/TVR/3/set", Topic: "zigbee2mqtt/TVR/3/set",
Retain: false, Retain: false,
}, },
}, true, nil} , }, true, nil},
{timeNow, device4, []Message{}, false, nil} , {timeNow, device4, []Message{}, false, nil},
{timeNow, device5, []Message{}, true, nil} , {timeNow, device5, []Message{}, true, nil},
{timeNow, device6, []Message{}, false, nil} , {timeNow, device6, []Message{}, false, nil},
{func()time.Time{return test_time.Add(7*time.Hour+30*time.Minute)}, device6, []Message{ {func() time.Time { return test_time.Add(7*time.Hour + 30*time.Minute) }, device6, []Message{
Message{ {
Payload: []byte("{\"current_heating_setpoint\": 19}"), Payload: []byte("{\"current_heating_setpoint\": 19}"),
Topic: "zigbee2mqtt/TVR/6/set", Topic: "zigbee2mqtt/TVR/6/set",
Retain: false, Retain: false,
}, },
}, true, nil} , }, true, nil},
} }
for _, tt := range tests { for _, tt := range tests {
testname := fmt.Sprintf("%s", tt.device.Name ) testname := fmt.Sprintf("%s", tt.device.Name)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
timeNow = tt.getTime timeNow = tt.getTime
@ -433,7 +429,7 @@ func TestUpdate(t *testing.T) {
result = append(result, msg) result = append(result, msg)
} }
err, ok := <- errchan err, ok := <-errchan
if !ok { if !ok {
t.Fatal(err) t.Fatal(err)
} }
@ -442,7 +438,7 @@ func TestUpdate(t *testing.T) {
t.Errorf("got %s, want %s", err, tt.err) t.Errorf("got %s, want %s", err, tt.err)
} }
change, ok := <- changechan change, ok := <-changechan
if !ok { if !ok {
t.Fatal(err) t.Fatal(err)
} }
@ -451,7 +447,7 @@ func TestUpdate(t *testing.T) {
t.Errorf("got %s, want %s", err, tt.err) t.Errorf("got %s, want %s", err, tt.err)
} }
if !reflect.DeepEqual(result,tt.want) { if !reflect.DeepEqual(result, tt.want) {
t.Errorf("got %v, want %v", result, tt.want) t.Errorf("got %v, want %v", result, tt.want)
} }
}) })

View File

@ -1,10 +1,10 @@
package device package device
import ( import (
"time"
"bytes" "bytes"
"text/template"
"fmt" "fmt"
"text/template"
"time"
) )
type DayOfWeek int type DayOfWeek int
@ -13,22 +13,22 @@ func (d DayOfWeek) Next() DayOfWeek {
if d == Sunday { if d == Sunday {
return Monday return Monday
} }
return d+1 return d + 1
} }
func (d DayOfWeek) Previous() DayOfWeek { func (d DayOfWeek) Previous() DayOfWeek {
if d == Monday { if d == Monday {
return Sunday return Sunday
} }
return d-1 return d - 1
} }
func (d DayOfWeek) DaysBetween(n DayOfWeek) int { func (d DayOfWeek) DaysBetween(n DayOfWeek) int {
var between int var between int
if (n < d) { if n < d {
between = 7 - int(d-n) between = 7 - int(d-n)
} else { } else {
between = int(n-d) between = int(n - d)
} }
return between return between
} }
@ -45,11 +45,11 @@ const (
func WeekDayEnToFr(weekday time.Weekday) DayOfWeek { func WeekDayEnToFr(weekday time.Weekday) DayOfWeek {
// translate weekday to french week, start by Monday // translate weekday to french week, start by Monday
return map[time.Weekday]DayOfWeek { return map[time.Weekday]DayOfWeek{
time.Monday : Monday, time.Monday: Monday,
time.Tuesday : Thuesday, time.Tuesday: Thuesday,
time.Wednesday: Wednesday, time.Wednesday: Wednesday,
time.Thursday : Thursday, time.Thursday: Thursday,
time.Friday: Friday, time.Friday: Friday,
time.Saturday: Saturday, time.Saturday: Saturday,
time.Sunday: Sunday, time.Sunday: Sunday,
@ -77,13 +77,12 @@ type Setpoint struct {
func (s Setpoint) Value(presets []Preset) (int, error) { func (s Setpoint) Value(presets []Preset) (int, error) {
//TODO need test //TODO need test
if len(presets) < s.Preset_id + 1 { if len(presets) < s.Preset_id+1 {
return 0, Error(fmt.Sprintf("preset id %d not found", s.Preset_id)) return 0, Error(fmt.Sprintf("preset id %d not found", s.Preset_id))
} }
return presets[s.Preset_id].Value, nil return presets[s.Preset_id].Value, nil
} }
type WeekProgram map[DayOfWeek][]Setpoint type WeekProgram map[DayOfWeek][]Setpoint
func (p WeekProgram) Current() Setpoint { func (p WeekProgram) Current() Setpoint {
@ -109,7 +108,7 @@ func (p WeekProgram) NextTime(t time.Time) (time.Time, error) {
daytime := daytime(t) daytime := daytime(t)
// Recursive func to find setpoint on weekday // Recursive func to find setpoint on weekday
get := func (weekday DayOfWeek, daytime int) (Setpoint, bool) { get := func(weekday DayOfWeek, daytime int) (Setpoint, bool) {
for _, sp := range p[weekday] { for _, sp := range p[weekday] {
if daytime <= sp.Start { if daytime <= sp.Start {
return sp, true return sp, true
@ -123,19 +122,19 @@ func (p WeekProgram) NextTime(t time.Time) (time.Time, error) {
if setpoint, ok := get(weekday, daytime); ok { if setpoint, ok := get(weekday, daytime); ok {
next := time.Date(t.Year(), next := time.Date(t.Year(),
t.Month(), t.Month(),
t.Day() + startweekday.DaysBetween(weekday), t.Day()+startweekday.DaysBetween(weekday),
0, 0, 0, 0, 0, 0, 0, 0,
time.Local) time.Local)
next = next.Add( next = next.Add(
time.Duration( (setpoint.Start) * int(time.Minute) )) time.Duration((setpoint.Start) * int(time.Minute)))
return next, nil return next, nil
} }
weekday = weekday.Next() weekday = weekday.Next()
daytime = 0 daytime = 0
if weekday == startweekday { if weekday == startweekday {
return time.Time{}, fmt.Errorf("Shouldn't happen no setpoint found over the week") return time.Time{}, Error("shouldn't happen no setpoint found over the week")
} }
} }
@ -222,17 +221,17 @@ var DefaultPresets = []Preset{
} }
var defaultSetpoints = []Setpoint{ var defaultSetpoints = []Setpoint{
{Start: 7*60, Preset_id: 1}, {Start: 7 * 60, Preset_id: 1},
{Start: 8*60, Preset_id: 0}, {Start: 8 * 60, Preset_id: 0},
{Start: 16*60, Preset_id: 1}, {Start: 16 * 60, Preset_id: 1},
{Start: 22*60, Preset_id: 0}, {Start: 22 * 60, Preset_id: 0},
} }
var DefaultWeekProgram = WeekProgram{ var DefaultWeekProgram = WeekProgram{
Monday: defaultSetpoints, Monday: defaultSetpoints,
Thuesday: defaultSetpoints, Thuesday: defaultSetpoints,
Wednesday: defaultSetpoints, Wednesday: defaultSetpoints,
Thursday : defaultSetpoints, Thursday: defaultSetpoints,
Friday: defaultSetpoints, Friday: defaultSetpoints,
Saturday: defaultSetpoints, Saturday: defaultSetpoints,
Sunday: defaultSetpoints, Sunday: defaultSetpoints,
@ -242,11 +241,11 @@ var DefaultPrograms = Programs{
"default": DefaultWeekProgram, "default": DefaultWeekProgram,
} }
var DefaultTVRSettings = TVRSettings { var DefaultTVRSettings = TVRSettings{
Setpoint_topic : "zigbee2mqtt/TVR/{{.Device}}/set", Setpoint_topic: "zigbee2mqtt/TVR/{{.Device}}/set",
Setpoint_payload : "{\"current_heating_setpoint\": {{.Setpoint}}}", Setpoint_payload: "{\"current_heating_setpoint\": {{.Setpoint}}}",
Setpoint_state_topic : "zigbee2mqtt/TVR/{{.Device}}", Setpoint_state_topic: "zigbee2mqtt/TVR/{{.Device}}",
Setpoint_state_jp : "$.current_heating_setpoint", Setpoint_state_jp: "$.current_heating_setpoint",
} }
var DefaultDeviceSettings = DeviceSettings{ var DefaultDeviceSettings = DeviceSettings{

View File

@ -1,25 +1,25 @@
package device package device
import ( import (
"fmt"
"testing" "testing"
"time" "time"
"fmt"
) )
func TestDaysBetween(t *testing.T) { func TestDaysBetween(t *testing.T) {
var tests = []struct{ var tests = []struct {
day1 DayOfWeek day1 DayOfWeek
day2 DayOfWeek day2 DayOfWeek
want int want int
}{ }{
{ Monday, Thuesday, 1 }, {Monday, Thuesday, 1},
{ Thuesday, Monday, 6 }, {Thuesday, Monday, 6},
{ Sunday, Monday, 1 }, {Sunday, Monday, 1},
{ Sunday, Thuesday, 2 }, {Sunday, Thuesday, 2},
{ Friday, Friday, 0 }, {Friday, Friday, 0},
} }
for _, tt := range tests { for _, tt := range tests {
testname := fmt.Sprintf("Days between %d and %d", tt.day1, tt.day2 ) testname := fmt.Sprintf("Days between %d and %d", tt.day1, tt.day2)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
between := tt.day1.DaysBetween(tt.day2) between := tt.day1.DaysBetween(tt.day2)
if between != tt.want { if between != tt.want {
@ -29,26 +29,24 @@ func TestDaysBetween(t *testing.T) {
} }
} }
func TestNextTime(t *testing.T) { func TestNextTime(t *testing.T) {
defaultSetpoints := []Setpoint{ defaultSetpoints := []Setpoint{
{Start: 7*60, Preset_id: 1}, {Start: 7 * 60, Preset_id: 1},
{Start: 8*60, Preset_id: 0}, {Start: 8 * 60, Preset_id: 0},
{Start: 16*60, Preset_id: 1}, {Start: 16 * 60, Preset_id: 1},
{Start: 22*60, Preset_id: 0}, {Start: 22 * 60, Preset_id: 0},
} }
zeroSetpoints := []Setpoint{ zeroSetpoints := []Setpoint{
{Start: 0, Preset_id: 0}, {Start: 0, Preset_id: 0},
} }
default_program := WeekProgram{ default_program := WeekProgram{
Monday: defaultSetpoints, Monday: defaultSetpoints,
Thuesday: defaultSetpoints, Thuesday: defaultSetpoints,
Wednesday: defaultSetpoints, Wednesday: defaultSetpoints,
Thursday : defaultSetpoints, Thursday: defaultSetpoints,
Friday: defaultSetpoints, Friday: defaultSetpoints,
Saturday: defaultSetpoints, Saturday: defaultSetpoints,
Sunday: defaultSetpoints, Sunday: defaultSetpoints,
@ -58,14 +56,13 @@ func TestNextTime(t *testing.T) {
Monday: zeroSetpoints, Monday: zeroSetpoints,
Thuesday: zeroSetpoints, Thuesday: zeroSetpoints,
Wednesday: zeroSetpoints, Wednesday: zeroSetpoints,
Thursday : zeroSetpoints, Thursday: zeroSetpoints,
Friday: zeroSetpoints, Friday: zeroSetpoints,
Saturday: zeroSetpoints, Saturday: zeroSetpoints,
Sunday: zeroSetpoints, Sunday: zeroSetpoints,
} }
var tests = []struct {
var tests = []struct{
prog WeekProgram prog WeekProgram
time time.Time time time.Time
want time.Time want time.Time
@ -92,7 +89,7 @@ func TestNextTime(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
testname := fmt.Sprintf("%s", tt.time.String() ) testname := fmt.Sprintf("%s", tt.time.String())
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
next, err := tt.prog.NextTime(tt.time) next, err := tt.prog.NextTime(tt.time)
if err != nil { if err != nil {