commit 97699c6c2df3c5928a8e3a2308cc6423da15e2de Author: Patrick Neff Date: Mon Jan 24 22:35:55 2022 +0100 initial commit diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..917577a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.formatting.provider": "black", + "python.linting.flake8Enabled": false, + "python.linting.pylintEnabled": true, + "python.linting.enabled": true +} diff --git a/custom_components/__init__.py b/custom_components/__init__.py new file mode 100644 index 0000000..56c29e7 --- /dev/null +++ b/custom_components/__init__.py @@ -0,0 +1,101 @@ +"""Support for ZoneMinder.""" +import logging +from time import sleep + +import voluptuous as vol +from zoneminder.zm import ZoneMinder + +from homeassistant.const import ( + ATTR_ID, + ATTR_NAME, + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PATH, + CONF_SSL, + CONF_TTL, + CONF_USERNAME, + CONF_VERIFY_SSL, + Platform, +) +from homeassistant.core import HomeAssistant, ServiceCall +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.typing import ConfigType +from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN + +_LOGGER = logging.getLogger(__name__) +DOMAIN = "zoneminder_alarm" + +DELAY = 10 + +SERVICE_TRIGGER_ALARM_STATE = "trigger_alarm_state" +TRIGGER_ALARM_SCHEMA = vol.Schema( + {vol.Required(ATTR_ID): cv.string, vol.Required(ATTR_NAME): cv.string} +) + +ALARM_COMMAND_ON = "on" +ALARM_COMMAND_OFF = "off" +ALARM_COMMAND_STATUS = "status" +ALARM_STATUS_URL = "api/monitors/alarm/id:{monitor_id}/command:{command}.json" + + +def setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the ZoneMinder component.""" + hass.data.setdefault(DOMAIN, {}) + + for conf in config[DOMAIN]: + zm_id = conf.get(CONF_HOST) + hass.data[DOMAIN].setdefault(zm_id, {}) + + def trigger_alarm_state(call: ServiceCall) -> None: + zm_id = call.data[ATTR_NAME] + monitor_id = call.data[ATTR_ID] + client = hass.data[ZONEMINDER_DOMAIN][zm_id] + + def get_alarm_status(): + result = client.get_state( + ALARM_STATUS_URL.format( + monitor_id=monitor_id, command=ALARM_COMMAND_STATUS + ) + ) + return int(result["status"]) + + def set_alarm_on(): + result = client.get_state( + ALARM_STATUS_URL.format(monitor_id=monitor_id, command=ALARM_COMMAND_ON) + ) + return result["status"] + + def set_alarm_off(): + result = client.get_state( + ALARM_STATUS_URL.format( + monitor_id=monitor_id, command=ALARM_COMMAND_OFF + ) + ) + return result["status"] + + if get_alarm_status() == 0: + set_alarm_on() + hass.data[DOMAIN][zm_id][monitor_id] = DELAY + + while hass.data[DOMAIN][zm_id][monitor_id] > 0: + sleep(1) + hass.data[DOMAIN][zm_id][monitor_id] -= 1 + + set_alarm_off() + else: + hass.data[DOMAIN][zm_id][monitor_id] += DELAY + + hass.services.register( + DOMAIN, + SERVICE_TRIGGER_ALARM_STATE, + trigger_alarm_state, + schema=TRIGGER_ALARM_SCHEMA, + ) + + platforms = (Platform.SWITCH, Platform.SENSOR) + for platform in platforms: + hass.async_create_task(async_load_platform(hass, platform, DOMAIN, {}, config)) + + return True diff --git a/custom_components/manifest.json b/custom_components/manifest.json new file mode 100644 index 0000000..7681f11 --- /dev/null +++ b/custom_components/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "zoneminder_alarm", + "name": "Zoneminder Alarm", + "documentation": "", + "dependencies": [], + "codeowners": [], + "requirements": [ + "zm-py" + ], + "iot_class": "local_polling", + "version": "0.1.0" +} diff --git a/custom_components/sensor.py b/custom_components/sensor.py new file mode 100644 index 0000000..56dd025 --- /dev/null +++ b/custom_components/sensor.py @@ -0,0 +1,70 @@ +"""Support for ZoneMinder switches.""" +from __future__ import annotations + +import logging + +from zoneminder.monitor import Monitor +from zoneminder.zm import ZoneMinder + +from homeassistant.components.sensor import ( + SensorEntity, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform( + hass: HomeAssistant, + config: ConfigType, # pylint: disable=unused-argument + add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, # pylint: disable=unused-argument +) -> None: + """Set up the ZoneMinder switch platform.""" + sensors = [] + for zm_client in hass.data[ZONEMINDER_DOMAIN].values(): + if not (monitors := zm_client.get_monitors()): + _LOGGER.warning("Could not fetch monitors from ZoneMinder") + return + + for monitor in monitors: + sensors.append(ZMSensorAlarm(zm_client, monitor)) + add_entities(sensors) + + +class ZMSensorAlarm(SensorEntity): + """Representation of a Sensor.""" + + def __init__(self, client: ZoneMinder, monitor: Monitor): + """Initialize the switch.""" + self._client = client + self._monitor = monitor + self._state = None + + @property + def name(self): + """Return the name of the switch.""" + return f"{self._monitor.name} Alarm Status" + + def update(self): + """Update the switch value.""" + data = self._get_data("status") + state = int(data["status"]) + if state == 0: + self._state = "Normal" + if state == 1: + self._state = "Warnung" + if state == 2: + self._state = "Alarm" + + @property + def native_value(self): + return self._state + + def _get_data(self, command: str): + return self._client.get_state( + f"api/monitors/alarm/id:{self._monitor.id}/command:{command}.json" + ) diff --git a/custom_components/services.yaml b/custom_components/services.yaml new file mode 100644 index 0000000..1d7d732 --- /dev/null +++ b/custom_components/services.yaml @@ -0,0 +1,15 @@ +trigger_alarm_state: + description: Trigger alarm in Zoneminder for monitor id. + fields: + name: + description: Zoneminder hostname + required: true + example: default + selector: + text: + id: + description: Monitor ID + required: true + example: 1 + selector: + text: diff --git a/custom_components/switch.py b/custom_components/switch.py new file mode 100644 index 0000000..9e4efad --- /dev/null +++ b/custom_components/switch.py @@ -0,0 +1,79 @@ +"""Support for ZoneMinder switches.""" +from __future__ import annotations + +import logging + +from zoneminder.monitor import Monitor +from zoneminder.zm import ZoneMinder + +from homeassistant.components.switch import SwitchEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform( + hass: HomeAssistant, + config: ConfigType, # pylint: disable=unused-argument + add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, # pylint: disable=unused-argument +) -> None: + """Set up the ZoneMinder switch platform.""" + switches = [] + for zm_client in hass.data[ZONEMINDER_DOMAIN].values(): + if not (monitors := zm_client.get_monitors()): + _LOGGER.warning("Could not fetch monitors from ZoneMinder") + return + + for monitor in monitors: + switches.append(ZMSwitchMonitors(zm_client, monitor)) + add_entities(switches) + + +class ZMSwitchMonitors(SwitchEntity): + """Representation of a ZoneMinder switch.""" + + icon = "mdi:alarm-bell" + + def __init__(self, client: ZoneMinder, monitor: Monitor): + """Initialize the switch.""" + self._client = client + self._monitor = monitor + self._state = None + + @property + def name(self): + """Return the name of the switch.""" + return f"{self._monitor.name} Alarm Status" + + def update(self): + """Update the switch value.""" + data = self._get_data("status") + self._state = int(data["status"]) > 0 + + def _get_data(self, command: str): + return self._client.get_state( + f"api/monitors/alarm/id:{self._monitor.id}/command:{command}.json" + ) + + @property + def is_on(self): + """Return True if entity is on.""" + return self._state + + def turn_on(self, **kwargs): + """Turn the entity on.""" + result = self._get_data("on") + data = self._get_data("status") + self._state = int(data["status"]) > 0 + return result + + def turn_off(self, **kwargs): + """Turn the entity off.""" + result = self._get_data("off") + data = self._get_data("status") + self._state = int(data["status"]) > 0 + return result