Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions PyMyGekko/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from PyMyGekko.resources.AlarmsLogics import AlarmsLogicValueAccessor
from PyMyGekko.resources.Blinds import Blind
from PyMyGekko.resources.Blinds import BlindValueAccessor
from PyMyGekko.resources.Cams import Cam
from PyMyGekko.resources.Cams import CamValueAccessor
from PyMyGekko.resources.EnergyCosts import EnergyCost
from PyMyGekko.resources.EnergyCosts import EnergyCostValueAccessor
from PyMyGekko.resources.HotWaterSystems import HotWaterSystem
Expand Down Expand Up @@ -70,6 +72,7 @@ def __init__(
self._data_provider
)
self._blind_value_accessor = BlindValueAccessor(self._data_provider)
self._cam_value_accessor = CamValueAccessor(self._data_provider)
self._energy_costs_value_accessor = EnergyCostValueAccessor(self._data_provider)
self._hot_water_systems_value_accessor = HotWaterSystemValueAccessor(
self._data_provider
Expand Down Expand Up @@ -120,6 +123,10 @@ def get_blinds(self) -> list[Blind]:
"""Returns the MyGekko blinds"""
return self._blind_value_accessor.blinds

def get_cams(self) -> list[Cam]:
"""Returns the MyGekko cams"""
return self._cam_value_accessor.cams

def get_energy_costs(self) -> list[EnergyCost]:
"""Returns the MyGekko energy_costs"""
return self._energy_costs_value_accessor.energy_costs
Expand Down
107 changes: 107 additions & 0 deletions PyMyGekko/resources/Cams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""MyGekko Cams implementation"""
from __future__ import annotations

from enum import IntEnum

from PyMyGekko.data_provider import DataProviderBase
from PyMyGekko.data_provider import EntityValueAccessor
from PyMyGekko.resources import Entity


class Cam(Entity):
"""Class for MyGekko Cam"""

def __init__(
self, entity_id: str, name: str, value_accessor: CamValueAccessor
) -> None:
super().__init__(entity_id, name, "/cams/")
self._value_accessor = value_accessor
self._supported_features = self._value_accessor.get_features(self)

@property
def supported_features(self) -> list[CamFeature]:
"""Returns the supported features"""
return self._supported_features

@property
def image_url(self) -> str | None:
"""Returns the image url"""
return self._value_accessor.get_value(self, "imagepath")

@property
def stream_url(self) -> str | None:
"""Returns the stream url"""
return self._value_accessor.get_value(self, "streampath")


class CamNewRecordAvailableState(IntEnum):
"""MyGekko Cams Record Available State"""

NO = 0
YES = 1


class CamFeature(IntEnum):
"""MyGekko Cams Feature"""

ON_OFF = 0
STREAM = 1


class CamValueAccessor(EntityValueAccessor):
"""Cam value accessor"""

def __init__(self, data_provider: DataProviderBase):
self._data = {}
self._data_provider = data_provider
self._data_provider.subscribe(self)

def update_status(self, status, hardware):
if status is not None and "cams" in status:
cams = status["cams"]
for key in cams:
if key.startswith("item"):
if key not in self._data:
self._data[key] = {}

if "sumstate" in cams[key] and "value" in cams[key]["sumstate"]:
(
self._data[key]["newRecordsAvailableState"],
*_other,
) = cams[
key
]["sumstate"]["value"].split(
";",
)

def update_resources(self, resources):
if resources is not None and "cams" in resources:
cams = resources["cams"]
for key in cams:
if key.startswith("item"):
if key not in self._data:
self._data[key] = {}
self._data[key]["name"] = cams[key]["name"]
self._data[key]["imagepath"] = cams[key]["imagepath"]
self._data[key]["streampath"] = cams[key]["streampath"]

@property
def cams(self):
"""Returns the cams read from MyGekko"""
result: list[Cam] = []
for key, data in self._data.items():
result.append(Cam(key, data["name"], self))

return result

def get_features(self, door: Cam) -> list[CamFeature]:
"""Returns the supported features"""
result = list()

if door and door.entity_id:
if door.entity_id in self._data:
data = self._data[door.entity_id]
if "streampath" in data and data["streampath"]:
result.append(CamFeature.STREAM)

return result
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ hatch build
### Release

- Increase version, then

```
hatch build
hatch publish -u __token__ -a <PyPiToken>
hatch build
hatch publish -u __token__ -a <PyPiToken>
```

---
Expand Down
26 changes: 26 additions & 0 deletions tests/cams/data/api_var_response_879015.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"cams": {
"item2": {
"name": "DoorBird",
"page": "Eingang",
"imagepath": "http://XXX@192.168.1.50/bha-api/image.cgi",
"streampath": "http://XXX@192.168.1.50/bha-api/video.cgi",
"sumstate": {
"description": "Summary of the states and their formats",
"format": "newRecordsAvailableState enum[0=no,1=yes](); ",
"type": "STRING",
"permission": "READ"
}
},
"group0": {
"name": "Grp 1",
"sumstate": {
"description": "Summary of the group states and their formats",
"format": "State[0=Off|1=On]",
"type": "AO",
"permission": "READ",
"index": 7500000
}
}
}
}
30 changes: 30 additions & 0 deletions tests/cams/data/api_var_status_response_879015.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"globals": {
"network": {
"gekkoname": {
"value": "myGEKKO"
},
"language": {
"value": "0"
},
"version": {
"value": "879015"
},
"hardware": {
"value": "Slide 2 (AC0DFE3012E6)"
}
}
},
"cams": {
"item2": {
"sumstate": {
"value": "0;"
}
},
"group0": {
"sumstate": {
"value": "1"
}
}
}
}
55 changes: 55 additions & 0 deletions tests/cams/test_pymygekko_cams_879015.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import logging

import pytest
from aiohttp import ClientSession
from aiohttp import web
from PyMyGekko import MyGekkoApiClientBase
from PyMyGekko.resources.Cams import CamFeature

_LOGGER: logging.Logger = logging.getLogger(__name__)


async def var_response(request):
varResponseFile = open("tests/cams/data/api_var_response_879015.json")
return web.Response(status=200, body=varResponseFile.read())


async def var_status_response(request):
statusResponseFile = open("tests/cams/data/api_var_status_response_879015.json")
return web.Response(status=200, body=statusResponseFile.read())


@pytest.fixture
def mock_server(aiohttp_server):
app = web.Application()
app.router.add_get("/api/v1/var", var_response)
app.router.add_get("/api/v1/var/status", var_status_response)
return aiohttp_server(app)


@pytest.mark.asyncio
async def test_get_cams(mock_server):
_LOGGER.setLevel(logging.DEBUG)

server = await mock_server
async with ClientSession() as session:
api = MyGekkoApiClientBase(
{},
session,
scheme=server.scheme,
host=server.host,
port=server.port,
)

await api.read_data()
cams = api.get_cams()

assert cams is not None
assert len(cams) == 1

assert cams[0].entity_id == "item2"
assert cams[0].name == "DoorBird"
assert cams[0].image_url == "http://XXX@192.168.1.50/bha-api/image.cgi"
assert cams[0].stream_url == "http://XXX@192.168.1.50/bha-api/video.cgi"
assert len(cams[0].supported_features) == 1
assert CamFeature.STREAM in cams[0].supported_features