Skip to content

Commit

Permalink
deep sleep mode, improved rules
Browse files Browse the repository at this point in the history
  • Loading branch information
Jannik Sven Scharrenbach committed Jan 9, 2021
1 parent 0aecae5 commit 49b90b9
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 24 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ Some values are already set up, other ones need to be entered manually (username
{
"username": "<your-username>",
"password": "<your-password>",
"away_temperature": <int, temperature to be set when away>,
"away_temperature": <int, temperature to be set when in away mode>,
"allow_deep_sleep": <true/false, allow/disallow deep sleep>,
"deep_sleep_after_hours": <float, time in hours after which deep sleep gets enabled>,
"deep_sleep_temperature": <int, temperature to be set when in deep sleep mode>,
"interval": <int, interval of ping events in seconds>,
"max_ping_cnt": <int, number of consecutive pings>,
"client_state_history_len": <int, number of ping events to evaluate for state>,
"min_home_success_pings": <int, minimal number of successfull pings to set state to home>,
"print_timestamp": <true/false, print the timestamps in terminal (set to false for privacy reasons)>,
"rules": [
{
"zone_id": <int, id of the zone>,
"zone_id": <int, id of the zone OR list<int>, zone ids OR "default">,
"ips": [
"<list of ips for the desired zone>"
]
Expand All @@ -47,7 +50,12 @@ Some values are already set up, other ones need to be entered manually (username
```

Each rule is defined by the zone id and a list of the devices to look for (make sure the devices have a static ip).

The `zone_id` field can be a single zone id, a list of zone ids (e.g. `[1, 2, 3]`) or the string `"default"`, which applies to all zones no rule is defined for.
Keep in mind: There can only be one rule per zone, otherwise the application will terminate.

Multiple rules aswell as multiple devices are possible. Only if all devices are not available, the state of the zone is away.
The `deep sleep mode` is acitvated, when a zone is set to away mode for `deep_sleep_after_hours` number of hours. Then the temperature is set to `deep_sleep_temperature`.

To list all zones run
```pip packets
Expand Down
3 changes: 3 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"username": "<your-username>",
"password": "<your-password>",
"away_temperature": 16,
"allow_deep_sleep": true,
"deep_sleep_after_hours": 12.0,
"deep_sleep_temperature": 14,
"interval": 10,
"max_ping_cnt": 5,
"client_state_history_len": 30,
Expand Down
27 changes: 24 additions & 3 deletions src/App.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
File name: App.py
Author: Jannik Scharrenbach
Date created: 10/10/2020
Date last modified: 10/12/2020
Date last modified: 09/01/2021
Python Version: 3.8
"""

Expand All @@ -23,6 +23,8 @@ def __init__(self):
ConfigHelper.initialize()
self.__tado = TadoWrapper()
self.__zone_states = None
self.__zone_off_time = dict()
ConfigHelper.initialize_zones(self.__tado.get_zones())

self.__client_states = None
self.__client_state_history = list()
Expand Down Expand Up @@ -87,6 +89,17 @@ def __get_desired_zone_states(self, client_states):
for r in ConfigHelper.get_rules():
if len(set(r["ips"]).intersection(clients_home)) != 0:
z_states[r["zone_id"]] = zs.ON
if r["zone_id"] in self.__zone_off_time.keys():
# remove from off time if turned on
self.__zone_off_time.pop(r["zone_id"])
elif ConfigHelper.get_allow_deep_sleep():
if r["zone_id"] in self.__zone_off_time.keys():
if (time.time() - self.__zone_off_time[r["zone_id"]]) > ConfigHelper.get_deep_sleep_after_seconds():
# set to deep sleep
z_states[r["zone_id"]] = zs.DEEP_SLEEP
else:
# save time when state was set to off
self.__zone_off_time[r["zone_id"]] = time.time()

return z_states

Expand All @@ -102,11 +115,15 @@ def __update_heating(self):

# get zones to turn on
turn_on = set(z[0] for z in desired_zone_states.items() if z[1] == zs.ON).intersection(
set(z[0] for z in self.__zone_states.items() if z[1] == zs.OFF))
set(z[0] for z in self.__zone_states.items() if z[1] != zs.ON))

# get zones to turn off
turn_off = set(z[0] for z in desired_zone_states.items() if z[1] == zs.OFF).intersection(
set(z[0] for z in self.__zone_states.items() if z[1] == zs.ON))
set(z[0] for z in self.__zone_states.items() if z[1] != zs.OFF))

# get zones to turn to deep sleep mode
turn_deep_sleep = set(z[0] for z in desired_zone_states.items() if z[1] == zs.DEEP_SLEEP).intersection(
set(z[0] for z in self.__zone_states.items() if z[1] != zs.DEEP_SLEEP))

for zone in turn_on:
LoggingHelper.log("Switching zone {} to state 'on'... ".format(zone))
Expand All @@ -116,6 +133,10 @@ def __update_heating(self):
LoggingHelper.log("Switching zone {} to state 'off'... ".format(zone))
self.__tado.set_zone(zone, ConfigHelper.get_away_temperature())

for zone in turn_deep_sleep:
LoggingHelper.log("Switching zone {} to state 'deep sleep'... ".format(zone))
self.__tado.set_zone(zone, ConfigHelper.get_deep_sleep_temperature())

self.__zone_states = desired_zone_states

def run(self):
Expand Down
91 changes: 76 additions & 15 deletions src/ConfigHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,126 @@
File name: ConfigHelper.py
Author: Jannik Scharrenbach
Date created: 10/10/2020
Date last modified: 10/12/2020
Date last modified: 09/01/2021
Python Version: 3.8
"""

import json
import os

import sys
import copy

class ConfigHelper:
CONFIG = None
__CONFIG = None

__RULES = None
__ZONES = None

@staticmethod
def initialize():
if ConfigHelper.CONFIG is None:
if ConfigHelper.__CONFIG is None:
with open(os.path.dirname(__file__) + "/" + os.path.pardir + "/config.json") as f:
ConfigHelper.CONFIG = json.load(f)
ConfigHelper.__CONFIG = json.load(f)

@staticmethod
def initialize_zones(zones):
ConfigHelper.__ZONES = zones
ConfigHelper.get_zones()

@staticmethod
def get_credentials():
return ConfigHelper.CONFIG["username"], ConfigHelper.CONFIG["password"]
return ConfigHelper.__CONFIG["username"], ConfigHelper.__CONFIG["password"]

@staticmethod
def get_ips():
ips = set()

for r in ConfigHelper.CONFIG["rules"]:
for r in ConfigHelper.get_rules():
for ip in r["ips"]:
ips.add(ip)

return list(ips)

@staticmethod
def get_rules():
return ConfigHelper.CONFIG["rules"]
if ConfigHelper.__RULES is None:
# initially construct rules
ConfigHelper.__RULES = list()
def_rule = None

for r in ConfigHelper.__CONFIG["rules"]:
if isinstance(r["zone_id"], list):
# create rule for each element
for z in r["zone_id"]:
r_copy = copy.copy(r)
r_copy["zone_id"] = z
ConfigHelper.__RULES.append(r_copy)
elif not isinstance(r["zone_id"], str):
# save rule
ConfigHelper.__RULES.append(r)
elif r["zone_id"] == "default":
# set default rule
def_rule = r

if def_rule is not None and ConfigHelper.__ZONES is not None:
# generate default rule
default_zones = set(t_zone["id"] for t_zone in ConfigHelper.__ZONES).difference(set(z_defined["zone_id"] for z_defined in ConfigHelper.__RULES))
for z in default_zones:
r_copy = copy.copy(def_rule)
r_copy["zone_id"] = z
ConfigHelper.__RULES.append(r_copy)

# check validity
z_ids = set()
for r in ConfigHelper.__RULES:
if r["zone_id"] in z_ids:
print("Invalid config, multiple rules for zone {}!".format(r["zone_id"]))
sys.exit(1)
else:
z_ids.add(r["zone_id"])

return ConfigHelper.__RULES

@staticmethod
def get_interval():
return int(ConfigHelper.CONFIG["interval"])
return int(ConfigHelper.__CONFIG["interval"])

@staticmethod
def get_zones():
return set(r["zone_id"] for r in ConfigHelper.CONFIG["rules"])
return set(r["zone_id"] for r in ConfigHelper.get_rules())

@staticmethod
def get_max_ping_cnt():
return int(ConfigHelper.CONFIG["max_ping_cnt"])
return int(ConfigHelper.__CONFIG["max_ping_cnt"])

@staticmethod
def get_away_temperature():
return int(ConfigHelper.CONFIG["away_temperature"])
return int(ConfigHelper.__CONFIG["away_temperature"])

@staticmethod
def get_client_state_history_len():
return int(ConfigHelper.CONFIG["client_state_history_len"])
return int(ConfigHelper.__CONFIG["client_state_history_len"])

@staticmethod
def get_min_home_success_pings():
return int(ConfigHelper.CONFIG["min_home_success_pings"])
return int(ConfigHelper.__CONFIG["min_home_success_pings"])

@staticmethod
def get_print_timestamp():
return ConfigHelper.CONFIG["print_timestamp"]
return ConfigHelper.__CONFIG["print_timestamp"]

@staticmethod
def get_allow_deep_sleep():
return bool(ConfigHelper.__CONFIG["allow_deep_sleep"])

@staticmethod
def get_allow_deep_sleep():
return bool(ConfigHelper.__CONFIG["allow_deep_sleep"])

@staticmethod
def get_deep_sleep_after_seconds():
return float(ConfigHelper.__CONFIG["deep_sleep_after_hours"]) * 60 * 60

@staticmethod
def get_deep_sleep_temperature():
return int(ConfigHelper.__CONFIG["deep_sleep_temperature"])
6 changes: 3 additions & 3 deletions src/TadoWrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
File name: TadoWrapper.py
Author: Jannik Scharrenbach
Date created: 10/10/2020
Date last modified: 10/12/2020
Date last modified: 09/01/2021
Python Version: 3.8
"""

Expand Down Expand Up @@ -49,7 +49,7 @@ def set_zone(self, zone, temperature):
try:
self.__t.setZoneOverlay(zone=zone, overlayMode="MANUAL", setTemp=temperature)
success = True
LoggingHelper.log("Zone {} turned off.".format(zone))
LoggingHelper.log("Zone {} set to {} degrees.".format(zone, temperature))
except (ConnectionError, r_exc.ReadTimeout):
LoggingHelper.log("Unable to set zone value.")
self.__reconnect()
Expand All @@ -60,7 +60,7 @@ def reset_zone(self, zone):
try:
self.__t.resetZoneOverlay(zone=zone)
success = True
LoggingHelper.log("Zone {} turned on.".format(zone))
LoggingHelper.log("Zone {} reset to tado schedule.".format(zone))
except (ConnectionError, r_exc.ReadTimeout):
LoggingHelper.log("Unable to reset zone.")
self.__reconnect()
3 changes: 2 additions & 1 deletion src/ZoneState.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
File name: ZoneState.py
Author: Jannik Scharrenbach
Date created: 10/10/2020
Date last modified: 10/12/2020
Date last modified: 09/01/2021
Python Version: 3.8
"""

Expand All @@ -12,6 +12,7 @@
class ZoneState(Enum):
OFF = 0
ON = 1
DEEP_SLEEP = 2

@staticmethod
def invert(val):
Expand Down

0 comments on commit 49b90b9

Please sign in to comment.