Skip to content

Commit

Permalink
Merge pull request #56 from chiefdragon/enable-auto-geofencing
Browse files Browse the repository at this point in the history
Add setAuto() to enable auto geofencing with checks and tests
  • Loading branch information
Wolfgang Malgadey authored Mar 16, 2023
2 parents 17a0656 + a52a4ec commit c25c81f
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 5 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.5, 3.6, 3.7, 3.8]
python-version: ["3.7", "3.8", "3.9", "3.10"]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
10 changes: 10 additions & 0 deletions PyTado/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Tado exceptions."""


class TadoException(Exception):
"""Base exception class for Tado."""


class TadoNotSupportedException(TadoException):
"""Exception to indicate a requested action is not supported by Tado."""

49 changes: 48 additions & 1 deletion PyTado/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from enum import IntEnum

from .zone import TadoZone
from .exceptions import TadoNotSupportedException

_LOGGER = logging.getLogger(__name__)

Expand All @@ -27,8 +28,13 @@ class Timetable(IntEnum):
THREE_DAY = 1
SEVEN_DAY = 2

# Instance-wide variables
_debugCalls = False

# Track whether the user's Tado instance supports auto-geofencing,
# set to None until explicitly set
_autoGeofencingSupported = None

# Instance-wide constant info
api2url = 'https://my.tado.com/api/v2/'
mobi2url = 'https://my.tado.com/mobile/1.9/'
Expand Down Expand Up @@ -205,10 +211,41 @@ def getHomeState(self):
# but a button is shown in the app. showHomePresenceSwitchButton
# is an indicator, that the homeState can be switched
# {"presence":"HOME","showHomePresenceSwitchButton":true}
# With an auto assist skill, showSwitchToAutoGeofencingButton is
# present when geofencing has been disabled due to the user selecting
# a mode manually:
# {'presence': 'HOME', 'presenceLocked': False,
# 'showSwitchToAutoGeofencingButton': True}
# With an auto assist skill, showSwitchToAutoGeofencingButton is NOT
# present when geofencing has been enabled:
# {'presence': 'HOME', 'presenceLocked': True}
# In both scenarios with the auto assist skill, 'presenceLocked'
# indicates whether presence is current locked (manually set) to
# HOME or AWAY or not locked (automatically set based on geolocation)
cmd = 'state'
data = self._apiCall(cmd)

# Check whether Auto Geofencing is permitted via the presence of
# showSwitchToAutoGeofencingButton or currently enabled via the
# presence of presenceLocked = False
if "showSwitchToAutoGeofencingButton" in data:
self._autoGeofencingSupported = data['showSwitchToAutoGeofencingButton']
elif "presenceLocked" in data:
if not data['presenceLocked']:
self._autoGeofencingSupported = True
else:
self._autoGeofencingSupported = False
else:
self._autoGeofencingSupported = False

return data

def getAutoGeofencingSupported(self):
"""Return whether the Tado Home supports auto geofencing"""
if self._autoGeofencingSupported is None:
self.getHomeState()
return self._autoGeofencingSupported

def getCapabilities(self, zone):
"""Gets current capabilities of Zone zone."""
# pylint: disable=C0103
Expand Down Expand Up @@ -390,7 +427,17 @@ def setAway(self):
payload = { "homePresence": "AWAY" }
data = self._apiCall(cmd, "PUT", payload)
return data


def setAuto(self):
"""Sets HomeState to AUTO """
# Only attempt to set Auto Geofencing if it is believed to be supported
if self._autoGeofencingSupported:
cmd = 'presenceLock'
data = self._apiCall(cmd, "DELETE")
return data
else:
raise TadoNotSupportedException("Auto mode is not known to be supported.")

def getWindowState(self, zone):
"""Returns the state of the window for Zone zone"""
data = self.getState(zone)['openWindow']
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
requirements = [x.strip() for x in open(here('requirements.txt')).readlines()]

setup(name='python-tado',
version='0.13.0',
version='0.14.0',
description='PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi',
long_description=readme,
keywords='tado',
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/tadov2.home_state.auto_not_supported.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presence": "HOME",
"presenceLocked": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presence": "HOME",
"presenceLocked": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"presence": "HOME",
"presenceLocked": true,
"showSwitchToAutoGeofencingButton": true
}
80 changes: 80 additions & 0 deletions tests/test_tado.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Test the Tado object."""

import os
import json
from unittest.mock import patch

from PyTado.interface import Tado


def load_fixture(filename):
"""Load a fixture."""
path = os.path.join(os.path.dirname(__file__), "fixtures", filename)
with open(path) as fptr:
return fptr.read()


def mock_tado():
"""Mock out a Tado object."""
with patch("PyTado.interface.Tado._loginV2"), patch(
"PyTado.interface.Tado.getMe"
):
tado = Tado("[email protected]", "mypassword")
return tado


def test_home_can_be_set_to_auto_when_home_supports_geofencing_and_home_set_to_manual_mode():
"""Test that the Tado home can be set to auto geofencing mode when it is supported and currently in manual mode."""
tado = mock_tado()
with patch("PyTado.interface.Tado._apiCall",
return_value=json.loads(load_fixture("tadov2.home_state.auto_supported.manual_mode.json")),
):
tado.getHomeState()

with patch("PyTado.interface.Tado._apiCall"):
raised = False
try:
tado.setAuto()
except:
raised = True

# An exception should NOT have been raised because geofencing is supported
assert raised is False


def test_home_remains_set_to_auto_when_home_supports_geofencing_and_home_already_set_to_auto_mode():
"""Test that the Tado home remains set to auto geofencing mode when it is supported, and already in auto mode."""
tado = mock_tado()
with patch("PyTado.interface.Tado._apiCall",
return_value=json.loads(load_fixture("tadov2.home_state.auto_supported.auto_mode.json")),
):
tado.getHomeState()

with patch("PyTado.interface.Tado._apiCall"):
raised = False
try:
tado.setAuto()
except:
raised = True

# An exception should NOT have been raised because geofencing is supported
assert raised is False


def test_home_cant_be_set_to_auto_when_home_does_not_support_geofencing():
"""Test that the Tado home can't be set to auto geofencing mode when it is not supported."""
tado = mock_tado()
with patch("PyTado.interface.Tado._apiCall",
return_value=json.loads(load_fixture("tadov2.home_state.auto_not_supported.json")),
):
tado.getHomeState()

with patch("PyTado.interface.Tado._apiCall"):
raised = False
try:
tado.setAuto()
except:
raised = True

# An exception should have been raised because geofencing is NOT supported
assert raised is True

0 comments on commit c25c81f

Please sign in to comment.