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 }