diff --git a/homeassistant/components/http/data_validator.py b/homeassistant/components/http/data_validator.py index 8f3e9a3e1e2..d63912360a2 100644 --- a/homeassistant/components/http/data_validator.py +++ b/homeassistant/components/http/data_validator.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) class RequestDataValidator: """Decorator that will validate the incoming data. - Takes in a voluptuous schema and adds 'post_data' as + Takes in a voluptuous schema and adds 'data' as keyword argument to the function call. Will return a 400 if no JSON provided or doesn't match schema. diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index b768e7a4465..debd688671c 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -14,6 +14,7 @@ from homeassistant.helpers import device_registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send +from .api import async_register_api from .const import ( DATA_CLIENT, DATA_UNSUBSCRIBE, @@ -22,7 +23,6 @@ from .const import ( PLATFORMS, ) from .discovery import async_discover_values -from .websocket_api import async_register_api LOGGER = logging.getLogger(__name__) CONNECT_TIMEOUT = 10 diff --git a/homeassistant/components/zwave_js/websocket_api.py b/homeassistant/components/zwave_js/api.py similarity index 86% rename from homeassistant/components/zwave_js/websocket_api.py rename to homeassistant/components/zwave_js/api.py index cb412875e1e..b85d63a8912 100644 --- a/homeassistant/components/zwave_js/websocket_api.py +++ b/homeassistant/components/zwave_js/api.py @@ -1,15 +1,20 @@ """Websocket API for Z-Wave JS.""" - +import json import logging +from aiohttp import hdrs, web, web_exceptions import voluptuous as vol +from zwave_js_server import dump from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode from homeassistant.components import websocket_api +from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.websocket_api.connection import ActiveConnection +from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -32,6 +37,7 @@ def async_register_api(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, websocket_stop_inclusion) websocket_api.async_register_command(hass, websocket_remove_node) websocket_api.async_register_command(hass, websocket_stop_exclusion) + hass.http.register_view(DumpView) # type: ignore @websocket_api.require_admin @@ -54,7 +60,7 @@ def websocket_network_status( }, "controller": { "home_id": client.driver.controller.data["homeId"], - "node_count": len(client.driver.controller.nodes), + "nodes": list(client.driver.controller.nodes), }, } connection.send_result( @@ -278,3 +284,31 @@ async def remove_from_device_registry( return registry.async_remove_device(device.id) + + +class DumpView(HomeAssistantView): + """View to dump the state of the Z-Wave JS server.""" + + url = "/api/zwave_js/dump/{config_entry_id}" + name = "api:zwave_js:dump" + + async def get(self, request: web.Request, config_entry_id: str) -> web.Response: + """Dump the state of Z-Wave.""" + hass = request.app["hass"] + + if config_entry_id not in hass.data[DOMAIN]: + raise web_exceptions.HTTPBadRequest + + entry = hass.config_entries.async_get_entry(config_entry_id) + + msgs = await dump.dump_msgs( + entry.data[CONF_URL], async_get_clientsession(hass), wait_nodes_ready=False + ) + + return web.Response( + body="\n".join(json.dumps(msg) for msg in msgs) + "\n", + headers={ + hdrs.CONTENT_TYPE: "application/jsonl", + hdrs.CONTENT_DISPOSITION: 'attachment; filename="zwave_js_dump.jsonl"', + }, + ) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index fc98e8f3a6e..40224e55119 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -4,5 +4,6 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", "requirements": ["zwave-js-server-python==0.13.0"], - "codeowners": ["@home-assistant/z-wave"] + "codeowners": ["@home-assistant/z-wave"], + "dependencies": ["http", "websocket_api"] } diff --git a/tests/components/zwave_js/test_websocket_api.py b/tests/components/zwave_js/test_api.py similarity index 84% rename from tests/components/zwave_js/test_websocket_api.py rename to tests/components/zwave_js/test_api.py index e3167ae4ad9..88e8acc5771 100644 --- a/tests/components/zwave_js/test_websocket_api.py +++ b/tests/components/zwave_js/test_api.py @@ -1,8 +1,10 @@ """Test the Z-Wave JS Websocket API.""" +from unittest.mock import patch + from zwave_js_server.event import Event +from homeassistant.components.zwave_js.api import ENTRY_ID, ID, NODE_ID, TYPE from homeassistant.components.zwave_js.const import DOMAIN -from homeassistant.components.zwave_js.websocket_api import ENTRY_ID, ID, NODE_ID, TYPE from homeassistant.helpers.device_registry import async_get_registry @@ -151,3 +153,22 @@ async def test_remove_node( identifiers={(DOMAIN, "3245146787-67")}, ) assert device is None + + +async def test_dump_view(integration, hass_client): + """Test the HTTP dump view.""" + client = await hass_client() + with patch( + "zwave_js_server.dump.dump_msgs", + return_value=[{"hello": "world"}, {"second": "msg"}], + ): + resp = await client.get(f"/api/zwave_js/dump/{integration.entry_id}") + assert resp.status == 200 + assert await resp.text() == '{"hello": "world"}\n{"second": "msg"}\n' + + +async def test_dump_view_invalid_entry_id(integration, hass_client): + """Test an invalid config entry id parameter.""" + client = await hass_client() + resp = await client.get("/api/zwave_js/dump/INVALID") + assert resp.status == 400