Skip to content

Commit

Permalink
Merge pull request #40 from Leggin/add-motion-sensor-functionality
Browse files Browse the repository at this point in the history
Add motion sensor functionality
based on @fdewes #38
  • Loading branch information
Leggin authored Jan 6, 2024
2 parents 52ce59e + b2473c7 commit 972abcd
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 1 deletion.
1 change: 0 additions & 1 deletion src/dirigera/devices/environment_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,4 @@ def set_name(self, name: str) -> None:
def dict_to_environment_sensor(
data: Dict[str, Any], dirigera_client: AbstractSmartHomeHub
) -> EnvironmentSensor:
print(data)
return EnvironmentSensor(dirigeraClient=dirigera_client, **data)
33 changes: 33 additions & 0 deletions src/dirigera/devices/motion_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from __future__ import annotations
from typing import Any, Dict
from .device import Attributes, Device
from ..hub.abstract_smart_home_hub import AbstractSmartHomeHub


class MotionSensorAttributes(Attributes):
battery_percentage: int
is_on: bool
light_level: float


class MotionSensor(Device):
dirigera_client: AbstractSmartHomeHub
attributes: MotionSensorAttributes

def reload(self) -> MotionSensor:
data = self.dirigera_client.get(route=f"/devices/{self.id}")
return MotionSensor(dirigeraClient=self.dirigera_client, **data)

def set_name(self, name: str) -> None:
if "customName" not in self.capabilities.can_receive:
raise AssertionError("This sensor does not support the set_name function")

data = [{"attributes": {"customName": name}}]
self.dirigera_client.patch(route=f"/devices/{self.id}", data=data)
self.attributes.custom_name = name


def dict_to_motion_sensor(
data: Dict[str, Any], dirigera_client: AbstractSmartHomeHub
) -> MotionSensor:
return MotionSensor(dirigeraClient=dirigera_client, **data)
27 changes: 27 additions & 0 deletions src/dirigera/hub/hub.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# pylint:disable=too-many-public-methods
import ssl
from typing import Any, Dict, List, Optional
import requests
import websocket # type: ignore
import urllib3
from requests import HTTPError
from urllib3.exceptions import InsecureRequestWarning

from ..devices.device import Device
from .abstract_smart_home_hub import AbstractSmartHomeHub
from ..devices.light import Light, dict_to_light
from ..devices.blinds import Blind, dict_to_blind
from ..devices.controller import Controller, dict_to_controller
from ..devices.outlet import Outlet, dict_to_outlet
from ..devices.environment_sensor import EnvironmentSensor, dict_to_environment_sensor
from ..devices.motion_sensor import MotionSensor, dict_to_motion_sensor
from ..devices.open_close_sensor import OpenCloseSensor, dict_to_open_close_sensor
from ..devices.scene import Scene, dict_to_scene

Expand Down Expand Up @@ -181,6 +185,14 @@ def get_environment_sensors(self) -> List[EnvironmentSensor]:
)
return [dict_to_environment_sensor(sensor, self) for sensor in sensors]

def get_motion_sensors(self) -> List[MotionSensor]:
"""
Fetches all motion sensors registered in the Hub
"""
devices = self.get("/devices")
sensors = list(filter(lambda x: x["deviceType"] == "motionSensor", devices))
return [dict_to_motion_sensor(sensor, self) for sensor in sensors]

def get_open_close_sensors(self) -> List[OpenCloseSensor]:
"""
Fetches all open/close sensors registered in the Hub
Expand Down Expand Up @@ -250,3 +262,18 @@ def get_scene_by_id(self, scene_id: str) -> Scene:
"""
data = self.get(f"/scenes/{scene_id}")
return dict_to_scene(data, self)

def get_all_devices(self) -> List[Device]:
"""
Fetches all devices registered in the Hub
"""
devices: List[Device] = []
devices.extend(self.get_blinds())
devices.extend(self.get_controllers())
devices.extend(self.get_environment_sensors())
devices.extend(self.get_lights())
devices.extend(self.get_motion_sensors())
devices.extend(self.get_open_close_sensors())
devices.extend(self.get_outlets())

return devices
123 changes: 123 additions & 0 deletions tests/test_motion_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from typing import Dict
import pytest
from src.dirigera.hub.abstract_smart_home_hub import FakeDirigeraHub
from src.dirigera.devices.motion_sensor import MotionSensor, dict_to_motion_sensor


