Skip to content

Commit 2eae313

Browse files
committed
Added support for door intercoms.
1 parent 1303775 commit 2eae313

6 files changed

Lines changed: 332 additions & 4 deletions

File tree

PyMyGekko/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from PyMyGekko.resources.Blinds import BlindValueAccessor
1616
from PyMyGekko.resources.Cams import Cam
1717
from PyMyGekko.resources.Cams import CamValueAccessor
18+
from PyMyGekko.resources.DoorInterComs import DoorInterCom
19+
from PyMyGekko.resources.DoorInterComs import DoorInterComValueAccessor
1820
from PyMyGekko.resources.EnergyCosts import EnergyCost
1921
from PyMyGekko.resources.EnergyCosts import EnergyCostValueAccessor
2022
from PyMyGekko.resources.HotWaterSystems import HotWaterSystem
@@ -73,6 +75,9 @@ def __init__(
7375
)
7476
self._blind_value_accessor = BlindValueAccessor(self._data_provider)
7577
self._cam_value_accessor = CamValueAccessor(self._data_provider)
78+
self._door_inter_com_value_accessor = DoorInterComValueAccessor(
79+
self._data_provider
80+
)
7681
self._energy_costs_value_accessor = EnergyCostValueAccessor(self._data_provider)
7782
self._hot_water_systems_value_accessor = HotWaterSystemValueAccessor(
7883
self._data_provider
@@ -127,6 +132,10 @@ def get_cams(self) -> list[Cam]:
127132
"""Returns the MyGekko cams"""
128133
return self._cam_value_accessor.cams
129134

135+
def get_door_inter_coms(self) -> list[DoorInterCom]:
136+
"""Returns the MyGekko door inter coms"""
137+
return self._door_inter_com_value_accessor.door_inter_coms
138+
130139
def get_energy_costs(self) -> list[EnergyCost]:
131140
"""Returns the MyGekko energy_costs"""
132141
return self._energy_costs_value_accessor.energy_costs

PyMyGekko/resources/Cams.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,13 @@ def cams(self):
9494

9595
return result
9696

97-
def get_features(self, door: Cam) -> list[CamFeature]:
97+
def get_features(self, cam: Cam) -> list[CamFeature]:
9898
"""Returns the supported features"""
9999
result = list()
100100

101-
if door and door.entity_id:
102-
if door.entity_id in self._data:
103-
data = self._data[door.entity_id]
101+
if cam and cam.entity_id:
102+
if cam.entity_id in self._data:
103+
data = self._data[cam.entity_id]
104104
if "streampath" in data and data["streampath"]:
105105
result.append(CamFeature.STREAM)
106106

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
"""MyGekko DoorInterComs implementation"""
2+
from __future__ import annotations
3+
4+
from datetime import datetime
5+
from enum import IntEnum, StrEnum
6+
7+
from PyMyGekko.data_provider import DataProviderBase
8+
from PyMyGekko.data_provider import EntityValueAccessor
9+
from PyMyGekko.resources import Entity
10+
11+
12+
class DoorInterCom(Entity):
13+
"""Class for MyGekko DoorInterCom"""
14+
15+
def __init__(
16+
self, entity_id: str, name: str, value_accessor: DoorInterComValueAccessor
17+
) -> None:
18+
super().__init__(entity_id, name, "/door_intercom/")
19+
self._value_accessor = value_accessor
20+
self._supported_features = self._value_accessor.get_features(self)
21+
22+
@property
23+
def supported_features(self) -> list[DoorInterComFeature]:
24+
"""Returns the supported features"""
25+
return self._supported_features
26+
27+
@property
28+
def image_url(self) -> str | None:
29+
"""Returns the image url"""
30+
return self._value_accessor.get_value(self, "imagepath")
31+
32+
@property
33+
def stream_url(self) -> str | None:
34+
"""Returns the stream url"""
35+
return self._value_accessor.get_value(self, "streampath")
36+
37+
@property
38+
def sound_mode(self) -> DoorInterComSoundMode | None:
39+
"""Returns the sound mode"""
40+
value = self._value_accessor.get_value(self, "soundMode")
41+
return DoorInterComSoundMode(int(value)) if value is not None else None
42+
43+
@property
44+
def action_on_ring_state(self) -> DoorInterComActionOnRingState | None:
45+
"""Returns the action on ring state"""
46+
value = self._value_accessor.get_value(self, "actionOnRingState")
47+
return DoorInterComActionOnRingState(int(value)) if value is not None else None
48+
49+
@property
50+
def connection_state(self) -> DoorInterComConnectionState | None:
51+
"""Returns the connection state"""
52+
value = self._value_accessor.get_value(self, "connectionState")
53+
return DoorInterComConnectionState(int(value)) if value is not None else None
54+
55+
@property
56+
def missed_calls(self) -> int | None:
57+
"""Returns the missed calls"""
58+
value = self._value_accessor.get_value(self, "missedCallsValue")
59+
return int(value) if value else None
60+
61+
@property
62+
def last_missed_call_date(self) -> datetime | None:
63+
"""Returns the missed calls"""
64+
value = self._value_accessor.get_value(self, "lastMissedCallDate")
65+
return datetime.strptime(value, "%d.%m.%Y %H:%M:%S") if value else None
66+
67+
68+
class DoorInterComFeature(IntEnum):
69+
"""MyGekko Door Inter Com Feature"""
70+
71+
ON_OFF = 0
72+
STREAM = 1
73+
74+
75+
class DoorInterComSoundMode(IntEnum):
76+
"""MyGekko DoorInterCom Sound Mode"""
77+
78+
MUTE = 0
79+
RINGING = 1
80+
81+
82+
class DoorInterComActionOnRingState(IntEnum):
83+
"""MyGekko DoorInterCom Action on ring state"""
84+
85+
OFF = 0
86+
ON = 1
87+
88+
89+
class DoorInterComConnectionState(IntEnum):
90+
"""MyGekko DoorInterCom Connection state"""
91+
92+
ERROR_PROCESSING = -6
93+
ERROR_AUTHORIZATION = -5
94+
VOIP_NOT_ACTIVE = -4
95+
ERROR_FAV_CHECK = -3
96+
ERROR_PROVISIONING = -2
97+
ERROR_CONNECTION = -1
98+
NOT_SET_UP = (0,)
99+
OK = 1
100+
101+
102+
class DoorInterComCommand(StrEnum):
103+
"""MyGekko DoorInterCom Commands"""
104+
105+
OPEN = "O"
106+
RINGING = "M1"
107+
MUTE = "M0"
108+
ACTION_ON = "A1"
109+
ACTION_OFF = "A0"
110+
111+
112+
class DoorInterComValueAccessor(EntityValueAccessor):
113+
"""DoorInterCom value accessor"""
114+
115+
def __init__(self, data_provider: DataProviderBase):
116+
self._data = {}
117+
self._data_provider = data_provider
118+
self._data_provider.subscribe(self)
119+
120+
def update_status(self, status, hardware):
121+
if status is not None and "door_intercom" in status:
122+
door_inter_coms = status["door_intercom"]
123+
for key in door_inter_coms:
124+
if key.startswith("item"):
125+
if key not in self._data:
126+
self._data[key] = {}
127+
128+
if (
129+
"sumstate" in door_inter_coms[key]
130+
and "value" in door_inter_coms[key]["sumstate"]
131+
):
132+
(
133+
self._data[key]["soundMode"],
134+
self._data[key]["actionOnRingState"],
135+
self._data[key]["connectionState"],
136+
self._data[key]["missedCallsValue"],
137+
self._data[key]["lastMissedCallDate"],
138+
*_other,
139+
) = door_inter_coms[key]["sumstate"]["value"].split(
140+
";",
141+
)
142+
143+
def update_resources(self, resources):
144+
if resources is not None and "door_intercom" in resources:
145+
door_inter_coms = resources["door_intercom"]
146+
for key in door_inter_coms:
147+
if key.startswith("item"):
148+
if key not in self._data:
149+
self._data[key] = {}
150+
self._data[key]["name"] = door_inter_coms[key]["name"]
151+
self._data[key]["imagepath"] = door_inter_coms[key].get(
152+
"imagepath", None
153+
)
154+
self._data[key]["streampath"] = door_inter_coms[key].get(
155+
"streampath", None
156+
)
157+
158+
@property
159+
def door_inter_coms(self):
160+
"""Returns the door intercoms read from MyGekko"""
161+
result: list[DoorInterCom] = []
162+
for key, data in self._data.items():
163+
result.append(DoorInterCom(key, data["name"], self))
164+
165+
return result
166+
167+
def get_features(self, door_inter_com: DoorInterCom) -> list[DoorInterComFeature]:
168+
"""Returns the supported features"""
169+
result = list()
170+
171+
if door_inter_com and door_inter_com.entity_id:
172+
if door_inter_com.entity_id in self._data:
173+
data = self._data[door_inter_com.entity_id]
174+
if "streampath" in data and data["streampath"]:
175+
result.append(DoorInterComFeature.STREAM)
176+
177+
return result
178+
179+
async def send_command(
180+
self, door_inter_com: DoorInterCom, state: DoorInterComCommand
181+
) -> None:
182+
"""Sends the command"""
183+
if door_inter_com and door_inter_com.entity_id:
184+
await self._data_provider.write_data(
185+
door_inter_com.resource_path, str(state)
186+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"door_intercom": {
3+
"item1": {
4+
"name": "2n",
5+
"page": "2N Video",
6+
"imagepath": "http://XXX.XXX.10.XXX/api/camera/snapshot?width=640&height=480",
7+
"streampath": "http://XXX.XXX.10.XXX/api/camera/snapshot?width=640&height=480&fps=15",
8+
"sumstate": {
9+
"description": "Summary of the states and their formats",
10+
"format": "soundMode enum[0=mute,1=ringing](); actionOnRingState enum[0=off,1=on]; connectionState enum[-6=errorProcessing,-5=errorAuthorization,-4=voIPNotActive,-3=errorFavCheck,-2=errorProvisioning,-1=errorConnection,0=notSet,1=ok](); missedCallsValue int[0,65535](); lastMissedCallDate string[dd.mm.yyyy hh:mm:ss](); ",
11+
"type": "STRING",
12+
"permission": "READ"
13+
},
14+
"scmd": {
15+
"description": "Summary of the commands and their formats",
16+
"format": "O|M1|M0|A1|A0 (Open|Ringing|Mute|ActionOn|ActionOff)",
17+
"type": "STRING",
18+
"permission": "WRITE",
19+
"index": 2100170
20+
}
21+
},
22+
"group0": {
23+
"name": "Grp 1",
24+
"sumstate": {
25+
"description": "Summary of the group states and their formats",
26+
"format": "State[0=Off|1=On]",
27+
"type": "AO",
28+
"permission": "READ",
29+
"index": 21000000
30+
}
31+
}
32+
}
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"globals": {
3+
"network": {
4+
"gekkoname": {
5+
"value": "myGEKKO"
6+
},
7+
"language": {
8+
"value": "0"
9+
},
10+
"version": {
11+
"value": "879015"
12+
},
13+
"hardware": {
14+
"value": "Slide 2 (AC0DFE3012E6)"
15+
}
16+
}
17+
},
18+
"door_intercom": {
19+
"item1": {
20+
"sumstate": {
21+
"value": "1;;1;0;;"
22+
}
23+
},
24+
"group0": {
25+
"sumstate": {
26+
"value": "1"
27+
}
28+
}
29+
}
30+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import logging
2+
3+
from PyMyGekko.resources.DoorInterComs import (
4+
DoorInterComActionOnRingState,
5+
DoorInterComConnectionState,
6+
)
7+
8+
import pytest
9+
from aiohttp import ClientSession
10+
from aiohttp import web
11+
from datetime import datetime
12+
from PyMyGekko import MyGekkoApiClientBase
13+
14+
_LOGGER: logging.Logger = logging.getLogger(__name__)
15+
16+
17+
async def var_response(request):
18+
varResponseFile = open("tests/door_inter_coms/data/api_var_response_879015.json")
19+
return web.Response(status=200, body=varResponseFile.read())
20+
21+
22+
async def var_status_response(request):
23+
statusResponseFile = open(
24+
"tests/door_inter_coms/data/api_var_status_response_879015.json"
25+
)
26+
return web.Response(status=200, body=statusResponseFile.read())
27+
28+
29+
@pytest.fixture
30+
def mock_server(aiohttp_server):
31+
app = web.Application()
32+
app.router.add_get("/api/v1/var", var_response)
33+
app.router.add_get("/api/v1/var/status", var_status_response)
34+
return aiohttp_server(app)
35+
36+
37+
@pytest.mark.asyncio
38+
async def test_get_door_inter_coms(mock_server):
39+
_LOGGER.setLevel(logging.DEBUG)
40+
41+
server = await mock_server
42+
async with ClientSession() as session:
43+
api = MyGekkoApiClientBase(
44+
{},
45+
session,
46+
scheme=server.scheme,
47+
host=server.host,
48+
port=server.port,
49+
)
50+
51+
await api.read_data()
52+
door_inter_coms = api.get_door_inter_coms()
53+
54+
assert door_inter_coms is not None
55+
assert len(door_inter_coms) == 1
56+
57+
assert door_inter_coms[0].entity_id == "item1"
58+
assert door_inter_coms[0].name == "2n"
59+
assert (
60+
door_inter_coms[0].image_url
61+
== "http://XXX.XXX.10.XXX/api/camera/snapshot?width=640&height=480"
62+
)
63+
assert (
64+
door_inter_coms[0].stream_url
65+
== "http://XXX.XXX.10.XXX/api/camera/snapshot?width=640&height=480&fps=15"
66+
)
67+
assert door_inter_coms[0].action_on_ring_state == None
68+
assert door_inter_coms[0].connection_state == DoorInterComConnectionState.OK
69+
assert door_inter_coms[0].last_missed_call_date == None
70+
assert door_inter_coms[0].missed_calls == 0

0 commit comments

Comments
 (0)