Skip to content

Commit

Permalink
feat: Add zone HeatingSchedule object
Browse files Browse the repository at this point in the history
The HeatingZone allows to configure schedule timetable and time blocks
with an easy to use interface.
  • Loading branch information
gonzolino committed Mar 18, 2022
1 parent 0ede991 commit 21f2617
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 69 deletions.
82 changes: 20 additions & 62 deletions examples/schedule/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,16 @@ func main() {
}

// Show schedule timetables
fmt.Println("Available heating schedule timetables:")
for _, timetable := range []*gotado.ScheduleTimetable{zone.ScheduleMonToSun(), zone.ScheduleMonToFriSatSun(), zone.ScheduleAllDays()} {
fmt.Printf("%s (%d)\n", timetable.Type, timetable.ID)
}
activeSchedule, err := zone.GetActiveScheduleTimetable(ctx)
activeSchedule, err := zone.GetHeatingSchedule(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get active heating schedule timetable: %v\n", err)
fmt.Fprintf(os.Stderr, "Failed to get active heating schedule: %v\n", err)
os.Exit(1)
}
fmt.Printf("Active heating schedule timetable: %s (%d)\n", activeSchedule.Type, activeSchedule.ID)
fmt.Printf("Active heating schedule days: %s\n", activeSchedule.ScheduleDays)

