Skip to content

Commit

Permalink
Feature/isapi request service (#227)
Browse files Browse the repository at this point in the history
* isapi_request action

* show action output

* blueprint for sensor state on video

* hassfest fix

* hassfest fix
  • Loading branch information
maciej-or authored Oct 23, 2024
1 parent 5db64a3 commit 483363c
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 4 deletions.
81 changes: 81 additions & 0 deletions blueprints/display_sensor_state_on_hikvision_video.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
blueprint:
name: Display Sensor State On Hikvision Video
description: |
Sets an overlay text on a Hikvision camera video.
domain: automation
input:
config_entry_id:
name: "Device"
description: "The Hikvision device."
selector:
config_entry:
integration: hikvision_next
camera_no:
name: "Camera"
description: "The camera number."
default: 1
selector:
number:
min: 1
max: 32
step: 1
mode: box
sensor_entity:
name: Sensor
description: "The sensor entity to display on the video."
selector:
entity:
domain: sensor
position_x:
name: "X"
description: "The X position of the overlay text."
default: 16
selector:
number:
min: 0
max: 1920
step: 1
position_y:
name: "Y"
description: "The Y position of the overlay text."
default: 570
selector:
number:
min: 0
max: 1080
step: 1
enabled:
name: "State"
description: "Enable or disable the overlay text."
default: true
selector:
boolean: {}
mode: single
variables:
entity_id: !input sensor_entity
camera_no: !input camera_no
position_x: !input position_x
position_y: !input position_y
enabled: !input enabled
trigger:
- platform: time_pattern
minutes: /15
action:
- service: hikvision_next.isapi_request
data:
method: PUT
config_entry_id: !input config_entry_id
path: "/System/Video/inputs/channels/{{camera_no}}/overlays/text"
payload: >
<?xml version="1.0" encoding="UTF-8"?>
<TextOverlayList version="2.0" xmlns="http://www.hikvision.com/ver20/XMLSchema">
<TextOverlay>
<id>1</id>
<enabled>{{ enabled | lower }}</enabled>
<positionX>{{ position_x }}</positionX>
<positionY>{{ position_y }}</positionY>
<displayText>{{ states(entity_id, with_unit=True) }}</displayText>
<directAngle></directAngle>
</TextOverlay>
</TextOverlayList>
response_variable: response
1 change: 1 addition & 0 deletions custom_components/hikvision_next/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

ATTR_CONFIG_ENTRY_ID = "config_entry_id"
ACTION_REBOOT = "reboot"
ACTION_ISAPI_REQUEST = "isapi_request"
ACTION_UPDATE_SNAPSHOT = "update_snapshot"

HIKVISION_EVENT = f"{DOMAIN}_event"
Expand Down
57 changes: 53 additions & 4 deletions custom_components/hikvision_next/services.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
"Integration actions."

from httpx import HTTPStatusError
import voluptuous as vol

from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
)
from homeassistant.exceptions import HomeAssistantError

from .const import ACTION_REBOOT, ATTR_CONFIG_ENTRY_ID, DATA_ISAPI, DOMAIN
from .const import (
ACTION_ISAPI_REQUEST,
ACTION_REBOOT,
ATTR_CONFIG_ENTRY_ID,
DATA_ISAPI,
DOMAIN,
)

ACTION_ISAPI_REQUEST_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
vol.Required("method"): str,
vol.Required("path"): str,
vol.Optional("payload"): str,
}
)


def setup_services(hass: HomeAssistant) -> None:
"""Set up the services for the Hikvision component."""

async def handle_reboot(call: ServiceCall):
"""Handle the service action call."""
"""Handle the reboot action call."""
entries = hass.data[DOMAIN]
entry_id = call.data.get(ATTR_CONFIG_ENTRY_ID)
isapi = entries[entry_id][DATA_ISAPI]
Expand All @@ -21,4 +42,32 @@ async def handle_reboot(call: ServiceCall):
except HTTPStatusError as ex:
raise HomeAssistantError(ex.response.content) from ex

hass.services.async_register(DOMAIN, ACTION_REBOOT, handle_reboot)
async def handle_isapi_request(call: ServiceCall) -> ServiceResponse:
"""Handle the custom ISAPI request action call."""
entries = hass.data[DOMAIN]
entry_id = call.data.get(ATTR_CONFIG_ENTRY_ID)
isapi = entries[entry_id][DATA_ISAPI]
method = call.data.get("method", "POST")
path = call.data["path"].strip("/")
payload = call.data.get("payload")
try:
response = await isapi.request(method, path, present="xml", data=payload)
except HTTPStatusError as ex:
if isinstance(ex.response.content, bytes):
response = ex.response.content.decode("utf-8")
else:
response = ex.response.content
return {"data": response.replace("\r", "")}

hass.services.async_register(
DOMAIN,
ACTION_REBOOT,
handle_reboot,
)
hass.services.async_register(
DOMAIN,
ACTION_ISAPI_REQUEST,
handle_isapi_request,
schema=ACTION_ISAPI_REQUEST_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
37 changes: 37 additions & 0 deletions custom_components/hikvision_next/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,40 @@ reboot:
selector:
config_entry:
integration: hikvision_next

isapi_request:
name: ISAPI request
description: "Send a custom ISAPI request to the device. WARNING: You do this at your own risk!"
fields:
config_entry_id:
required: true
name: "Device"
description: "The Hikvision device."
selector:
config_entry:
integration: hikvision_next
method:
required: true
name: "HTTP method"
description: "The HTTP method of the request."
default: GET
selector:
select:
mode: dropdown
options:
- GET
- POST
- PUT
path:
required: true
name: "ISAPI path"
description: "The ISAPI endpoint to request. Example: /System/deviceInfo"
selector:
text:
payload:
required: false
name: "Request payload"
description: "XML data"
selector:
text:
multiline: true

0 comments on commit 483363c

Please sign in to comment.