@pytest.fixture(name="fake_client")
def fixture_fake_client() -> FakeDirigeraHub:
return FakeDirigeraHub()


@pytest.fixture(name="motion_sensor_dict")
def fixture_motion_sensor_dict() -> Dict:
return {
"id": "62e95143-c8b6-4f28-b581-adfd622c0db7_1",
"type": "sensor",
"deviceType": "motionSensor",
"createdAt": "2023-12-14T18:28:57.000Z",
"isReachable": True,
"lastSeen": "2023-12-14T17:30:48.000Z",
"attributes": {
"customName": "Bewegungssensor",
"firmwareVersion": "24.4.5",
"hardwareVersion": "1",
"manufacturer": "IKEA of Sweden",
"model": "TRADFRI motion sensor",
"productCode": "E1745",
"serialNumber": "142D51FFFE229101",
"batteryPercentage": 100,
"isOn": False,
"lightLevel": 1,
"permittingJoin": False,
"otaPolicy": "autoUpdate",
"otaProgress": 0,
"otaScheduleEnd": "00:00",
"otaScheduleStart": "00:00",
"otaState": "readyToCheck",
"otaStatus": "upToDate",
"sensorConfig": {
"scheduleOn": False,
"onDuration": 120,
"schedule": {
"onCondition": {"time": "sunset", "offset": -60},
"offCondition": {"time": "sunrise", "offset": 60},
},
},
"circadianPresets": [],
},
"capabilities": {
"canSend": ["isOn", "lightLevel"],
"canReceive": ["customName"],
},
"room": {
"id": "e1631a64-9ceb-4113-a6b3-1d866216503c",
"name": "Zimmer",
"color": "ikea_beige_1",
"icon": "rooms_arm_chair",
},
"deviceSet": [],
"remoteLinks": [],
"isHidden": False,
}


@pytest.fixture(name="fake_motion_sensor")
def fixture_blind(
motion_sensor_dict: Dict, fake_client: FakeDirigeraHub
) -> MotionSensor:
return MotionSensor(
dirigeraClient=fake_client,
**motion_sensor_dict,
)


def test_set_motion_sensor_name(
fake_motion_sensor: MotionSensor, fake_client: FakeDirigeraHub
) -> None:
new_name = "motion_sensor_name"
assert fake_motion_sensor.attributes.custom_name != new_name
fake_motion_sensor.set_name(new_name)
action = fake_client.patch_actions.pop()
assert action["route"] == f"/devices/{fake_motion_sensor.id}"
assert action["data"] == [{"attributes": {"customName": new_name}}]
assert fake_motion_sensor.attributes.custom_name == new_name


def test_dict_to_blind(motion_sensor_dict: Dict, fake_client: FakeDirigeraHub) -> None:
motion_sensor = dict_to_motion_sensor(motion_sensor_dict, fake_client)
assert motion_sensor.dirigera_client == fake_client
assert motion_sensor.id == motion_sensor_dict["id"]
assert motion_sensor.is_reachable == motion_sensor_dict["isReachable"]
assert (
motion_sensor.attributes.custom_name
== motion_sensor_dict["attributes"]["customName"]
)
assert (
motion_sensor.attributes.battery_percentage
== motion_sensor_dict["attributes"]["batteryPercentage"]
)
assert motion_sensor.attributes.is_on == motion_sensor_dict["attributes"]["isOn"]
assert (
motion_sensor.attributes.light_level
== motion_sensor_dict["attributes"]["lightLevel"]
)
assert (
motion_sensor.capabilities.can_receive
== motion_sensor_dict["capabilities"]["canReceive"]
)
assert motion_sensor.room.id == motion_sensor_dict["room"]["id"]
assert motion_sensor.room.name == motion_sensor_dict["room"]["name"]
assert (
motion_sensor.attributes.firmware_version
== motion_sensor_dict["attributes"]["firmwareVersion"]
)
assert (
motion_sensor.attributes.hardware_version
== motion_sensor_dict["attributes"]["hardwareVersion"]
)
assert motion_sensor.attributes.model == motion_sensor_dict["attributes"]["model"]
assert (
motion_sensor.attributes.manufacturer
== motion_sensor_dict["attributes"]["manufacturer"]
)

0 comments on commit 972abcd

Please sign in to comment.