heater/pkg/device/device.go

271 lines
6.9 KiB
Go

package device
import (
"github.com/eclipse/paho.mqtt.golang"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
"fmt"
"time"
"encoding/json"
"context"
"github.com/rs/zerolog"
// "reflect"
)
type DeviceState struct {
Mode string `json:"mode"`
Setpoint int `json:"setpoint"`
Time time.Time `json:"time"`
Program_name string `json:"program_name"`
Until_time time.Time `json:"until_time"`
}
// Internal state
type Device struct {
Name string
Settings DeviceSettings
CurrentSetpoint int
State DeviceState
}
func (d *Device) SameState(state DeviceState) bool {
if state.Mode != d.State.Mode {
return false
}
if state.Setpoint != d.State.Setpoint {
return false
}
if state.Program_name != d.State.Program_name {
return false
}
if !state.Time.Equal(d.State.Time) {
return false
}
if !state.Until_time.Equal(d.State.Until_time) {
return false
}
return true
}
func (d *Device) StateTopic() string {
return fmt.Sprintf("heater/%s/state", d.Name)
}
func (d *Device) CheckSetpoint(logger *zerolog.Logger, pubchan chan Message) error {
// Handle all the update setpoint logic
// push in pubChan a Message if setpoint need to be update
log := logger.With().
Str("device", d.Name).
Int("current_setpoint", d.CurrentSetpoint).
Str("State.Mode", d.State.Mode).
Int("State.Setpoint", d.State.Setpoint).
Str("State.Program_name", d.State.Program_name).
Logger()
log.Info().Msg("Check if setpoint need an update")
switch d.State.Mode {
case "always":
log.Info().Msg("Use always")
case "until_time":
log.Info().Msg("Use until_time")
if d.State.Until_time.Before(time.Now()) {
log.Info().Time("until_time", d.State.Until_time).
Msg("until_time passed, reset")
return d.setProgAndReset(&log, pubchan)
}
case "until_next":
log.Info().Msg("Use until_next")
var prog string = "default"
if d.State.Program_name != "" {
prog = d.State.Program_name
}
program, ok := d.Settings.Programs[prog]
if !ok {
return fmt.Errorf("program not found")
}
next, err := program.NextTime(d.State.Time)
if err!= nil {
return err
}
if time.Now().After(next) {
// force current program
// reset state
log.Info().Time("now", time.Now()).Time("next", next).Msg("until_next expired")
return d.setProgAndReset(&log, pubchan)
}
case "program":
log.Info().Msg("Use program mode")
if d.State.Setpoint == 0 {
//in case of new program mode
var prog string = "default"
if d.State.Program_name != "" {
prog = d.State.Program_name
}
value, err := d.setpointValueProg(prog)
if err != nil {
return err
}
d.State.Setpoint = value
d.PublishState(d.State, pubchan)
}
default:
log.Info().Msg("Use default mode")
return d.setProgAndReset(&log, pubchan)
}
if d.State.Setpoint != d.CurrentSetpoint {
log.Warn().Msg("Need setpoint update")
return d.publishSetpoint(d.State.Setpoint, pubchan)
} else {
log.Warn().Msg("No setpoint update")
}
return nil
}
func (d *Device) setProgAndReset(logger *zerolog.Logger, pubchan chan Message) error {
prog_name := "default"
if d.State.Program_name != "" {
prog_name = d.State.Program_name
}
log := logger.With().Str("program", prog_name).Logger()
value, err := d.setpointValueProg(prog_name)
if err != nil {
return err
}
log = log.With().Int("futur_setpoint", value).Logger()
if d.CurrentSetpoint != value {
log.Warn().Msg("publish setpoint update")
err = d.publishSetpoint(value, pubchan)
if err != nil {
return err
}
} else {
log.Warn().Msg("no setpoint update")
}
state := DeviceState{
Setpoint: value,
Mode: "program",
Program_name: prog_name,
Time: time.Now(),
}
d.State = state
payload, err := json.Marshal(state)
if err != nil {
return err
}
pubchan <- Message {
Topic: d.StateTopic(),
Payload: payload,
Retain: true,
}
return nil
}
func (d *Device) publishSetpoint(value int, pubchan chan Message) error {
topic, err := d.Settings.TVR.FormatTopic(d.Name)
if err != nil {
return err
}
payload, err := d.Settings.TVR.FormatPayload(value)
if err != nil {
return err
}
pubchan <- Message{
Topic: topic,
Payload: []byte(payload),
Retain: false,
}
return nil
}
func (d *Device) PublishState(state DeviceState, pubchan chan Message) error {
payload, err := json.Marshal(state)
if err != nil {
return err
}
pubchan <- Message{
Topic: d.StateTopic(),
Payload: payload,
Retain: true,
}
return nil
}
func (d Device) ListenTopic() (string, error) {
return d.Settings.TVR.FormatTopicState(d.Name)
}
func (d *Device) onMessage(ctx context.Context, msg mqtt.Message) {
log := zerolog.Ctx(ctx).With().
Str("action", "device state receive").
Str("device", d.Name).
Logger()
log.Debug().
Str("topic", msg.Topic()).
Str("payload", string(msg.Payload())).
Msg("Message get")
obj, err := oj.ParseString(string(msg.Payload()))
if err != nil {
log.Error().Err(err).Msg("during payload parse")
return
}
x, err := jp.ParseString(d.Settings.TVR.Setpoint_state_jp)
if err != nil {
log.Error().Err(err).Msg("while parsing payload")
return
}
r := x.First(obj)
if v, ok := r.(int64); ok{
d.CurrentSetpoint = int(v)
} else {
log.Error().Err(err).Interface("parsing payload", r).Msg("while parsing payload")
}
}
func (d *Device) programSetpoint(prog_name string) (Setpoint, error) {
program, ok := d.Settings.Programs[prog_name]
if !ok {
return Setpoint{}, fmt.Errorf("device %s don't have %s program", d.Name, prog_name)
}
setpoint := program.Current()
return setpoint, nil
}
func (d *Device) setpointValue(setpoint Setpoint) (int, error) {
if len(d.Settings.Presets) < setpoint.Preset_id + 1 {
return 0, fmt.Errorf("Preset id %d didn't found", setpoint.Preset_id)
}
return d.Settings.Presets[setpoint.Preset_id].Value, nil
}
func (d *Device) setpointValueProg(prog_name string) (int, error) {
setpoint, err := d.programSetpoint(prog_name)
if err != nil{
return 0, err
}
val, err := d.setpointValue(setpoint)
if err != nil{
return 0, err
}
return val, nil
}