256 lines
5.3 KiB
Go
256 lines
5.3 KiB
Go
package device
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
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 = 0
|
|
Thuesday DayOfWeek = 1
|
|
Wednesday DayOfWeek = 2
|
|
Thursday DayOfWeek = 3
|
|
Friday DayOfWeek = 4
|
|
Saturday DayOfWeek = 5
|
|
Sunday DayOfWeek = 6
|
|
)
|
|
|
|
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"`
|
|
}
|
|
|
|
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",
|
|
}
|
|
|
|
var DefaultDeviceSettings = DeviceSettings{
|
|
Programs: DefaultPrograms,
|
|
Presets: DefaultPresets,
|
|
TVR: DefaultTVRSettings,
|
|
}
|