Skip to content

Commit

Permalink
feat: add functions to control away config
Browse files Browse the repository at this point in the history
  • Loading branch information
gonzolino committed Jun 10, 2021
1 parent 095c5b7 commit 08a99b4
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
112 changes: 112 additions & 0 deletions examples/away/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"context"
"fmt"
"os"
"time"

"github.com/gonzolino/gotado"
)

const (
clientID = "tado-web-app"
clientSecret = "wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc"
)

func main() {
// Get credentials from env vars
username, ok := os.LookupEnv("TADO_USERNAME")
if !ok {
fmt.Fprintf(os.Stderr, "Variable TADO_USERNAME not set\n")
os.Exit(1)
}
password, ok := os.LookupEnv("TADO_PASSWORD")
if !ok {
fmt.Fprintf(os.Stderr, "Variable TADO_PASSWORD not set\n")
os.Exit(1)
}

if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "Usage: %s homeName zoneName\n", os.Args[0])
os.Exit(1)
}
homeName, zoneName := os.Args[1], os.Args[2]

ctx := context.Background()

// Create authenticated tado° client
client := gotado.NewClient(clientID, clientSecret).WithTimeout(5 * time.Second)
client, err := client.WithCredentials(ctx, username, password)
if err != nil {
fmt.Fprintf(os.Stderr, "Authentication failed: %v\n", err)
os.Exit(1)
}

user, err := gotado.GetMe(client)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get user info: %v\n", err)
os.Exit(1)
}

// Find the home to control
var home *gotado.UserHome
for _, h := range user.Homes {
if h.Name == homeName {
home = &h
break
}
}
if home == nil {
fmt.Fprintf(os.Stderr, "Home '%s' not found\n", homeName)
os.Exit(1)
}

// Find zone to control
zones, err := gotado.GetZones(client, home)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get zones: %v\n", err)
os.Exit(1)
}
var zone *gotado.Zone
for _, z := range zones {
if z.Name == zoneName {
zone = z
break
}
}
if zone == nil {
fmt.Fprintf(os.Stderr, "Zone '%s' not found\n", zoneName)
os.Exit(1)
}

// Show away configuration
awayConfig, err := gotado.GetAwayConfiguration(client, home, zone)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get away configuration: %v\n", err)
os.Exit(1)
}
fmt.Println("Away Configuration:")
if awayConfig.AutoAdjust {
fmt.Printf("Comfort Level: %d\n", awayConfig.ComfortLevel)
} else {
fmt.Printf("Temperature: %.2f C°, %.2f F°\n", awayConfig.Setting.Temperature.Celsius, awayConfig.Setting.Temperature.Fahrenheit)
}

// Update comfort level
err = gotado.SetAwayComfortLevel(client, home, zone, 0)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get set comfort level: %v\n", err)
os.Exit(1)
}

fmt.Printf("Set comfort level for away mode in home '%s', zone '%s' to 'Eco'\n", home.Name, zone.Name)
time.Sleep(10 * time.Second)

// Restore original away configuration
if err := gotado.SetAwayConfiguration(client, home, zone, awayConfig); err != nil {
fmt.Fprintf(os.Stderr, "Failed to set away configuration: %v\n", err)
os.Exit(1)
}
fmt.Printf("Restored original away configuration in home '%s', zone '%s'\n", home.Name, zone.Name)
}
104 changes: 104 additions & 0 deletions tado.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,28 @@ type ScheduleBlockSettingTemperature struct {
Fahrenheit float64 `json:"fahrenheit"`
}

// AwayConfiguration holds the settings to use when everybody leaves the house
type AwayConfiguration struct {
Type string `json:"type"`
AutoAdjust bool `json:"autoAdjust"`
// Comfort Level must be 0 (Eco), 50 (Balanced) or 100 (Comfort)
ComfortLevel int32 `json:"comfortLevel"`
Setting *AwayConfigurationSetting `json:"setting"`
}

