Add zwave_js WS API commands to get statistics (#53393)

* Add zwave_js WS API commands to get statistics

* update function name

* move nested functions to top level functions
pull/53508/head
Raman Gupta 2021-07-26 11:59:16 -04:00 committed by GitHub
parent a6331d85ed
commit 4b189bd8c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 336 additions and 12 deletions

View File

@ -19,13 +19,14 @@ from zwave_js_server.exceptions import (
SetValueFailed,
)
from zwave_js_server.firmware import begin_firmware_update
from zwave_js_server.model.controller import ControllerStatistics
from zwave_js_server.model.firmware import (
FirmwareUpdateFinished,
FirmwareUpdateProgress,
)
from zwave_js_server.model.log_config import LogConfig
from zwave_js_server.model.log_message import LogMessage
from zwave_js_server.model.node import Node
from zwave_js_server.model.node import Node, NodeStatistics
from zwave_js_server.util.node import async_set_config_parameter
from homeassistant.components import websocket_api
@ -200,6 +201,10 @@ def async_register_api(hass: HomeAssistant) -> None:
)
websocket_api.async_register_command(hass, websocket_check_for_config_updates)
websocket_api.async_register_command(hass, websocket_install_config_update)
websocket_api.async_register_command(
hass, websocket_subscribe_controller_statistics
)
websocket_api.async_register_command(hass, websocket_subscribe_node_statistics)
hass.http.register_view(DumpView())
hass.http.register_view(FirmwareUploadView())
@ -1332,6 +1337,16 @@ async def websocket_abort_firmware_update(
connection.send_result(msg[ID])
def _get_firmware_update_progress_dict(
progress: FirmwareUpdateProgress,
) -> dict[str, int]:
"""Get a dictionary of firmware update progress."""
return {
"sent_fragments": progress.sent_fragments,
"total_fragments": progress.total_fragments,
}
@websocket_api.require_admin
@websocket_api.websocket_command(
{
@ -1348,7 +1363,7 @@ async def websocket_subscribe_firmware_update_status(
msg: dict,
node: Node,
) -> None:
"""Subsribe to the status of a firmware update."""
"""Subscribe to the status of a firmware update."""
@callback
def async_cleanup() -> None:
@ -1364,8 +1379,7 @@ async def websocket_subscribe_firmware_update_status(
msg[ID],
{
"event": event["event"],
"sent_fragments": progress.sent_fragments,
"total_fragments": progress.total_fragments,
**_get_firmware_update_progress_dict(progress),
},
)
)
@ -1390,15 +1404,10 @@ async def websocket_subscribe_firmware_update_status(
]
connection.subscriptions[msg["id"]] = async_cleanup
result = (
{
"sent_fragments": node.firmware_update_progress.sent_fragments,
"total_fragments": node.firmware_update_progress.total_fragments,
}
if node.firmware_update_progress
else None
progress = node.firmware_update_progress
connection.send_result(
msg[ID], _get_firmware_update_progress_dict(progress) if progress else None
)
connection.send_result(msg[ID], result)
class FirmwareUploadView(HomeAssistantView):
@ -1495,3 +1504,126 @@ async def websocket_install_config_update(
"""Check for config updates."""
success = await client.driver.async_install_config_update()
connection.send_result(msg[ID], success)
def _get_controller_statistics_dict(
statistics: ControllerStatistics,
) -> dict[str, int]:
"""Get dictionary of controller statistics."""
return {
"messages_tx": statistics.messages_tx,
"messages_rx": statistics.messages_rx,
"messages_dropped_tx": statistics.messages_dropped_tx,
"messages_dropped_rx": statistics.messages_dropped_rx,
"nak": statistics.nak,
"can": statistics.can,
"timeout_ack": statistics.timeout_ack,
"timout_response": statistics.timeout_response,
"timeout_callback": statistics.timeout_callback,
}
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required(TYPE): "zwave_js/subscribe_controller_statistics",
vol.Required(ENTRY_ID): str,
}
)
@websocket_api.async_response
@async_get_entry
async def websocket_subscribe_controller_statistics(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict,
entry: ConfigEntry,
client: Client,
) -> None:
"""Subsribe to the statistics updates for a controller."""
@callback
def async_cleanup() -> None:
"""Remove signal listeners."""
for unsub in unsubs:
unsub()
@callback
def forward_stats(event: dict) -> None:
statistics: ControllerStatistics = event["statistics_updated"]
connection.send_message(
websocket_api.event_message(
msg[ID],
{
"event": event["event"],
"source": "controller",
**_get_controller_statistics_dict(statistics),
},
)
)
controller = client.driver.controller
msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("statistics updated", forward_stats)
]
connection.subscriptions[msg["id"]] = async_cleanup
connection.send_result(
msg[ID], _get_controller_statistics_dict(controller.statistics)
)
def _get_node_statistics_dict(statistics: NodeStatistics) -> dict[str, int]:
"""Get dictionary of node statistics."""
return {
"commands_tx": statistics.commands_tx,
"commands_rx": statistics.commands_rx,
"commands_dropped_tx": statistics.commands_dropped_tx,
"commands_dropped_rx": statistics.commands_dropped_rx,
"timeout_response": statistics.timeout_response,
}
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required(TYPE): "zwave_js/subscribe_node_statistics",
vol.Required(ENTRY_ID): str,
vol.Required(NODE_ID): int,
}
)
@websocket_api.async_response
@async_get_node
async def websocket_subscribe_node_statistics(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict,
node: Node,
) -> None:
"""Subsribe to the statistics updates for a node."""
@callback
def async_cleanup() -> None:
"""Remove signal listeners."""
for unsub in unsubs:
unsub()
@callback
def forward_stats(event: dict) -> None:
statistics: NodeStatistics = event["statistics_updated"]
connection.send_message(
websocket_api.event_message(
msg[ID],
{
"event": event["event"],
"source": "node",
"node_id": node.node_id,
**_get_node_statistics_dict(statistics),
},
)
)
msg[DATA_UNSUBSCRIBE] = unsubs = [node.on("statistics updated", forward_stats)]
connection.subscriptions[msg["id"]] = async_cleanup
connection.send_result(msg[ID], _get_node_statistics_dict(node.statistics))

