heater/pkg/device/settings.go

307 lines
6.3 KiB
Go

package device
import (
"bytes"
"fmt"
"text/template"
"time"
"github.com/ohler55/ojg/jp"
)
type DayOfWeek int
func (d DayOfWeek) Next() DayOfWeek {
if d == Sunday {
return Monday
}
return d + 1
}
func (d DayOfWeek) Previous() DayOfWeek {
if d == Monday {
return Sunday
}
return d - 1
}
func (d DayOfWeek) DaysBetween(n DayOfWeek) int {
var between int
if n < d {
between = 7 - int(d-n)
} else {
between = int(n - d)
}
return between
}
const (
Monday DayOfWeek = iota
Thuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
func WeekDayEnToFr(weekday time.Weekday) DayOfWeek {
// translate weekday to french week, start by Monday
return map[time.Weekday]DayOfWeek{
time.Monday: Monday,
time.Tuesday: Thuesday,
time.Wednesday: Wednesday,
time.Thursday: Thursday,
time.Friday: Friday,
time.Saturday: Saturday,
time.Sunday: Sunday,
}[weekday]
}
func daytime(t time.Time) int {
return t.Hour()*60 + t.Minute()
}
func weekday(t time.Time) DayOfWeek {
return WeekDayEnToFr(t.Weekday())
}
type Message struct {
Payload []byte
Topic string
Retain bool
}
type Setpoint struct {
Start int `json:"start"`
Preset_id int `json:"preset_id"`
}
func (s Setpoint) Value(presets []Preset) (int, error) {
//TODO need test
if len(presets) < s.Preset_id+1 {
return 0, Error(fmt.Sprintf("preset id %d not found", s.Preset_id))
}
return presets[s.Preset_id].Value, nil
}
type WeekProgram map[DayOfWeek][]Setpoint
func (p WeekProgram) Current() Setpoint {
// TODO: need test
// return current Setpoint
now := timeNow()
weekday := weekday(now)
daytime := daytime(now)
setpoint := Setpoint{}
for _, sp := range p[weekday] {
if daytime < sp.Start {
break
}
setpoint = sp
}
return setpoint
}
func (p WeekProgram) NextTime(t time.Time) (time.Time, error) {
// return next program change
weekday := weekday(t)
daytime := daytime(t)
// Recursive func to find setpoint on weekday
get := func(weekday DayOfWeek, daytime int) (Setpoint, bool) {
for _, sp := range p[weekday] {
if daytime <= sp.Start {
return sp, true
}
}
return Setpoint{}, false
}
startweekday := weekday
for {
if setpoint, ok := get(weekday, daytime); ok {
next := time.Date(t.Year(),
t.Month(),
t.Day()+startweekday.DaysBetween(weekday),
0, 0, 0, 0,
time.Local)
next = next.Add(
time.Duration((setpoint.Start) * int(time.Minute)))
return next, nil
}
weekday = weekday.Next()
daytime = 0
if weekday == startweekday {
return time.Time{}, Error("shouldn't happen no setpoint found over the week")
}
}
}
type Programs map[string]WeekProgram
type Preset struct {
Label string `json:"label"`
Value int `json:"value"`
Color string `json:"color"`
}
type TVRSettings struct {
Setpoint_topic string `json:"setpoint_topic"`
Setpoint_payload string `json:"setpoint_payload"`
Setpoint_state_topic string `json:"setpoint_state_topic"`
Setpoint_state_jp string `json:"setpoint_state_jp"`
StateLocalTemperatureJP string `json:"state_local_temperature_jp"`
StateValveJP string `json:"state_valve_jp"`
}
func (s TVRSettings) ParseSetpoint(obj interface{}) (int, error) {
jp, err := jp.ParseString(s.Setpoint_state_jp)
if err != nil {
return 0, err
}
r := jp.First(obj)
if v, ok := r.(int64); ok {
return int(v), nil
} else {
return 0, Error("Unexpected type")
}
}
func (s TVRSettings) ParseLocalTemperature(obj interface{}) (int, error) {
jp, err := jp.ParseString(s.StateLocalTemperatureJP)
if err != nil {
return 0, err
}
r := jp.First(obj)
if v, ok := r.(int64); ok {
return int(v), nil
} else {
return 0, Error("Unexpected type")
}
}
func (s TVRSettings) ParseValve(obj interface{}) (string, error) {
jp, err := jp.ParseString(s.StateValveJP)
if err != nil {
return "", err
}
r := jp.First(obj)
if v, ok := r.(string); ok {
return string(v), nil
} else {
return "", Error("Unexpected type")
}
}
func (s TVRSettings) FormatTopicState(device_name string) (string, error) {
type Variable struct {
Device string
}
variables := Variable{device_name}
t, err := template.New("topic").Parse(s.Setpoint_state_topic)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = t.Execute(buf, variables)
if err != nil {
return "", err
}
return buf.String(), nil
}
func (s TVRSettings) FormatTopic(device_name string) (string, error) {
type Variable struct {
Device string
}
variables := Variable{device_name}
t, err := template.New("topic").Parse(s.Setpoint_topic)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = t.Execute(buf, variables)
if err != nil {
return "", err
}
return buf.String(), nil
}
func (s TVRSettings) FormatPayload(setpoint int) (string, error) {
type Variable struct {
Setpoint int
}
variables := Variable{setpoint}
t, err := template.New("payload").Parse(s.Setpoint_payload)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = t.Execute(buf, variables)
if err != nil {
return "", err
}
return buf.String(), nil
}
type DeviceSettings struct {
Programs Programs `json:"programs"`
Presets []Preset `json:"presets"`
TVR TVRSettings `json:"TVR"`
}
//////////////////////////////////////////////
// Defaults
var DefaultPresets = []Preset{
{Label: "default", Value: 17, Color: "#012a36"},
{Label: "normal", Value: 19, Color: "#b6244f"},
}
var defaultSetpoints = []Setpoint{
{Start: 7 * 60, Preset_id: 1},
{Start: 8 * 60, Preset_id: 0},
{Start: 16 * 60, Preset_id: 1},
{Start: 22 * 60, Preset_id: 0},
}
var DefaultWeekProgram = WeekProgram{
Monday: defaultSetpoints,
Thuesday: defaultSetpoints,
Wednesday: defaultSetpoints,
Thursday: defaultSetpoints,
Friday: defaultSetpoints,
Saturday: defaultSetpoints,
Sunday: defaultSetpoints,
}
var DefaultPrograms = Programs{
"default": DefaultWeekProgram,
}
var DefaultTVRSettings = TVRSettings{
Setpoint_topic: "zigbee2mqtt/TVR/{{.Device}}/set",
Setpoint_payload: "{\"current_heating_setpoint\": {{.Setpoint}}}",
Setpoint_state_topic: "zigbee2mqtt/TVR/{{.Device}}",
Setpoint_state_jp: "$.current_heating_setpoint",
StateLocalTemperatureJP: "$.local_temperature",
StateValveJP: "$.valve_state",
}
var DefaultDeviceSettings = DeviceSettings{
Programs: DefaultPrograms,
Presets: DefaultPresets,
TVR: DefaultTVRSettings,
}