diff --git a/pyfritzhome/cli.py b/pyfritzhome/cli.py
index 9d004b5..55f85c5 100644
--- a/pyfritzhome/cli.py
+++ b/pyfritzhome/cli.py
@@ -174,6 +174,27 @@ def template_apply(fritz, args):
fritz.apply_template(args.ain)
+def list_triggers(fritz, args):
+ """Command that prints all trigger information."""
+ triggers = fritz.get_triggers()
+
+ for trigger in triggers:
+ print("#" * 30)
+ print("name=%s" % trigger.name)
+ print(" ain=%s" % trigger.ain)
+ print(" active=%s" % trigger.active)
+
+
+def trigger_set_active(fritz, args):
+ """Command that enables a trigger."""
+ fritz.set_trigger_active(args.ain)
+
+
+def trigger_set_inactive(fritz, args):
+ """Command that disables a trigger."""
+ fritz.set_trigger_inactive(args.ain)
+
+
def main(args=None):
"""Enter the main function of the CLI tool."""
parser = argparse.ArgumentParser(description="Fritz!Box Smarthome CLI tool.")
@@ -338,6 +359,24 @@ def main(args=None):
subparser.add_argument("ain", type=str, metavar="AIN", help="Actor Identification")
subparser.set_defaults(func=template_apply)
+ # triggers
+ subparser = _sub.add_parser("trigger", help="Trigger commands")
+ _sub_switch = subparser.add_subparsers()
+
+ # list triggers
+ subparser = _sub_switch.add_parser("list", help="List all available triggers")
+ subparser.set_defaults(func=list_triggers)
+
+ # activate triggers
+ subparser = _sub_switch.add_parser("activate", help="Activate a trigger")
+ subparser.add_argument("ain", type=str, metavar="AIN", help="Actor Identification")
+ subparser.set_defaults(func=trigger_set_active)
+
+ # deactivate triggers
+ subparser = _sub_switch.add_parser("deactivate", help="Deactivate a trigger")
+ subparser.add_argument("ain", type=str, metavar="AIN", help="Actor Identification")
+ subparser.set_defaults(func=trigger_set_inactive)
+
args = parser.parse_args(args)
logging.basicConfig()
diff --git a/pyfritzhome/devicetypes/__init__.py b/pyfritzhome/devicetypes/__init__.py
index bef9b92..c84fb3b 100644
--- a/pyfritzhome/devicetypes/__init__.py
+++ b/pyfritzhome/devicetypes/__init__.py
@@ -12,6 +12,7 @@
from .fritzhomedevicelightbulb import FritzhomeDeviceLightBulb
from .fritzhomedeviceblind import FritzhomeDeviceBlind
from .fritzhometemplate import FritzhomeTemplate
+from .fritzhometrigger import FritzhomeTrigger
__all__ = (
@@ -27,4 +28,5 @@
"FritzhomeDeviceLightBulb",
"FritzhomeDeviceBlind",
"FritzhomeTemplate",
+ "FritzhomeTrigger",
)
diff --git a/pyfritzhome/devicetypes/fritzhomeentitybase.py b/pyfritzhome/devicetypes/fritzhomeentitybase.py
index b15278f..c6d8de1 100644
--- a/pyfritzhome/devicetypes/fritzhomeentitybase.py
+++ b/pyfritzhome/devicetypes/fritzhomeentitybase.py
@@ -53,7 +53,11 @@ def _update_from_node(self, node):
@property
def device_and_unit_id(self):
"""Get the device and possible unit id."""
- if self.ain.startswith("tmp") or self.ain.startswith("grp"):
+ if (
+ self.ain.startswith("tmp")
+ or self.ain.startswith("grp")
+ or self.ain.startswith("trg")
+ ):
return (self.ain, None)
elif self.ain.startswith("Z") and len(self.ain) == 19:
return (self.ain[0:17], self.ain[17:])
diff --git a/pyfritzhome/devicetypes/fritzhometrigger.py b/pyfritzhome/devicetypes/fritzhometrigger.py
new file mode 100644
index 0000000..95b0c2b
--- /dev/null
+++ b/pyfritzhome/devicetypes/fritzhometrigger.py
@@ -0,0 +1,22 @@
+"""The trigger class."""
+# -*- coding: utf-8 -*-
+
+import logging
+from xml.etree import ElementTree
+
+from .fritzhomeentitybase import FritzhomeEntityBase
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class FritzhomeTrigger(FritzhomeEntityBase):
+ """The Fritzhome Trigger class."""
+
+ active = None
+
+ def _update_from_node(self, node):
+ _LOGGER.debug("update trigger")
+ _LOGGER.debug(ElementTree.tostring(node))
+ self.ain = node.attrib["identifier"]
+ self.name = node.findtext("name").strip()
+ self.active = node.attrib["active"] == "1"
diff --git a/pyfritzhome/fritzhome.py b/pyfritzhome/fritzhome.py
index e6ed45b..527e5ec 100644
--- a/pyfritzhome/fritzhome.py
+++ b/pyfritzhome/fritzhome.py
@@ -16,6 +16,7 @@
from .errors import InvalidError, LoginError, NotLoggedInError
from .fritzhomedevice import FritzhomeDevice
from .fritzhomedevice import FritzhomeTemplate
+from .fritzhomedevice import FritzhomeTrigger
from typing import Dict, Optional
_LOGGER = logging.getLogger(__name__)
@@ -28,6 +29,7 @@ class Fritzhome(object):
_session = None
_devices: Optional[Dict[str, FritzhomeDevice]] = None
_templates: Optional[Dict[str, FritzhomeTemplate]] = None
+ _triggers: Optional[Dict[str, FritzhomeTrigger]] = None
def __init__(self, host, user, password, ssl_verify=True):
"""Create a fritzhome object."""
@@ -519,3 +521,71 @@ def get_template_by_ain(self, ain):
def apply_template(self, ain):
"""Appliy a template."""
self._aha_request("applytemplate", ain=ain)
+
+ # Trigger-related commands
+
+ def has_triggers(self):
+ """Check if the Fritz!Box supports smarthome triggers."""
+ plain = self._aha_request("gettriggerlistinfos")
+ try:
+ ElementTree.fromstring(plain)
+ except ElementTree.ParseError:
+ return False
+ return True
+
+ def update_triggers(self, ignore_removed=True):
+ """Update the triger."""
+ _LOGGER.info("Updating Trigers ...")
+ if self._triggers is None:
+ self._triggers = {}
+
+ trigger_elements = self.get_trigger_elements()
+ for element in trigger_elements:
+ if element.attrib["identifier"] in self._triggers.keys():
+ _LOGGER.info(
+ "Updating already existing Trigger " + element.attrib["identifier"]
+ )
+ self._triggers[element.attrib["identifier"]]._update_from_node(element)
+ else:
+ _LOGGER.info("Adding new Trigger " + element.attrib["identifier"])
+ trigger = FritzhomeTrigger(self, node=element)
+ self._triggers[trigger.ain] = trigger
+
+ if not ignore_removed:
+ for identifier in list(self._triggers.keys()):
+ if identifier not in [
+ element.attrib["identifier"] for element in trigger_elements
+ ]:
+ _LOGGER.info("Removing no more existing trigger " + identifier)
+ self._triggers.pop(identifier)
+
+ return True
+
+ def get_trigger_elements(self):
+ """Get the DOM elements for the trigger list."""
+ return self._get_listinfo_elements("trigger")
+
+ def get_triggers(self):
+ """Get the list of all known triggers."""
+ return list(self.get_triggers_as_dict().values())
+
+ def get_triggers_as_dict(self):
+ """Get all known triggers as dictionary."""
+ if self._triggers is None:
+ self.update_triggers()
+ return self._triggers
+
+ def get_trigger_by_ain(self, ain):
+ """Return a trigger specified by the AIN."""
+ return self.get_triggers_as_dict()[ain]
+
+ def _set_trigger_state(self, ain, state):
+ self._aha_request("settriggeractive", ain=ain, param={"active": state})
+
+ def set_trigger_active(self, ain):
+ """Set the trigger to active state."""
+ self._set_trigger_state(ain, "1")
+
+ def set_trigger_inactive(self, ain):
+ """Set the trigger to inactive state."""
+ self._set_trigger_state(ain, "0")
diff --git a/pyfritzhome/fritzhomedevice.py b/pyfritzhome/fritzhomedevice.py
index 4823cb4..59648f1 100644
--- a/pyfritzhome/fritzhomedevice.py
+++ b/pyfritzhome/fritzhomedevice.py
@@ -3,6 +3,7 @@
# -*- coding: utf-8 -*-
from .devicetypes import FritzhomeTemplate # noqa: F401
+from .devicetypes import FritzhomeTrigger # noqa: F401
from .devicetypes import (
FritzhomeDeviceAlarm,
FritzhomeDeviceBlind,
diff --git a/tests/responses/triggers/trigger_list.xml b/tests/responses/triggers/trigger_list.xml
new file mode 100644
index 0000000..fbae1b6
--- /dev/null
+++ b/tests/responses/triggers/trigger_list.xml
@@ -0,0 +1,8 @@
+
+
+ Test Trigger Active
+
+
+ Test Trigger Inactive
+
+
\ No newline at end of file
diff --git a/tests/responses/triggers/trigger_list_removed_trigger.xml b/tests/responses/triggers/trigger_list_removed_trigger.xml
new file mode 100644
index 0000000..c9fa381
--- /dev/null
+++ b/tests/responses/triggers/trigger_list_removed_trigger.xml
@@ -0,0 +1,5 @@
+
+
+ Test Trigger Active
+
+
\ No newline at end of file
diff --git a/tests/test_fritzhometrigger.py b/tests/test_fritzhometrigger.py
new file mode 100644
index 0000000..8bc3ad4
--- /dev/null
+++ b/tests/test_fritzhometrigger.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from unittest.mock import MagicMock
+
+from pyfritzhome import Fritzhome
+
+from .helper import Helper
+
+
+class TestFritzhomeTrigger(object):
+ def setup_method(self):
+ self.mock = MagicMock()
+ self.fritz = Fritzhome("10.0.0.1", "user", "pass")
+ self.fritz._request = self.mock
+ self.fritz._devices = {}
+ self.fritz._sid = "0000001"
+
+ self.mock.side_effect = [Helper.response("triggers/trigger_list")]
+
+ self.fritz.update_triggers()
+
+ def test_trigger_init(self):
+ trigger = self.fritz.get_trigger_by_ain("trg303e4f-41979A02D")
+
+ assert trigger.ain == "trg303e4f-41979A02D"
+ assert trigger.active
+
+ trigger = self.fritz.get_trigger_by_ain("trg405e4a-57AE9A80A")
+
+ assert trigger.ain == "trg405e4a-57AE9A80A"
+ assert not trigger.active
+
+ def test_trigger_removed(self):
+ self.mock.side_effect = [
+ Helper.response("triggers/trigger_list"),
+ Helper.response("triggers/trigger_list_removed_trigger"),
+ Helper.response("triggers/trigger_list_removed_trigger"),
+ ]
+
+ self.fritz.update_triggers()
+ assert len(self.fritz.get_triggers()) == 2
+ self.fritz.update_triggers()
+ assert len(self.fritz.get_triggers()) == 2
+ self.fritz.update_triggers(ignore_removed=False)
+ assert len(self.fritz.get_triggers()) == 1
+
+ def test_has_trigger(self):
+ self.mock.side_effect = [
+ "invalid_xml",
+ Helper.response("triggers/trigger_list"),
+ ]
+ assert not self.fritz.has_triggers()
+ assert self.fritz.has_triggers()
+
+ def test_set_trigger_active(self):
+ self.mock.side_effect = [
+ Helper.response("triggers/trigger_list"),
+ "1",
+ ]
+ self.fritz.update_triggers()
+
+ self.fritz.set_trigger_active("trg303e4f-41979A02D")
+ self.fritz._request.assert_called_with(
+ "http://10.0.0.1/webservices/homeautoswitch.lua",
+ {
+ "switchcmd": "settriggeractive",
+ "sid": "0000001",
+ "active": "1",
+ "ain": "trg303e4f-41979A02D",
+ },
+ )
+
+ def test_set_trigger_inactive(self):
+ self.mock.side_effect = [
+ Helper.response("triggers/trigger_list"),
+ "1",
+ ]
+ self.fritz.update_triggers()
+
+ self.fritz.set_trigger_inactive("trg303e4f-41979A02D")
+ self.fritz._request.assert_called_with(
+ "http://10.0.0.1/webservices/homeautoswitch.lua",
+ {
+ "switchcmd": "settriggeractive",
+ "sid": "0000001",
+ "active": "0",
+ "ain": "trg303e4f-41979A02D",
+ },
+ )