// Get and print schedule time blocks
timeBlocks, err := activeSchedule.GetTimeBlocks(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get heating schedule: %v\n", err)
os.Exit(1)
}
fmt.Println("Heating schedule:")
for _, block := range timeBlocks {
for _, block := range activeSchedule.Blocks {
fmt.Printf("%s (%s - %s): %s %s", block.DayType, block.Start, block.End, block.Setting.Type, block.Setting.Power)
if block.Setting.Power == "ON" {
fmt.Printf(" (%.2f°C, %.2f°F)", block.Setting.Temperature.Celsius, block.Setting.Temperature.Fahrenheit)
Expand All @@ -84,64 +75,31 @@ func main() {
}

// Update schedule
newSchedule := zone.ScheduleMonToSun()
newTimeBlocks := []*gotado.ScheduleTimeBlock{
{
DayType: "MONDAY_TO_SUNDAY",
Start: "00:00",
End: "12:00",
GeolocationOverride: false,
Setting: &gotado.ZoneSetting{
Type: "HEATING",
Power: "ON",
Temperature: &gotado.ZoneSettingTemperature{
Celsius: 18,
},
},
},
{
DayType: "MONDAY_TO_SUNDAY",
Start: "12:00",
End: "14:00",
GeolocationOverride: false,
Setting: &gotado.ZoneSetting{
Type: "HEATING",
Power: "OFF",
},
},
{
DayType: "MONDAY_TO_SUNDAY",
Start: "14:00",
End: "00:00",
GeolocationOverride: false,
Setting: &gotado.ZoneSetting{
Type: "HEATING",
Power: "ON",
Temperature: &gotado.ZoneSettingTemperature{
Celsius: 20,
},
},
},
}
if err := zone.SetActiveScheduleTimetable(ctx, newSchedule); err != nil {
fmt.Fprintf(os.Stderr, "Failed to set active heating schedule timetable: %v\n", err)
newSchedule, err := zone.ScheduleMonToFriSatSun(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize new heating schedule: %v\n", err)
os.Exit(1)
}
if err := newSchedule.SetTimeBlocks(ctx, newTimeBlocks); err != nil {
fmt.Fprintf(os.Stderr, "Failed to set heating schedule: %v\n", err)

newSchedule = newSchedule.
NewTimeBlock(ctx, gotado.DayTypeMondayToFriday, "00:00", "07:00", true, gotado.PowerOff, 0.0).
AddTimeBlock(ctx, gotado.DayTypeMondayToFriday, "07:00", "00:00", false, gotado.PowerOn, 20.0).
AddTimeBlock(ctx, gotado.DayTypeSaturday, "00:00", "09:00", true, gotado.PowerOff, 0.0).
AddTimeBlock(ctx, gotado.DayTypeSaturday, "09:00", "00:00", false, gotado.PowerOn, 18.0).
AddTimeBlock(ctx, gotado.DayTypeSunday, "00:00", "09:00", true, gotado.PowerOff, 0.0).
AddTimeBlock(ctx, gotado.DayTypeSunday, "09:00", "00:00", false, gotado.PowerOn, 19.0)

if err := zone.SetHeatingSchedule(ctx, newSchedule); err != nil {
fmt.Fprintf(os.Stderr, "Failed to set active heating schedule: %v\n", err)
os.Exit(1)
}

fmt.Printf("Changed heating schedule in home '%s', zone '%s'\n", home.Name, zone.Name)
time.Sleep(10 * time.Second)

// Restore original heating schedule
if err := zone.SetActiveScheduleTimetable(ctx, activeSchedule); err != nil {
fmt.Fprintf(os.Stderr, "Failed to set active heating schedule timetable: %v\n", err)
os.Exit(1)
}
if err := activeSchedule.SetTimeBlocks(ctx, timeBlocks); err != nil {
fmt.Fprintf(os.Stderr, "Failed to set heating schedule: %v\n", err)
if err := zone.SetHeatingSchedule(ctx, activeSchedule); err != nil {
fmt.Fprintf(os.Stderr, "Failed to set active heating schedule: %v\n", err)
os.Exit(1)
}
fmt.Printf("Restored original heating schedule in home '%s', zone '%s'\n", home.Name, zone.Name)
Expand Down
15 changes: 15 additions & 0 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,21 @@ type ScheduleTimeBlock struct {
Setting *ZoneSetting `json:"setting"`
}

type ScheduleDays string

const (
ScheduleDaysMonToSun ScheduleDays = ScheduleDays(TimetableOneDay)
ScheduleDaysMonToFriSatSun ScheduleDays = ScheduleDays(TimetableThreeDay)
ScheduleDaysMonTueWedThuFriSatSun ScheduleDays = ScheduleDays(TimetableSevenDay)
)

type HeatingSchedule struct {
zone *Zone
ScheduleDays ScheduleDays
Timetable *ScheduleTimetable
Blocks []*ScheduleTimeBlock
}

// ComfortLevel defines how a zone is preheated before arrival
type ComfortLevel int32

Expand Down
41 changes: 40 additions & 1 deletion schedule.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gotado

import "context"
import (
"context"
)

// GetTimeBlocks returns all time blocks of the schedule.
func (s *ScheduleTimetable) GetTimeBlocks(ctx context.Context) ([]*ScheduleTimeBlock, error) {
Expand Down Expand Up @@ -31,3 +33,40 @@ func (s *ScheduleTimetable) SetTimeBlocks(ctx context.Context, blocks []*Schedul

return nil
}

// NewTimeBlock resets the list of time blocks in the heating schedule and adds the given time block as the first new block.
func (s *HeatingSchedule) NewTimeBlock(ctx context.Context, dayType DayType, start, end string, geolocationOverride bool, power Power, temperature float64) *HeatingSchedule {
s.Blocks = make([]*ScheduleTimeBlock, 0)
return s.AddTimeBlock(ctx, dayType, start, end, geolocationOverride, power, temperature)
}

// AddTimeBlock adds a time block to the heating schedule.
// Start and end parameters define when the time blocks starts and ends and are
// in the format HH:MM. GeolocationOverride specifies if the timeblock will
// override geofencing control. Power defines if heating is powered on or off
// and temperature specifies the temperature to heat to. Temperature is
// interpreted in Celsius / Fahrenheit depending on the temperature unit
// configured in the home.
func (s *HeatingSchedule) AddTimeBlock(ctx context.Context, dayType DayType, start, end string, geolocationOverride bool, power Power, temperature float64) *HeatingSchedule {
temp := &ZoneSettingTemperature{}
switch s.zone.home.TemperatureUnit {
case TemperatureUnitCelsius:
temp.Celsius = temperature
case TemperatureUnitFahrenheit:
temp.Fahrenheit = temperature
}

block := &ScheduleTimeBlock{
DayType: dayType,
Start: start,
End: end,
GeolocationOverride: geolocationOverride,
Setting: &ZoneSetting{
Type: ZoneTypeHeating,
Power: power,
Temperature: temp,
},
}
s.Blocks = append(s.Blocks, block)
return s
}
81 changes: 75 additions & 6 deletions zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,20 +126,47 @@ func (z *Zone) newScheduleTimetable(id int32, typ TimetableType) *ScheduleTimeta
}
}

// newHeatingSchedule creates a new heating schedule linked to the zone.
func (z *Zone) newHeatingSchedule(timetable *ScheduleTimetable, blocks []*ScheduleTimeBlock) *HeatingSchedule {
return &HeatingSchedule{
zone: z,
Timetable: timetable,
Blocks: blocks,
}
}

// ScheduleMonToSun has the same schedule for all days between monday and sunday.
func (z *Zone) ScheduleMonToSun() *ScheduleTimetable {
return z.newScheduleTimetable(0, TimetableOneDay)
func (z *Zone) ScheduleMonToSun(ctx context.Context) (*HeatingSchedule, error) {
timetable := z.newScheduleTimetable(0, TimetableOneDay)
blocks, err := timetable.GetTimeBlocks(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get schedule time blocks: %w", err)
}

return z.newHeatingSchedule(timetable, blocks), nil
}

// TimetableTMonToFriSatSun has the same schedule for all days between monday
// and friday and different schedules for saturday and sunday.
func (z *Zone) ScheduleMonToFriSatSun() *ScheduleTimetable {
return z.newScheduleTimetable(1, TimetableThreeDay)
func (z *Zone) ScheduleMonToFriSatSun(ctx context.Context) (*HeatingSchedule, error) {
timetable := z.newScheduleTimetable(1, TimetableThreeDay)
blocks, err := timetable.GetTimeBlocks(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get schedule time blocks: %w", err)
}

return z.newHeatingSchedule(timetable, blocks), nil
}

// ScheduleAllDays has a different schedule for each day of the week.
func (z *Zone) ScheduleAllDays() *ScheduleTimetable {
return z.newScheduleTimetable(2, TimetableSevenDay)
func (z *Zone) ScheduleAllDays(ctx context.Context) (*HeatingSchedule, error) {
timetable := z.newScheduleTimetable(2, TimetableSevenDay)
blocks, err := timetable.GetTimeBlocks(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get schedule time blocks: %w", err)
}

return z.newHeatingSchedule(timetable, blocks), nil
}

// GetActiveScheduleTimetable returns the active schedule timetable for the zone.
Expand All @@ -160,6 +187,48 @@ func (z *Zone) SetActiveScheduleTimetable(ctx context.Context, timetable *Schedu
return z.client.put(ctx, apiURL("homes/%d/zones/%d/schedule/activeTimetable", z.home.ID, z.ID), newTimetable)
}

// GetHeatingSchedule gets the whole active schedule for the zone, including active timetable and time blocks.
func (z *Zone) GetHeatingSchedule(ctx context.Context) (*HeatingSchedule, error) {
timetable, err := z.GetActiveScheduleTimetable(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get active schedule timetable: %w", err)
}
blocks, err := timetable.GetTimeBlocks(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get time blocks: %w", err)
}

var scheduleDays ScheduleDays
switch timetable.Type {
case TimetableOneDay:
scheduleDays = ScheduleDaysMonToSun
case TimetableThreeDay:
scheduleDays = ScheduleDaysMonToFriSatSun
case TimetableSevenDay:
scheduleDays = ScheduleDaysMonTueWedThuFriSatSun
default:
return nil, errors.New("unknown schedule timetable type")
}

return &HeatingSchedule{
zone: z,
ScheduleDays: scheduleDays,
Timetable: timetable,
Blocks: blocks,
}, nil
}

// SetHeatingSchedule sets the whole active schedule for the zone, including active timetable and time blocks.
func (z *Zone) SetHeatingSchedule(ctx context.Context, schedule *HeatingSchedule) error {
if err := z.SetActiveScheduleTimetable(ctx, schedule.Timetable); err != nil {
return fmt.Errorf("unable to set active schedule timetable: %w", err)
}
if err := schedule.Timetable.SetTimeBlocks(ctx, schedule.Blocks); err != nil {
return fmt.Errorf("unable to set time blocks: %w", err)
}
return nil
}

// GetAwayConfiguration returns the away configuration of the zone.
func (z *Zone) GetAwayConfiguration(ctx context.Context) (*AwayConfiguration, error) {
awayConfig := &AwayConfiguration{}
Expand Down

0 comments on commit 21f2617

Please sign in to comment.