From ffe847b25bef7ca4895c4ea08c393184675b86e9 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:47:35 -0400 Subject: [PATCH 1/3] Add support for lastSeen property --- test/conftest.py | 2 +- test/model/test_node.py | 22 +++++++++++++++++++++- test/test_client.py | 2 +- test/test_dump.py | 2 +- test/test_main.py | 2 +- zwave_js_server/const/__init__.py | 4 ++-- zwave_js_server/model/node/__init__.py | 9 +++++++++ zwave_js_server/model/node/data_model.py | 1 + zwave_js_server/model/node/statistics.py | 5 +++++ 9 files changed, 42 insertions(+), 7 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index b0a6f2c37..57e4ffca1 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -222,7 +222,7 @@ def version_data_fixture(): "serverVersion": "test_server_version", "homeId": "test_home_id", "minSchemaVersion": 0, - "maxSchemaVersion": 29, + "maxSchemaVersion": 30, } diff --git a/test/model/test_node.py b/test/model/test_node.py index 96c4b4221..f0367d8d5 100644 --- a/test/model/test_node.py +++ b/test/model/test_node.py @@ -1,7 +1,7 @@ """Test the node model.""" import json from copy import deepcopy -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import patch import pytest @@ -136,6 +136,26 @@ def test_from_state(client): ) assert node.endpoints[0] != node.endpoints[0].index assert hash(node.endpoints[0]) == hash((client.driver, node.node_id, 0)) + assert node.last_seen is None + event = Event( + "statistics updated", + { + "source": "node", + "event": "statistics updated", + "nodeId": node.node_id, + "statistics": { + "commandsTX": 1, + "commandsRX": 2, + "commandsDroppedTX": 3, + "commandsDroppedRX": 4, + "timeoutResponse": 5, + "rssi": 7, + "lastSeen": "2023-07-18T15:42:34.701Z", + }, + }, + ) + node.receive_event(event) + assert node.last_seen == datetime(2023, 7, 18, 15, 42, 34, 701000, timezone.utc) async def test_highest_security_value(lock_schlage_be469, ring_keypad): diff --git a/test/test_client.py b/test/test_client.py index fafa94dae..f5fbf38c5 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -454,7 +454,7 @@ async def test_additional_user_agent_components(client_session, url): { "command": "initialize", "messageId": "initialize", - "schemaVersion": 29, + "schemaVersion": 30, "additionalUserAgentComponents": { "zwave-js-server-python": __version__, "foo": "bar", diff --git a/test/test_dump.py b/test/test_dump.py index 71264b060..f43b440f5 100644 --- a/test/test_dump.py +++ b/test/test_dump.py @@ -105,7 +105,7 @@ async def test_dump_additional_user_agent_components( { "command": "initialize", "messageId": "initialize", - "schemaVersion": 29, + "schemaVersion": 30, "additionalUserAgentComponents": { "zwave-js-server-python": __version__, "foo": "bar", diff --git a/test/test_main.py b/test/test_main.py index 6413b8b63..f2495fcf3 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -55,7 +55,7 @@ def test_dump_state( assert captured.out == ( "{'type': 'version', 'driverVersion': 'test_driver_version', " "'serverVersion': 'test_server_version', 'homeId': 'test_home_id', " - "'minSchemaVersion': 0, 'maxSchemaVersion': 29}\n" + "'minSchemaVersion': 0, 'maxSchemaVersion': 30}\n" "{'type': 'result', 'success': True, 'result': {}, 'messageId': 'initialize'}\n" "test_result\n" ) diff --git a/zwave_js_server/const/__init__.py b/zwave_js_server/const/__init__.py index 7702cee67..1402ddf5e 100644 --- a/zwave_js_server/const/__init__.py +++ b/zwave_js_server/const/__init__.py @@ -8,9 +8,9 @@ __version__ = metadata.version(PACKAGE_NAME) # minimal server schema version we can handle -MIN_SERVER_SCHEMA_VERSION = 29 +MIN_SERVER_SCHEMA_VERSION = 30 # max server schema version we can handle (and our code is compatible with) -MAX_SERVER_SCHEMA_VERSION = 29 +MAX_SERVER_SCHEMA_VERSION = 30 VALUE_UNKNOWN = "unknown" diff --git a/zwave_js_server/model/node/__init__.py b/zwave_js_server/model/node/__init__.py index 0efda79eb..cd3c1f2da 100644 --- a/zwave_js_server/model/node/__init__.py +++ b/zwave_js_server/model/node/__init__.py @@ -347,6 +347,13 @@ def keep_awake(self) -> bool: """Return whether the node is set to keep awake.""" return self.data["keepAwake"] + @property + def last_seen(self) -> datetime | None: + """Return when the node was last seen.""" + if last_seen := self.data.get("lastSeen"): + return datetime.fromisoformat(last_seen) + return None + def update(self, data: NodeDataType) -> None: """Update the internal state data.""" self.data = copy.deepcopy(data) @@ -969,3 +976,5 @@ def handle_statistics_updated(self, event: Event) -> None: event.data["statistics_updated"] = self._statistics = NodeStatistics( self.client, statistics ) + if last_seen := statistics.get("lastSeen"): + self.data["lastSeen"] = last_seen diff --git a/zwave_js_server/model/node/data_model.py b/zwave_js_server/model/node/data_model.py index f0b352e16..4cae0aab2 100644 --- a/zwave_js_server/model/node/data_model.py +++ b/zwave_js_server/model/node/data_model.py @@ -62,3 +62,4 @@ class NodeDataType(TypedDict, total=False): statistics: NodeStatisticsDataType highestSecurityClass: int isControllerNode: bool + lastSeen: str diff --git a/zwave_js_server/model/node/statistics.py b/zwave_js_server/model/node/statistics.py index 67e20cddf..7a3c927df 100644 --- a/zwave_js_server/model/node/statistics.py +++ b/zwave_js_server/model/node/statistics.py @@ -3,6 +3,7 @@ from contextlib import suppress from dataclasses import dataclass, field +from datetime import datetime from typing import TYPE_CHECKING, TypedDict from zwave_js_server.exceptions import RssiErrorReceived @@ -27,6 +28,7 @@ class NodeStatisticsDataType(TypedDict, total=False): rssi: int lwr: RouteStatisticsDataType nlwr: RouteStatisticsDataType + lastSeen: str @dataclass @@ -43,6 +45,7 @@ class NodeStatistics: rtt: int | None = field(init=False) lwr: RouteStatistics | None = field(init=False, default=None) nlwr: RouteStatistics | None = field(init=False, default=None) + last_seen: datetime | None = field(init=False, default=None) def __post_init__(self) -> None: """Post initialize.""" @@ -52,6 +55,8 @@ def __post_init__(self) -> None: self.commands_dropped_tx = self.data["commandsDroppedTX"] self.timeout_response = self.data["timeoutResponse"] self.rtt = self.data.get("rtt") + if last_seen := self.data.get("lastSeen"): + self.last_seen = datetime.fromisoformat(last_seen) if lwr := self.data.get("lwr"): with suppress(ValueError): self.lwr = RouteStatistics(self.client, lwr) From e9b569ca2cb43815b7d316f10711dcabd1f47504 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 20 Jul 2023 02:28:06 -0400 Subject: [PATCH 2/3] backport fromisoformat --- requirements.txt | 1 + setup.py | 2 +- zwave_js_server/__init__.py | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7b1f71e9f..e4155500f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ aiohttp==3.8.5 pydantic==2.0.3 +backports-datetime-fromisoformat==2.0.0 diff --git a/setup.py b/setup.py index 217dcccb5..ca83a9f0d 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ packages=find_packages(exclude=["test.*", "test"]), package_data={"zwave_js_server": ["py.typed"]}, python_requires=">=3.11", - install_requires=["aiohttp>3", "pydantic>=1.10.0"], + install_requires=["aiohttp>3", "pydantic>=1.10.0", "backports-datetime-fromisoformat>=2.0.0"], entry_points={ "console_scripts": ["zwave-js-server-python = zwave_js_server.__main__:main"] }, diff --git a/zwave_js_server/__init__.py b/zwave_js_server/__init__.py index 404c5928c..48bef0568 100644 --- a/zwave_js_server/__init__.py +++ b/zwave_js_server/__init__.py @@ -1 +1,9 @@ """Provide a package for zwave-js-server.""" + +import sys + +# monkeypatch fromisoformat for Python 3.10 to support more formats +if sys.version_info < (3, 11): + from backports.datetime_fromisoformat import MonkeyPatch + + MonkeyPatch.patch_fromisoformat() From 82f4ce4e51f73c10106a207623956c8796ff43ff Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:59:41 -0400 Subject: [PATCH 3/3] Remove backport since we are dropping python 3.10 support --- requirements.txt | 1 - setup.py | 2 +- zwave_js_server/__init__.py | 8 -------- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index e4155500f..7b1f71e9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ aiohttp==3.8.5 pydantic==2.0.3 -backports-datetime-fromisoformat==2.0.0 diff --git a/setup.py b/setup.py index ca83a9f0d..217dcccb5 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ packages=find_packages(exclude=["test.*", "test"]), package_data={"zwave_js_server": ["py.typed"]}, python_requires=">=3.11", - install_requires=["aiohttp>3", "pydantic>=1.10.0", "backports-datetime-fromisoformat>=2.0.0"], + install_requires=["aiohttp>3", "pydantic>=1.10.0"], entry_points={ "console_scripts": ["zwave-js-server-python = zwave_js_server.__main__:main"] }, diff --git a/zwave_js_server/__init__.py b/zwave_js_server/__init__.py index 48bef0568..404c5928c 100644 --- a/zwave_js_server/__init__.py +++ b/zwave_js_server/__init__.py @@ -1,9 +1 @@ """Provide a package for zwave-js-server.""" - -import sys - -# monkeypatch fromisoformat for Python 3.10 to support more formats -if sys.version_info < (3, 11): - from backports.datetime_fromisoformat import MonkeyPatch - - MonkeyPatch.patch_fromisoformat()