// AwayConfigurationSetting holds the setting of an away configuration
type AwayConfigurationSetting struct {
Type string `json:"type"`
Power string `json:"power"`
Temperature *AwayConfigurationSettingTemperature `json:"temperature,omitempty"`
}

// AwayConfigurationSettingTemperature holds the temperature of an away configuration setting
type AwayConfigurationSettingTemperature struct {
Celsius float64 `json:"celsius"`
Fahrenheit float64 `json:"fahrenheit"`
}

// PresenceLock holds a locked presence setting for a home
type PresenceLock struct {
HomePresence string `json:"homePresence"`
Expand Down Expand Up @@ -608,6 +630,88 @@ func SetSchedule(client *Client, userHome *UserHome, zone *Zone, timetable *Sche
return nil
}

// GetAwayConfiguration returns the away configuration of the given zone
func GetAwayConfiguration(client *Client, userHome *UserHome, zone *Zone) (*AwayConfiguration, error) {
resp, err := client.Request(http.MethodGet, apiURL("homes/%d/zones/%d/schedule/awayConfiguration", userHome.ID, zone.ID), nil)
if err != nil {
return nil, err
}

if err := isError(resp); err != nil {
return nil, fmt.Errorf("tado° API error: %w", err)
}

awayConfig := &AwayConfiguration{}
if err := json.NewDecoder(resp.Body).Decode(&awayConfig); err != nil {
return nil, fmt.Errorf("unable to decode tado° API response: %w", err)
}

return awayConfig, nil
}

// SetAwayConfiguration sets an away configuration for the given zone
func SetAwayConfiguration(client *Client, userHome *UserHome, zone *Zone, awayConfig *AwayConfiguration) error {
data, err := json.Marshal(awayConfig)
if err != nil {
return fmt.Errorf("unable to marshal away configuration: %w", err)
}
req, err := http.NewRequest(http.MethodPut, apiURL("homes/%d/zones/%d/schedule/awayConfiguration", userHome.ID, zone.ID), bytes.NewReader(data))
if err != nil {
return fmt.Errorf("unable to create http request: %w", err)
}
req.Header.Set("Content-Type", "application/json;charset=utf-8")
resp, err := client.Do(req)
if err != nil {
return err
}

if err := isError(resp); err != nil {
return fmt.Errorf("tado° API error: %w", err)
}

return nil
}

// SetAwayTemperature sets the manual temperature for a zone when everybody leaves the house
func SetAwayTemperature(client *Client, userHome *UserHome, zone *Zone, temperature float64) error {
home, err := GetHome(client, userHome)
if err != nil || home == nil {
return fmt.Errorf("unable to determine temperature unit")
}
temperatureSetting := &AwayConfigurationSettingTemperature{}
switch home.TemperatureUnit {
case "CELSIUS":
temperatureSetting.Celsius = temperature
case "FAHRENHEIT":
temperatureSetting.Fahrenheit = temperature
default:
return fmt.Errorf("invalid temperature unit '%s'", home.TemperatureUnit)
}

awayConfig := &AwayConfiguration{
Type: "HEATING",
AutoAdjust: false,
Setting: &AwayConfigurationSetting{
Type: "HEATING",
Power: "ON",
Temperature: temperatureSetting,
},
}

return SetAwayConfiguration(client, userHome, zone, awayConfig)
}

// SetAwayComfortLevel sets the away configuration to auto-adjust at the given comfort level ("preheat").
// Allowed values got the comfort level are 0, 50 and 100 (Eco, Balanced, Comfort)
func SetAwayComfortLevel(client *Client, userHome *UserHome, zone *Zone, comfortLevel int32) error {
awayConfig := &AwayConfiguration{
Type: "HEATING",
AutoAdjust: true,
ComfortLevel: comfortLevel,
}
return SetAwayConfiguration(client, userHome, zone, awayConfig)
}

// setPresenceLock sets a locked presence on the given home (HOME or AWAY)
func setPresenceLock(client *Client, userHome *UserHome, presence PresenceLock) error {
data, err := json.Marshal(presence)
Expand Down

0 comments on commit 08a99b4

Please sign in to comment.