View File

@ -2703,3 +2703,195 @@ async def test_install_config_update(hass, client, integration, hass_ws_client):
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
async def test_subscribe_controller_statistics(
hass, integration, client, hass_ws_client
):
"""Test the subscribe_controller_statistics command."""
entry = integration
ws_client = await hass_ws_client(hass)
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_controller_statistics",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == {
"messages_tx": 0,
"messages_rx": 0,
"messages_dropped_tx": 0,
"messages_dropped_rx": 0,
"nak": 0,
"can": 0,
"timeout_ack": 0,
"timout_response": 0,
"timeout_callback": 0,
}
# Fire statistics updated
event = Event(
"statistics updated",
{
"source": "controller",
"event": "statistics updated",
"statistics": {
"messagesTX": 1,
"messagesRX": 1,
"messagesDroppedTX": 1,
"messagesDroppedRX": 1,
"NAK": 1,
"CAN": 1,
"timeoutACK": 1,
"timeoutResponse": 1,
"timeoutCallback": 1,
},
},
)
client.driver.controller.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "statistics updated",
"source": "controller",
"messages_tx": 1,
"messages_rx": 1,
"messages_dropped_tx": 1,
"messages_dropped_rx": 1,
"nak": 1,
"can": 1,
"timeout_ack": 1,
"timout_response": 1,
"timeout_callback": 1,
}
# Test sending command with improper entry ID fails
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/subscribe_controller_statistics",
ENTRY_ID: "fake_entry_id",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/subscribe_controller_statistics",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_subscribe_node_statistics(
hass, multisensor_6, integration, client, hass_ws_client
):
"""Test the subscribe_node_statistics command."""
entry = integration
ws_client = await hass_ws_client(hass)
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_node_statistics",
ENTRY_ID: entry.entry_id,
NODE_ID: multisensor_6.node_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == {
"commands_tx": 0,
"commands_rx": 0,
"commands_dropped_tx": 0,
"commands_dropped_rx": 0,
"timeout_response": 0,
}
# Fire statistics updated
event = Event(
"statistics updated",
{
"source": "node",
"event": "statistics updated",
"nodeId": multisensor_6.node_id,
"statistics": {
"commandsTX": 1,
"commandsRX": 1,
"commandsDroppedTX": 1,
"commandsDroppedRX": 1,
"timeoutResponse": 1,
},
},
)
client.driver.controller.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "statistics updated",
"source": "node",
"node_id": multisensor_6.node_id,
"commands_tx": 1,
"commands_rx": 1,
"commands_dropped_tx": 1,
"commands_dropped_rx": 1,
"timeout_response": 1,
}
# Test sending command with improper entry ID fails
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/subscribe_node_statistics",
ENTRY_ID: "fake_entry_id",
NODE_ID: multisensor_6.node_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test sending command with improper node ID fails
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/subscribe_node_statistics",
ENTRY_ID: entry.entry_id,
NODE_ID: multisensor_6.node_id + 100,
}
)
msg = await ws_client.receive_json()
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/subscribe_node_statistics",
ENTRY_ID: entry.entry_id,
NODE_ID: multisensor_6.node_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED