gofmt
This commit is contained in:
parent
11b3622abe
commit
6837a07beb
408
main.go
408
main.go
@ -1,274 +1,270 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"citadel/heater/pkg/device"
|
"citadel/heater/mqtt"
|
||||||
"citadel/heater/mqtt"
|
"citadel/heater/pkg/device"
|
||||||
mqttpaho "github.com/eclipse/paho.mqtt.golang"
|
"context"
|
||||||
"context"
|
"encoding/json"
|
||||||
"encoding/json"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"strings"
|
||||||
"time"
|
"syscall"
|
||||||
"strings"
|
"time"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"flag"
|
mqttpaho "github.com/eclipse/paho.mqtt.golang"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
Clientid string
|
Clientid string
|
||||||
}
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
mqtt_hub *mqtt.Hub
|
mqtt_hub *mqtt.Hub
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
DeviceManager *device.DeviceManager
|
DeviceManager *device.DeviceManager
|
||||||
PubChan chan device.Message
|
PubChan chan device.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(conf Config) *App {
|
func NewApp(conf Config) *App {
|
||||||
hub := mqtt.NewHub(&mqtt.Config{
|
hub := mqtt.NewHub(&mqtt.Config{
|
||||||
Host: conf.Host,
|
Host: conf.Host,
|
||||||
Port: conf.Port,
|
Port: conf.Port,
|
||||||
Username: conf.Username,
|
Username: conf.Username,
|
||||||
Password: conf.Password,
|
Password: conf.Password,
|
||||||
ClientID: conf.Clientid,
|
ClientID: conf.Clientid,
|
||||||
CleanSession: true,
|
CleanSession: true,
|
||||||
AutoReconnect: true,
|
AutoReconnect: true,
|
||||||
Retained: false,
|
Retained: false,
|
||||||
KeepAlive: 15 * time.Second,
|
KeepAlive: 15 * time.Second,
|
||||||
MsgChanDept: 100,
|
MsgChanDept: 100,
|
||||||
})
|
})
|
||||||
pubchan := make(chan device.Message, 10)
|
pubchan := make(chan device.Message, 10)
|
||||||
|
|
||||||
app := &App{
|
app := &App{
|
||||||
mqtt_hub: hub,
|
mqtt_hub: hub,
|
||||||
PubChan: pubchan,
|
PubChan: pubchan,
|
||||||
}
|
}
|
||||||
app.DeviceManager = device.NewDeviceManager(pubchan, app.subscribe)
|
app.DeviceManager = device.NewDeviceManager(pubchan, app.subscribe)
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) subscribe(
|
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)
|
||||||
|
|
||||||
log := zerolog.Ctx(ctx)
|
log := zerolog.Ctx(ctx)
|
||||||
|
|
||||||
go func(ctx context.Context, sub *mqtt.Subscriber) {
|
go func(ctx context.Context, sub *mqtt.Subscriber) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg, ok := <-sub.OnMessage:
|
case msg, ok := <-sub.OnMessage:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
callback(ctx, msg)
|
callback(ctx, msg)
|
||||||
case err, ok := <-sub.OnError:
|
case err, ok := <-sub.OnError:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error().Err(err)
|
log.Error().Err(err)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(ctx, app.mqtt_hub.Subscribe(ctx, topic, qos))
|
}(ctx, app.mqtt_hub.Subscribe(ctx, topic, qos))
|
||||||
log.Info().Str("topic", topic).Msg("subscribe")
|
log.Info().Str("topic", topic).Msg("subscribe")
|
||||||
return cancelFunc
|
return cancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) runTicker() {
|
func (app *App) runTicker() {
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
logger := zerolog.Ctx(app.ctx).With().Str("action", "ticker").Logger()
|
logger := zerolog.Ctx(app.ctx).With().Str("action", "ticker").Logger()
|
||||||
logger.Info().Msg("Start ticker")
|
logger.Info().Msg("Start ticker")
|
||||||
|
|
||||||
ctx := logger.WithContext(app.ctx)
|
ctx := logger.WithContext(app.ctx)
|
||||||
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")
|
||||||
app.DeviceManager.CheckAll(ctx)
|
app.DeviceManager.CheckAll(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) onSettingsMessage(ctx context.Context, msg mqttpaho.Message) {
|
func (app *App) onSettingsMessage(ctx context.Context, msg mqttpaho.Message) {
|
||||||
// callback for topic /{prefix}/{tvr_id}/settings change's
|
// callback for topic /{prefix}/{tvr_id}/settings change's
|
||||||
device_name := strings.Split(msg.Topic(), "/")[1]
|
device_name := strings.Split(msg.Topic(), "/")[1]
|
||||||
|
|
||||||
logger := zerolog.Ctx(ctx).With().
|
logger := zerolog.Ctx(ctx).With().
|
||||||
Str("action", "onSettingsMessage").
|
Str("action", "onSettingsMessage").
|
||||||
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)
|
|
||||||
|
|
||||||
var device_settings device.DeviceSettings
|
logger.Debug().
|
||||||
if err := json.Unmarshal(msg.Payload(), &device_settings); err != nil {
|
Str("topic", msg.Topic()).
|
||||||
logger.Error().Err(err).Msg("Parsing payload")
|
Str("payload", string(msg.Payload())).
|
||||||
}
|
Msg("")
|
||||||
|
|
||||||
tvr := device.Device{
|
var device_settings device.DeviceSettings
|
||||||
Name: device_name,
|
if err := json.Unmarshal(msg.Payload(), &device_settings); err != nil {
|
||||||
Settings: device_settings,
|
logger.Error().Err(err).Msg("Parsing payload")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := app.DeviceManager.Get(device_name); !ok {
|
tvr := device.Device{
|
||||||
err := app.DeviceManager.Add(tvr)
|
Name: device_name,
|
||||||
if err != nil {
|
Settings: device_settings,
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := app.DeviceManager.Check(ctx, device_name)
|
if _, ok := app.DeviceManager.Get(device_name); !ok {
|
||||||
if err != nil {
|
if err := app.DeviceManager.Add(tvr); err != nil {
|
||||||
logger.Error().Err(err).Msg("During device `Check`")
|
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) {
|
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().
|
logger := zerolog.Ctx(ctx).With().
|
||||||
Str("action", "onSetStateMessage").
|
Str("action", "onSetStateMessage").
|
||||||
Str("device", device_name).
|
Str("device", device_name).
|
||||||
Logger()
|
Logger()
|
||||||
ctx = logger.WithContext(ctx)
|
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
|
logger.Debug().Interface("state", state).Msg("new state")
|
||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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() {
|
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}
|
||||||
log := zerolog.New(output).
|
log := zerolog.New(output).
|
||||||
With().
|
With().
|
||||||
Logger()
|
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 {
|
app.ctx = ctx
|
||||||
log.Fatal().Err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
go func(ctx context.Context, pub *mqtt.Publisher) {
|
||||||
app.subscribe("heater/+/state/set", 2, app.onSetStateMessage)
|
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) {
|
case msg, ok := <-app.PubChan:
|
||||||
log := zerolog.Ctx(ctx).With().
|
if !ok {
|
||||||
Str("action", "publisher").
|
log.Error().Msg("publish PubChan Not OK")
|
||||||
Logger()
|
return
|
||||||
defer log.Error().Msg("publisher stoped")
|
}
|
||||||
for {
|
log.Debug().
|
||||||
select {
|
Str("topic", msg.Topic).
|
||||||
|
Str("payload", string(msg.Payload)).
|
||||||
|
Msg("publish")
|
||||||
|
|
||||||
case msg, ok := <-app.PubChan:
|
pub.Publish(msg.Topic, 1, msg.Payload, msg.Retain)
|
||||||
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)
|
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:
|
case <-ctx.Done():
|
||||||
if !ok {
|
return
|
||||||
log.Error().Msg("publish OnError Not OK")
|
}
|
||||||
return
|
}
|
||||||
}
|
}(ctx, app.mqtt_hub.Publisher(ctx))
|
||||||
log.Error().Err(err).Msg("publish OnError")
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
app.runTicker()
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(ctx, app.mqtt_hub.Publisher(ctx))
|
|
||||||
|
|
||||||
app.runTicker()
|
for {
|
||||||
|
select {
|
||||||
for {
|
case <-ctx.Done():
|
||||||
select {
|
return
|
||||||
case <-ctx.Done():
|
}
|
||||||
return
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
host := flag.String("host", "localhost", "mqtt host")
|
host := flag.String("host", "localhost", "mqtt host")
|
||||||
port := flag.String("port", "1883", "mqtt port")
|
port := flag.String("port", "1883", "mqtt port")
|
||||||
username := flag.String("username", "", "mqtt username")
|
username := flag.String("username", "", "mqtt username")
|
||||||
password := flag.String("password", "", "mqtt password")
|
password := flag.String("password", "", "mqtt password")
|
||||||
clientid := flag.String("clientid", "goheater", "mqtt client id")
|
clientid := flag.String("clientid", "goheater", "mqtt client id")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
config := Config{
|
config := Config{
|
||||||
Host: *host,
|
Host: *host,
|
||||||
Port: *port,
|
Port: *port,
|
||||||
Username: *username,
|
Username: *username,
|
||||||
Password: *password,
|
Password: *password,
|
||||||
Clientid: *clientid,
|
Clientid: *clientid,
|
||||||
}
|
}
|
||||||
|
|
||||||
app := NewApp(config)
|
app := NewApp(config)
|
||||||
go app.Run()
|
go app.Run()
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,376 +1,364 @@
|
|||||||
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"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
"context"
|
"github.com/ohler55/ojg/jp"
|
||||||
"github.com/rs/zerolog"
|
"github.com/ohler55/ojg/oj"
|
||||||
// "reflect"
|
"github.com/rs/zerolog"
|
||||||
|
// "reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
var timeNow = func() time.Time {
|
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"`
|
||||||
Setpoint int `json:"setpoint"`
|
Setpoint int `json:"setpoint"`
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
Program_name string `json:"program_name"`
|
Program_name string `json:"program_name"`
|
||||||
Until_time time.Time `json:"until_time"`
|
Until_time time.Time `json:"until_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DeviceState) Equivalent(state DeviceState) bool {
|
func (s *DeviceState) Equivalent(state DeviceState) bool {
|
||||||
if state.Mode != s.Mode {
|
if state.Mode != s.Mode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !state.Time.Equal(s.Time) {
|
if !state.Time.Equal(s.Time) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch state.Mode {
|
switch state.Mode {
|
||||||
case "always":
|
case "always":
|
||||||
if state.Setpoint != s.Setpoint {
|
if state.Setpoint != s.Setpoint {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case "until_next":
|
case "until_next":
|
||||||
if state.Setpoint != s.Setpoint {
|
if state.Setpoint != s.Setpoint {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case "until_time":
|
case "until_time":
|
||||||
if state.Setpoint != s.Setpoint {
|
if state.Setpoint != s.Setpoint {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !state.Until_time.Equal(s.Until_time) {
|
if !state.Until_time.Equal(s.Until_time) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case "program":
|
case "program":
|
||||||
if state.Program_name != s.Program_name {
|
if state.Program_name != s.Program_name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Internal state
|
// Internal state
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Name string
|
Name string
|
||||||
Settings DeviceSettings
|
Settings DeviceSettings
|
||||||
CurrentSetpoint int
|
CurrentSetpoint int
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Device) ListenTopic() (string, error) {
|
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"
|
||||||
if d.State.Program_name != "" {
|
if d.State.Program_name != "" {
|
||||||
prog_name = d.State.Program_name
|
prog_name = d.State.Program_name
|
||||||
}
|
}
|
||||||
|
|
||||||
program, ok := d.Settings.Programs[prog_name]
|
program, ok := d.Settings.Programs[prog_name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return WeekProgram{}, Error(fmt.Sprintf("device %s don't have %s program", d.Name, prog_name))
|
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 {
|
||||||
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
|
}
|
||||||
}
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pubchan <- Message {
|
pubchan <- Message{
|
||||||
Topic: d.StateTopic(),
|
Topic: d.StateTopic(),
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
Retain: true,
|
Retain: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := d.Settings.TVR.FormatPayload(value)
|
payload, err := d.Settings.TVR.FormatPayload(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pubchan <- Message{
|
pubchan <- Message{
|
||||||
Topic: topic,
|
Topic: topic,
|
||||||
Payload: []byte(payload),
|
Payload: []byte(payload),
|
||||||
Retain: false,
|
Retain: false,
|
||||||
}
|
}
|
||||||
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
|
||||||
if d.State.Equivalent(state) {
|
if d.State.Equivalent(state) {
|
||||||
log.Debug().Msg("same state no change")
|
log.Debug().Msg("same state no change")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
d.State = state
|
d.State = state
|
||||||
|
|
||||||
// ignore change info, we already known
|
// ignore change info, we already known
|
||||||
_, err := d.update(log, pubchan)
|
_, err := d.update(log, pubchan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.publishState(pubchan); err != nil {
|
if err := d.publishState(pubchan); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if change {
|
if change {
|
||||||
if err := d.publishState(pubchan); err != nil {
|
if err := d.publishState(pubchan); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
*log = log.With().
|
*log = log.With().
|
||||||
Str("device", d.Name).
|
Str("device", d.Name).
|
||||||
Int("current_setpoint", d.CurrentSetpoint).
|
Int("current_setpoint", d.CurrentSetpoint).
|
||||||
Str("State.Mode", d.State.Mode).
|
Str("State.Mode", d.State.Mode).
|
||||||
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":
|
||||||
return d.handle_always(log, pubchan)
|
return d.handle_always(log, pubchan)
|
||||||
|
|
||||||
case "until_time":
|
case "until_time":
|
||||||
return d.handle_until_time(log, pubchan)
|
return d.handle_until_time(log, pubchan)
|
||||||
|
|
||||||
case "until_next":
|
case "until_next":
|
||||||
return d.handle_until_next(log, pubchan)
|
return d.handle_until_next(log, pubchan)
|
||||||
case "program":
|
case "program":
|
||||||
return d.handle_program(log, pubchan)
|
return d.handle_program(log, pubchan)
|
||||||
default:
|
default:
|
||||||
log.Info().Msg("Use default mode")
|
log.Info().Msg("Use default mode")
|
||||||
return d.handle_reset_state(log, pubchan)
|
return d.handle_reset_state(log, pubchan)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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").
|
||||||
Str("device", d.Name).
|
Str("device", d.Name).
|
||||||
Logger()
|
Logger()
|
||||||
|
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("topic", msg.Topic()).
|
Str("topic", msg.Topic()).
|
||||||
Str("payload", string(msg.Payload())).
|
Str("payload", string(msg.Payload())).
|
||||||
Msg("Message get")
|
Msg("Message get")
|
||||||
|
|
||||||
obj, err := oj.ParseString(string(msg.Payload()))
|
obj, err := oj.ParseString(string(msg.Payload()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("during payload parse")
|
log.Error().Err(err).Msg("during payload parse")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err := jp.ParseString(d.Settings.TVR.Setpoint_state_jp)
|
x, err := jp.ParseString(d.Settings.TVR.Setpoint_state_jp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("while parsing payload")
|
log.Error().Err(err).Msg("while parsing payload")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
current_setpoint := program.Current()
|
current_setpoint := program.Current()
|
||||||
|
|
||||||
value, err := current_setpoint.Value(d.Settings.Presets)
|
value, err := current_setpoint.Value(d.Settings.Presets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.State = DeviceState{
|
d.State = DeviceState{
|
||||||
Setpoint: value,
|
Setpoint: value,
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Program_name: d.ProgramName(),
|
Program_name: d.ProgramName(),
|
||||||
Time: timeNow(),
|
Time: timeNow(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.CurrentSetpoint != value {
|
if d.CurrentSetpoint != value {
|
||||||
log.Info().Msg("publish setpoint update")
|
log.Info().Msg("publish setpoint update")
|
||||||
err = d.SetSetpoint(value, pubchan)
|
err = d.SetSetpoint(value, pubchan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Msg("no setpoint update")
|
log.Info().Msg("no setpoint update")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) handle_until_time(log *zerolog.Logger, pubchan chan Message) (bool, error) {
|
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()) {
|
if d.State.Until_time.Before(timeNow()) {
|
||||||
log.Info().Msg("until_time passed, reset")
|
log.Info().Msg("until_time passed, reset")
|
||||||
return d.handle_reset_state(log, pubchan)
|
return d.handle_reset_state(log, pubchan)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.State.Setpoint != d.CurrentSetpoint {
|
if d.State.Setpoint != d.CurrentSetpoint {
|
||||||
log.Info().Msg("need setpoint update")
|
log.Info().Msg("need setpoint update")
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Msg("no setpoint update")
|
log.Info().Msg("no setpoint update")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) handle_until_next(log *zerolog.Logger, pubchan chan Message) (bool, error) {
|
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()
|
program, err := d.Program()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if timeNow().After(next) {
|
if timeNow().After(next) {
|
||||||
// force current program
|
// force current program
|
||||||
// reset state
|
// reset state
|
||||||
log.Info().Time("now", timeNow()).Time("next", next).Msg("until_next expired")
|
log.Info().Time("now", timeNow()).Time("next", next).Msg("until_next expired")
|
||||||
return d.handle_reset_state(log, pubchan)
|
return d.handle_reset_state(log, pubchan)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.State.Setpoint != d.CurrentSetpoint {
|
if d.State.Setpoint != d.CurrentSetpoint {
|
||||||
log.Info().Msg("need setpoint update")
|
log.Info().Msg("need setpoint update")
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Msg("no setpoint update")
|
log.Info().Msg("no setpoint update")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) handle_program(log *zerolog.Logger, pubchan chan Message) (bool, error) {
|
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()
|
program, err := d.Program()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
current_setpoint := program.Current()
|
current_setpoint := program.Current()
|
||||||
|
|
||||||
value, err := current_setpoint.Value(d.Settings.Presets)
|
value, err := current_setpoint.Value(d.Settings.Presets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.CurrentSetpoint != value {
|
if d.CurrentSetpoint != value {
|
||||||
log.Info().Msg("publish setpoint update")
|
log.Info().Msg("publish setpoint update")
|
||||||
if err := d.SetSetpoint(value, pubchan); err != nil {
|
if err := d.SetSetpoint(value, pubchan); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
d.State.Setpoint = value
|
d.State.Setpoint = value
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Msg("no setpoint update")
|
log.Info().Msg("no setpoint update")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -1,99 +1,100 @@
|
|||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"context"
|
"fmt"
|
||||||
"github.com/eclipse/paho.mqtt.golang"
|
|
||||||
"github.com/rs/zerolog"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Subscriber func(string, int, func(context.Context, mqtt.Message)) context.CancelFunc
|
type Subscriber func(string, int, func(context.Context, mqtt.Message)) context.CancelFunc
|
||||||
|
|
||||||
type DeviceManager struct {
|
type DeviceManager struct {
|
||||||
devices map[string]*Device
|
devices map[string]*Device
|
||||||
subscriber Subscriber
|
subscriber Subscriber
|
||||||
sub_cancel map[string]context.CancelFunc
|
sub_cancel map[string]context.CancelFunc
|
||||||
PubChan chan Message
|
PubChan chan Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeviceManager(pubchan chan Message, subscriber Subscriber) *DeviceManager {
|
func NewDeviceManager(pubchan chan Message, subscriber Subscriber) *DeviceManager {
|
||||||
return &DeviceManager{
|
return &DeviceManager{
|
||||||
devices: make(map[string]*Device),
|
devices: make(map[string]*Device),
|
||||||
sub_cancel: make(map[string]context.CancelFunc),
|
sub_cancel: make(map[string]context.CancelFunc),
|
||||||
subscriber: subscriber,
|
subscriber: subscriber,
|
||||||
PubChan: pubchan,
|
PubChan: pubchan,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeviceManager) Get(name string) (*Device, bool) {
|
func (m *DeviceManager) Get(name string) (*Device, bool) {
|
||||||
device, ok := m.devices[name]
|
device, ok := m.devices[name]
|
||||||
return device, ok
|
return device, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeviceManager) Add(device Device) error {
|
func (m *DeviceManager) Add(device Device) error {
|
||||||
if _, prs := m.devices[device.Name]; prs {
|
if _, prs := m.devices[device.Name]; prs {
|
||||||
return fmt.Errorf("device %s already exist", device.Name)
|
return fmt.Errorf("device %s already exist", device.Name)
|
||||||
}
|
}
|
||||||
m.devices[device.Name] = &device
|
m.devices[device.Name] = &device
|
||||||
|
|
||||||
// subscribe to state topic
|
// subscribe to state topic
|
||||||
topic, err := device.ListenTopic()
|
topic, err := device.ListenTopic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if topic != "" {
|
if topic != "" {
|
||||||
cancel := m.subscriber(topic, 2, device.onMessage)
|
cancel := m.subscriber(topic, 2, device.onMessage)
|
||||||
m.sub_cancel[device.Name] = cancel
|
m.sub_cancel[device.Name] = cancel
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeviceManager) Delete(name string) error {
|
func (m *DeviceManager) Delete(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeviceManager) SetSettings(name string, settings DeviceSettings) error {
|
func (m *DeviceManager) SetSettings(name string, settings DeviceSettings) error {
|
||||||
device, ok := m.devices[name]
|
device, ok := m.devices[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Not existings device %s", name)
|
return fmt.Errorf("Not existings device %s", name)
|
||||||
}
|
}
|
||||||
device.Settings = settings
|
device.Settings = settings
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeviceManager) SetState(name string, state DeviceState) error {
|
func (m *DeviceManager) SetState(name string, state DeviceState) error {
|
||||||
device, ok := m.devices[name]
|
device, ok := m.devices[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Not existings device %s", name)
|
return fmt.Errorf("Not existings device %s", name)
|
||||||
}
|
}
|
||||||
device.State = state
|
device.State = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeviceManager) Check(ctx context.Context, name string) error {
|
func (m *DeviceManager) Check(ctx context.Context, name string) error {
|
||||||
logger := zerolog.Ctx(ctx)
|
logger := zerolog.Ctx(ctx)
|
||||||
|
|
||||||
device, ok := m.devices[name]
|
device, ok := m.devices[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Device %s don't exist", name)
|
return fmt.Errorf("Device %s don't exist", name)
|
||||||
}
|
}
|
||||||
err := device.CheckSetpoint(logger, m.PubChan)
|
err := device.CheckSetpoint(logger, m.PubChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("")
|
logger.Error().Err(err).Msg("")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeviceManager) CheckAll(ctx context.Context) error {
|
func (m *DeviceManager) CheckAll(ctx context.Context) error {
|
||||||
logger := zerolog.Ctx(ctx)
|
logger := zerolog.Ctx(ctx)
|
||||||
|
|
||||||
for _, device := range m.devices {
|
for _, device := range m.devices {
|
||||||
err := device.CheckSetpoint(logger, m.PubChan)
|
err := device.CheckSetpoint(logger, m.PubChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("")
|
logger.Error().Err(err).Msg("")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,459 +1,455 @@
|
|||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"fmt"
|
||||||
"time"
|
"io/ioutil"
|
||||||
"fmt"
|
"reflect"
|
||||||
"reflect"
|
"testing"
|
||||||
"github.com/rs/zerolog"
|
"time"
|
||||||
"io/ioutil"
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
var test_programs = Programs{
|
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,
|
||||||
Presets: test_presets,
|
Presets: test_presets,
|
||||||
TVR: DefaultTVRSettings,
|
TVR: DefaultTVRSettings,
|
||||||
},
|
},
|
||||||
CurrentSetpoint: 0,
|
CurrentSetpoint: 0,
|
||||||
State: DeviceState{
|
State: DeviceState{
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "default",
|
Program_name: "default",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "default",
|
Program_name: "default",
|
||||||
},
|
},
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "always",
|
Mode: "always",
|
||||||
Setpoint: 13,
|
Setpoint: 13,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "default",
|
Program_name: "default",
|
||||||
},
|
},
|
||||||
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "default",
|
Program_name: "default",
|
||||||
},
|
},
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Setpoint: 13,
|
Setpoint: 13,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "default",
|
Program_name: "default",
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "default",
|
Program_name: "default",
|
||||||
},
|
},
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "program",
|
Mode: "program",
|
||||||
Setpoint: 13,
|
Setpoint: 13,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "other",
|
Program_name: "other",
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "always",
|
Mode: "always",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
},
|
},
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "always",
|
Mode: "always",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "other",
|
Program_name: "other",
|
||||||
Until_time: test_time,
|
Until_time: test_time,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "always",
|
Mode: "always",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
},
|
},
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "always",
|
Mode: "always",
|
||||||
Setpoint: 15,
|
Setpoint: 15,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "other",
|
Program_name: "other",
|
||||||
Until_time: test_time,
|
Until_time: test_time,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "until_next",
|
Mode: "until_next",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
},
|
},
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "until_next",
|
Mode: "until_next",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "other",
|
Program_name: "other",
|
||||||
Until_time: test_time,
|
Until_time: test_time,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "until_next",
|
Mode: "until_next",
|
||||||
Setpoint: 14,
|
Setpoint: 14,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
},
|
},
|
||||||
DeviceState{
|
DeviceState{
|
||||||
Mode: "until_next",
|
Mode: "until_next",
|
||||||
Setpoint: 13,
|
Setpoint: 13,
|
||||||
Time: test_time,
|
Time: test_time,
|
||||||
Program_name: "other",
|
Program_name: "other",
|
||||||
Until_time: test_time,
|
Until_time: test_time,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DeviceState{
|
DeviceState{
|
||||||
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 {
|
||||||
t.Errorf("got %t, want %t", r, tt.want)
|
t.Errorf("got %t, want %t", r, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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" {
|
||||||
t.Errorf("Got %s; want heater/valid/state", topic)
|
t.Errorf("Got %s; want heater/valid/state", topic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListenTopic(t *testing.T) {
|
func TestListenTopic(t *testing.T) {
|
||||||
topic, err := test_device.ListenTopic()
|
topic, err := test_device.ListenTopic()
|
||||||
want := "zigbee2mqtt/TVR/valid"
|
want := "zigbee2mqtt/TVR/valid"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Got %s", err.Error())
|
t.Errorf("Got %s", err.Error())
|
||||||
}
|
}
|
||||||
if topic != want {
|
if topic != want {
|
||||||
t.Errorf("Got %s; want %s", topic, want)
|
t.Errorf("Got %s; want %s", topic, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProgram(t *testing.T) {
|
func TestProgram(t *testing.T) {
|
||||||
//case 1: no program set in state return default
|
//case 1: no program set in state return default
|
||||||
case1_device := test_device
|
case1_device := test_device
|
||||||
case1_device.State.Program_name = ""
|
case1_device.State.Program_name = ""
|
||||||
|
|
||||||
//case 2: program set "confort" must return it
|
//case 2: program set "confort" must return it
|
||||||
var test_confort_weekprogram = WeekProgram{
|
var test_confort_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,
|
||||||
}
|
}
|
||||||
case2_device := test_device
|
case2_device := test_device
|
||||||
case2_device.Settings.Programs = Programs{
|
case2_device.Settings.Programs = Programs{
|
||||||
"default": test_weekprogram,
|
"default": test_weekprogram,
|
||||||
"confort": test_confort_weekprogram,
|
"confort": test_confort_weekprogram,
|
||||||
}
|
}
|
||||||
case2_device.State.Program_name = "confort"
|
case2_device.State.Program_name = "confort"
|
||||||
|
|
||||||
//case 3: program set "confort" but not exist
|
//case 3: program set "confort" but not exist
|
||||||
case3_device := test_device
|
case3_device := test_device
|
||||||
case3_device.State.Program_name = "confort"
|
case3_device.State.Program_name = "confort"
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
name string
|
name string
|
||||||
device Device
|
device Device
|
||||||
result WeekProgram
|
result WeekProgram
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{"case 1 no program set use default", case1_device, DefaultWeekProgram, nil},
|
{"case 1 no program set use default", case1_device, DefaultWeekProgram, nil},
|
||||||
{"case 2 program confort", case2_device, test_confort_weekprogram, 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")},
|
{"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
|
||||||
// device 3: currentSetpoint 0 always, 22
|
// device 3: currentSetpoint 0 always, 22
|
||||||
// device 4: currentSetpoint 22 until_time, fixed at timeNow, for 2h 22
|
// device 4: currentSetpoint 22 until_time, fixed at timeNow, for 2h 22
|
||||||
// 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 {
|
||||||
|
return test_time
|
||||||
|
}
|
||||||
|
|
||||||
timeNow = func() time.Time {
|
device1 := test_device
|
||||||
return test_time
|
device1.Name = "1"
|
||||||
}
|
device1.State = DeviceState{
|
||||||
|
Mode: "program",
|
||||||
|
Setpoint: 0,
|
||||||
|
Time: test_time,
|
||||||
|
Program_name: "",
|
||||||
|
}
|
||||||
|
|
||||||
device1 := test_device
|
device2 := test_device
|
||||||
device1.Name = "1"
|
device2.Name = "2"
|
||||||
device1.State = DeviceState{
|
device2.State.Program_name = "unknown"
|
||||||
Mode: "program",
|
|
||||||
Setpoint: 0,
|
|
||||||
Time: test_time,
|
|
||||||
Program_name: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
device2 := test_device
|
device3 := test_device
|
||||||
device2.Name = "2"
|
device3.Name = "3"
|
||||||
device2.State.Program_name = "unknown"
|
device3.State = DeviceState{
|
||||||
|
Mode: "always",
|
||||||
|
Setpoint: 22,
|
||||||
|
}
|
||||||
|
|
||||||
device3 := test_device
|
device4 := test_device
|
||||||
device3.Name = "3"
|
device4.Name = "4"
|
||||||
device3.State = DeviceState{
|
device4.CurrentSetpoint = 22
|
||||||
Mode: "always",
|
device4.State = DeviceState{
|
||||||
Setpoint: 22,
|
Mode: "until_time",
|
||||||
}
|
Setpoint: 22,
|
||||||
|
Time: timeNow(),
|
||||||
|
Until_time: timeNow().Add(2 * time.Hour),
|
||||||
|
}
|
||||||
|
|
||||||
device4 := test_device
|
device5 := test_device
|
||||||
device4.Name = "4"
|
device5.Name = "5"
|
||||||
device4.CurrentSetpoint = 22
|
device5.CurrentSetpoint = 17
|
||||||
device4.State = DeviceState{
|
device5.State = DeviceState{
|
||||||
Mode: "until_time",
|
Mode: "until_time",
|
||||||
Setpoint: 22,
|
Setpoint: 22,
|
||||||
Time: timeNow(),
|
Time: timeNow().Add(-2 * time.Hour),
|
||||||
Until_time: timeNow().Add(2*time.Hour),
|
Until_time: timeNow().Add(-1 * time.Minute),
|
||||||
}
|
}
|
||||||
|
|
||||||
device5 := test_device
|
device6 := test_device
|
||||||
device5.Name = "5"
|
device6.Name = "6"
|
||||||
device5.CurrentSetpoint = 17
|
device6.CurrentSetpoint = 22
|
||||||
device5.State = DeviceState{
|
device6.State = DeviceState{
|
||||||
Mode: "until_time",
|
Mode: "until_next",
|
||||||
Setpoint: 22,
|
Setpoint: 22,
|
||||||
Time: timeNow().Add(-2*time.Hour),
|
Time: timeNow(),
|
||||||
Until_time: timeNow().Add(-1*time.Minute),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
device6 := test_device
|
var tests = []struct {
|
||||||
device6.Name = "6"
|
getTime func() time.Time
|
||||||
device6.CurrentSetpoint = 22
|
device Device
|
||||||
device6.State = DeviceState{
|
want []Message
|
||||||
Mode: "until_next",
|
change bool
|
||||||
Setpoint: 22,
|
err error
|
||||||
Time: timeNow(),
|
}{
|
||||||
}
|
{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{
|
timeNow = tt.getTime
|
||||||
getTime func() time.Time
|
result := []Message{}
|
||||||
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} ,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
rchan := make(chan Message, 10)
|
||||||
testname := fmt.Sprintf("%s", tt.device.Name )
|
errchan := make(chan error, 1)
|
||||||
t.Run(testname, func(t *testing.T) {
|
changechan := make(chan bool, 1)
|
||||||
|
|
||||||
timeNow = tt.getTime
|
go func(result chan Message, changechan chan bool, errchan chan error) {
|
||||||
result := []Message{}
|
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)
|
for msg := range rchan {
|
||||||
errchan := make(chan error, 1)
|
result = append(result, msg)
|
||||||
changechan := make(chan bool, 1)
|
}
|
||||||
|
|
||||||
go func(result chan Message, changechan chan bool, errchan chan error) {
|
err, ok := <-errchan
|
||||||
logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger()
|
if !ok {
|
||||||
change, err := tt.device.update(&logger, result)
|
t.Fatal(err)
|
||||||
errchan <- err
|
}
|
||||||
changechan <- change
|
|
||||||
close(rchan)
|
|
||||||
close(changechan)
|
|
||||||
close(errchan)
|
|
||||||
}(rchan, changechan, errchan)
|
|
||||||
|
|
||||||
for msg := range rchan {
|
if err != tt.err {
|
||||||
result = append(result, msg)
|
t.Errorf("got %s, want %s", err, tt.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err, ok := <- errchan
|
change, ok := <-changechan
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != tt.err {
|
if change != tt.change {
|
||||||
t.Errorf("got %s, want %s", err, tt.err)
|
t.Errorf("got %s, want %s", err, tt.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
change, ok := <- changechan
|
if !reflect.DeepEqual(result, tt.want) {
|
||||||
if !ok {
|
t.Errorf("got %v, want %v", result, tt.want)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,256 +1,255 @@
|
|||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"bytes"
|
||||||
"bytes"
|
"fmt"
|
||||||
"text/template"
|
"text/template"
|
||||||
"fmt"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DayOfWeek int
|
type DayOfWeek int
|
||||||
|
|
||||||
func (d DayOfWeek) Next() DayOfWeek {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Monday DayOfWeek = 0
|
Monday DayOfWeek = 0
|
||||||
Thuesday DayOfWeek = 1
|
Thuesday DayOfWeek = 1
|
||||||
Wednesday DayOfWeek = 2
|
Wednesday DayOfWeek = 2
|
||||||
Thursday DayOfWeek = 3
|
Thursday DayOfWeek = 3
|
||||||
Friday DayOfWeek = 4
|
Friday DayOfWeek = 4
|
||||||
Saturday DayOfWeek = 5
|
Saturday DayOfWeek = 5
|
||||||
Sunday DayOfWeek = 6
|
Sunday DayOfWeek = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
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,
|
||||||
}[weekday]
|
}[weekday]
|
||||||
}
|
}
|
||||||
|
|
||||||
func daytime(t time.Time) int {
|
func daytime(t time.Time) int {
|
||||||
return t.Hour()*60 + t.Minute()
|
return t.Hour()*60 + t.Minute()
|
||||||
}
|
}
|
||||||
|
|
||||||
func weekday(t time.Time) DayOfWeek {
|
func weekday(t time.Time) DayOfWeek {
|
||||||
return WeekDayEnToFr(t.Weekday())
|
return WeekDayEnToFr(t.Weekday())
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Payload []byte
|
Payload []byte
|
||||||
Topic string
|
Topic string
|
||||||
Retain bool
|
Retain bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Setpoint struct {
|
type Setpoint struct {
|
||||||
Start int `json:"start"`
|
Start int `json:"start"`
|
||||||
Preset_id int `json:"preset_id"`
|
Preset_id int `json:"preset_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
// TODO: need test
|
// TODO: need test
|
||||||
// return current Setpoint
|
// return current Setpoint
|
||||||
now := timeNow()
|
now := timeNow()
|
||||||
weekday := weekday(now)
|
weekday := weekday(now)
|
||||||
daytime := daytime(now)
|
daytime := daytime(now)
|
||||||
setpoint := Setpoint{}
|
setpoint := Setpoint{}
|
||||||
for _, sp := range p[weekday] {
|
for _, sp := range p[weekday] {
|
||||||
if daytime < sp.Start {
|
if daytime < sp.Start {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
setpoint = sp
|
setpoint = sp
|
||||||
}
|
}
|
||||||
return setpoint
|
return setpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p WeekProgram) NextTime(t time.Time) (time.Time, error) {
|
func (p WeekProgram) NextTime(t time.Time) (time.Time, error) {
|
||||||
// return next program change
|
// return next program change
|
||||||
|
|
||||||
weekday := weekday(t)
|
weekday := weekday(t)
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Setpoint{}, false
|
return Setpoint{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
startweekday := weekday
|
startweekday := weekday
|
||||||
for {
|
for {
|
||||||
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Programs map[string]WeekProgram
|
type Programs map[string]WeekProgram
|
||||||
|
|
||||||
type Preset struct {
|
type Preset struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Value int `json:"value"`
|
Value int `json:"value"`
|
||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TVRSettings struct {
|
type TVRSettings struct {
|
||||||
Setpoint_topic string `json:"setpoint_topic"`
|
Setpoint_topic string `json:"setpoint_topic"`
|
||||||
Setpoint_payload string `json:"setpoint_payload"`
|
Setpoint_payload string `json:"setpoint_payload"`
|
||||||
Setpoint_state_topic string `json:"setpoint_state_topic"`
|
Setpoint_state_topic string `json:"setpoint_state_topic"`
|
||||||
Setpoint_state_jp string `json:"setpoint_state_jp"`
|
Setpoint_state_jp string `json:"setpoint_state_jp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TVRSettings) FormatTopicState(device_name string) (string, error) {
|
func (s TVRSettings) FormatTopicState(device_name string) (string, error) {
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
Device string
|
Device string
|
||||||
}
|
}
|
||||||
variables := Variable{device_name}
|
variables := Variable{device_name}
|
||||||
t, err := template.New("topic").Parse(s.Setpoint_state_topic)
|
t, err := template.New("topic").Parse(s.Setpoint_state_topic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
err = t.Execute(buf, variables)
|
err = t.Execute(buf, variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TVRSettings) FormatTopic(device_name string) (string, error) {
|
func (s TVRSettings) FormatTopic(device_name string) (string, error) {
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
Device string
|
Device string
|
||||||
}
|
}
|
||||||
variables := Variable{device_name}
|
variables := Variable{device_name}
|
||||||
t, err := template.New("topic").Parse(s.Setpoint_topic)
|
t, err := template.New("topic").Parse(s.Setpoint_topic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
err = t.Execute(buf, variables)
|
err = t.Execute(buf, variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TVRSettings) FormatPayload(setpoint int) (string, error) {
|
func (s TVRSettings) FormatPayload(setpoint int) (string, error) {
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
Setpoint int
|
Setpoint int
|
||||||
}
|
}
|
||||||
variables := Variable{setpoint}
|
variables := Variable{setpoint}
|
||||||
t, err := template.New("payload").Parse(s.Setpoint_payload)
|
t, err := template.New("payload").Parse(s.Setpoint_payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
err = t.Execute(buf, variables)
|
err = t.Execute(buf, variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceSettings struct {
|
type DeviceSettings struct {
|
||||||
Programs Programs `json:"programs"`
|
Programs Programs `json:"programs"`
|
||||||
Presets []Preset `json:"presets"`
|
Presets []Preset `json:"presets"`
|
||||||
TVR TVRSettings `json:"TVR"`
|
TVR TVRSettings `json:"TVR"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
// Defaults
|
// Defaults
|
||||||
|
|
||||||
var DefaultPresets = []Preset{
|
var DefaultPresets = []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 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultPrograms = Programs{
|
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{
|
||||||
Programs: DefaultPrograms,
|
Programs: DefaultPrograms,
|
||||||
Presets: DefaultPresets,
|
Presets: DefaultPresets,
|
||||||
TVR: DefaultTVRSettings,
|
TVR: DefaultTVRSettings,
|
||||||
}
|
}
|
||||||
|
@ -1,107 +1,104 @@
|
|||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"fmt"
|
||||||
"time"
|
"testing"
|
||||||
"fmt"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
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 {
|
||||||
t.Errorf("got %d, want %d", between, tt.want)
|
t.Errorf("got %d, want %d", between, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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{
|
||||||
|
Monday: defaultSetpoints,
|
||||||
|
Thuesday: defaultSetpoints,
|
||||||
|
Wednesday: defaultSetpoints,
|
||||||
|
Thursday: defaultSetpoints,
|
||||||
|
Friday: defaultSetpoints,
|
||||||
|
Saturday: defaultSetpoints,
|
||||||
|
Sunday: defaultSetpoints,
|
||||||
|
}
|
||||||
|
|
||||||
default_program := WeekProgram{
|
zero_program := WeekProgram{
|
||||||
Monday: defaultSetpoints,
|
Monday: zeroSetpoints,
|
||||||
Thuesday: defaultSetpoints,
|
Thuesday: zeroSetpoints,
|
||||||
Wednesday: defaultSetpoints,
|
Wednesday: zeroSetpoints,
|
||||||
Thursday : defaultSetpoints,
|
Thursday: zeroSetpoints,
|
||||||
Friday: defaultSetpoints,
|
Friday: zeroSetpoints,
|
||||||
Saturday: defaultSetpoints,
|
Saturday: zeroSetpoints,
|
||||||
Sunday: defaultSetpoints,
|
Sunday: zeroSetpoints,
|
||||||
}
|
}
|
||||||
|
|
||||||
zero_program := WeekProgram{
|
var tests = []struct {
|
||||||
Monday: zeroSetpoints,
|
prog WeekProgram
|
||||||
Thuesday: zeroSetpoints,
|
time time.Time
|
||||||
Wednesday: zeroSetpoints,
|
want time.Time
|
||||||
Thursday : zeroSetpoints,
|
}{
|
||||||
Friday: zeroSetpoints,
|
{
|
||||||
Saturday: zeroSetpoints,
|
default_program,
|
||||||
Sunday: zeroSetpoints,
|
time.Date(2022, time.October, 23, 9, 0, 0, 0, time.Local),
|
||||||
}
|
time.Date(2022, time.October, 23, 16, 0, 0, 0, time.Local),
|
||||||
|
},
|
||||||
|
{
|
||||||
var tests = []struct{
|
default_program,
|
||||||
prog WeekProgram
|
time.Date(2022, time.October, 24, 5, 0, 0, 0, time.Local),
|
||||||
time time.Time
|
time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local),
|
||||||
want time.Time
|
},
|
||||||
}{
|
{
|
||||||
{
|
default_program,
|
||||||
default_program,
|
time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local),
|
||||||
time.Date(2022, time.October, 23, 9, 0, 0, 0, time.Local),
|
time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local),
|
||||||
time.Date(2022, time.October, 23, 16, 0, 0, 0, time.Local),
|
},
|
||||||
},
|
{
|
||||||
{
|
zero_program,
|
||||||
default_program,
|
time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local),
|
||||||
time.Date(2022, time.October, 24, 5, 0, 0, 0, time.Local),
|
time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local),
|
||||||
time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local),
|
},
|
||||||
},
|
}
|
||||||
{
|
for _, tt := range tests {
|
||||||
default_program,
|
testname := fmt.Sprintf("%s", tt.time.String())
|
||||||
time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local),
|
t.Run(testname, func(t *testing.T) {
|
||||||
time.Date(2022, time.October, 24, 7, 0, 0, 0, time.Local),
|
next, err := tt.prog.NextTime(tt.time)
|
||||||
},
|
if err != nil {
|
||||||
{
|
t.Fatalf(err.Error())
|
||||||
zero_program,
|
}
|
||||||
time.Date(2022, time.October, 23, 23, 0, 0, 0, time.Local),
|
if !next.Equal(tt.want) {
|
||||||
time.Date(2022, time.October, 24, 0, 0, 0, 0, time.Local),
|
t.Errorf("got %s, want %s", next.String(), tt.want.String())
|
||||||
},
|
}
|
||||||
}
|
})
|
||||||
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())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user