-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 90a80ac
Showing
16 changed files
with
771 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: HACS Validate | ||
|
||
on: | ||
push: | ||
pull_request: | ||
schedule: | ||
- cron: "0 0 * * *" | ||
workflow_dispatch: | ||
|
||
jobs: | ||
validate-hacs: | ||
runs-on: "ubuntu-latest" | ||
steps: | ||
- uses: "actions/checkout@v3" | ||
- name: HACS validation | ||
uses: "hacs/action@main" | ||
with: | ||
category: "integration" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: All tests | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
schedule: | ||
- cron: '0 0 * * 0' | ||
workflow_dispatch: | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ubuntu-latest | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
python-version: ["3.11"] | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v3 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
- name: Install dependencies | ||
run: | | ||
pip install -r requirements_dev.txt | ||
- name: Test with pytest | ||
run: | | ||
pytest -p no:sugar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pyc | ||
__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# 1.0.0 | ||
|
||
* Initial release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# HKC Alarm Integration for Home Assistant | ||
|
||
This repository contains an unofficial Home Assistant integration for [HKC Alarm](https://www.hkcsecurity.com/) systems, allowing you to control and monitor your HKC Alarm directly from Home Assistant. | ||
|
||
## Installation | ||
|
||
You will need [HACS](https://hacs.xyz) installed in your Home Assistant server. Install the integration by installing this repository as a Custom Repository. Then, navigate to Integrations, Add an Integration and select HKC Alarm. You will then be asked to enter: | ||
|
||
* **Panel ID**: Your HKC Alarm Panel ID. | ||
* **Panel Password**: Your HKC Alarm Panel Password. | ||
* **Alarm Code**: Your HKC Alarm Code. | ||
* **Update Interval (seconds)**: (Optional) Custom update interval for fetching data from HKC Alarm. Default is 60 seconds. Recommend keeping this at 60s, as this is similar to the Mobile App's polling interval, and we want to respect HKC's API. | ||
|
||
[![Open your Home Assistant instance and add this integration](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=hkc_alarm) | ||
|
||
## Entities | ||
|
||
The integration updates data every minute by default. It exposes the following entities: | ||
|
||
* An alarm control panel entity representing the HKC Alarm system. | ||
* Sensor entities for each input on the HKC Alarm system. | ||
|
||
The *State* of the alarm control panel is either `armed_home`, `armed_away`, or `disarmed`. The sensor entities will have states `Open` or `Closed` based on the state of the corresponding input on the HKC Alarm system. | ||
|
||
## Sample Automation to notify about alarm state changes | ||
|
||
```yaml | ||
alias: HKC Alarm State Notifications | ||
description: "" | ||
trigger: | ||
- platform: state | ||
entity_id: alarm_control_panel.hkc_alarm_system | ||
condition: [] | ||
action: | ||
- service: notify.notify | ||
data: | ||
title: 🚨 HKC Alarm Notification 🚨 | ||
message: > | ||
Alarm System is now {{ states('alarm_control_panel.hkc_alarm_system') }} | ||
mode: single | ||
``` | ||
## Troubleshooting | ||
If you encounter issues with this integration, you can enable debug logging for this component by adding the following lines to your `configuration.yaml` file: | ||
|
||
```yaml | ||
logger: | ||
logs: | ||
custom_components.hkc_alarm: debug | ||
``` | ||
|
||
This will produce detailed debug logs which can help in diagnosing the problem. | ||
|
||
## Links | ||
|
||
- [Github Repository](https://github.com/jasonmadigan/pyhkc) | ||
- [HKC Alarm PyPi Package](https://pypi.org/project/pyhkc/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from pyhkc.hkc_api import HKCAlarm | ||
from datetime import timedelta | ||
from homeassistant.core import callback | ||
from .const import DOMAIN, DEFAULT_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL | ||
|
||
async def async_setup_entry(hass, entry): | ||
panel_id = entry.data["panel_id"] | ||
panel_password = entry.data["panel_password"] | ||
user_code = entry.data["user_code"] | ||
|
||
hkc_alarm = await hass.async_add_executor_job( | ||
HKCAlarm, panel_id, panel_password, user_code | ||
) | ||
|
||
# Get update interval from options, or use default | ||
update_interval = entry.options.get(CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL) | ||
SCAN_INTERVAL = timedelta(seconds=update_interval) | ||
|
||
# Create a dictionary to store both the HKCAlarm instance and SCAN_INTERVAL | ||
entry_data = { | ||
"hkc_alarm": hkc_alarm, | ||
"scan_interval": SCAN_INTERVAL | ||
} | ||
|
||
# Store the dictionary in hass.data | ||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = entry_data | ||
|
||
@callback | ||
def update_options(entry): | ||
"""Update options.""" | ||
nonlocal entry_data | ||
update_interval = entry.options.get(CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL) | ||
entry_data["scan_interval"] = timedelta(seconds=update_interval) | ||
|
||
entry.add_update_listener(update_options) | ||
|
||
# Load platforms | ||
await hass.config_entries.async_forward_entry_setups( | ||
entry, ["alarm_control_panel", "sensor"] | ||
) | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity | ||
from .const import DOMAIN | ||
from homeassistant.components.alarm_control_panel import ( | ||
AlarmControlPanelEntity, | ||
AlarmControlPanelEntityFeature, | ||
) | ||
from homeassistant.helpers.update_coordinator import ( | ||
DataUpdateCoordinator, | ||
CoordinatorEntity, | ||
UpdateFailed, | ||
) | ||
from datetime import timedelta | ||
from .const import DOMAIN, CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL | ||
|
||
import logging | ||
|
||
from datetime import timedelta | ||
from homeassistant.core import callback | ||
|
||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class HKCAlarmControlPanel(AlarmControlPanelEntity, CoordinatorEntity): | ||
_attr_supported_features = ( | ||
AlarmControlPanelEntityFeature.ARM_HOME | ||
| AlarmControlPanelEntityFeature.ARM_AWAY | ||
) | ||
|
||
def __init__(self, data, device_info, coordinator): | ||
AlarmControlPanelEntity.__init__(self) | ||
CoordinatorEntity.__init__(self, coordinator) | ||
self._hkc_alarm = data.get('hkc_alarm') | ||
self._scan_interval = data.get('scan_interval') | ||
self._device_info = device_info | ||
self._state = None | ||
self._panel_data = None | ||
|
||
@property | ||
def unique_id(self): | ||
"""Return the unique ID of the sensor.""" | ||
return self._hkc_alarm.panel_id + "panel" | ||
|
||
@property | ||
def extra_state_attributes(self): | ||
"""Return the state attributes of the alarm control panel.""" | ||
if self._panel_data is None: | ||
return None | ||
|
||
# Extract the desired attributes from self._panel_data | ||
attributes = { | ||
"Green LED": self._panel_data['greenLed'], | ||
"Red LED": self._panel_data['redLed'], | ||
"Amber LED": self._panel_data['amberLed'], | ||
"Cursor On": self._panel_data['cursorOn'], | ||
"Cursor Index": self._panel_data['cursorIndex'], | ||
"Display": self._panel_data['display'], | ||
"Blink": self._panel_data['blink'], | ||
} | ||
return attributes | ||
|
||
@property | ||
def device_info(self): | ||
return { | ||
"identifiers": {(DOMAIN, self._hkc_alarm.panel_id)}, | ||
"name": "HKC Alarm System", | ||
"manufacturer": "HKC", | ||
"model": "HKC Alarm", | ||
"sw_version": "1.0.0", | ||
} | ||
|
||
@property | ||
def state(self): | ||
return self._state | ||
|
||
@property | ||
def name(self): | ||
return "HKC Alarm System" | ||
|
||
@property | ||
def available(self) -> bool: | ||
"""Return True if alarm is available.""" | ||
return self._panel_data is not None and "display" in self._panel_data | ||
|
||
async def async_alarm_disarm(self, code: str | None = None) -> None: | ||
"""Send disarm command.""" | ||
await self.hass.async_add_executor_job(self._hkc_alarm.disarm) | ||
|
||
async def async_alarm_arm_home(self, code: str | None = None) -> None: | ||
"""Send arm home command.""" | ||
await self.hass.async_add_executor_job(self._hkc_alarm.arm_partset_a) | ||
|
||
async def async_alarm_arm_away(self, code: str | None = None) -> None: | ||
"""Send arm away command.""" | ||
await self.hass.async_add_executor_job(self._hkc_alarm.arm_fullset) | ||
|
||
@callback | ||
def _handle_coordinator_update(self) -> None: | ||
"""Handle updated data from the coordinator.""" | ||
_logger.error("self.coordinator.data") | ||
_logger.error(self.coordinator.data) | ||
status, panel_data = self.coordinator.data | ||
self._panel_data = panel_data | ||
blocks = status.get("blocks", []) | ||
|
||
if any(block["armState"] == 3 for block in blocks): | ||
self._state = "armed_away" | ||
elif any(block["armState"] == 1 for block in blocks): | ||
self._state = "armed_home" | ||
else: | ||
self._state = "disarmed" | ||
self.async_write_ha_state() # Update the state with the latest data | ||
|
||
|
||
async def async_setup_entry(hass, entry, async_add_entities): | ||
hkc_alarm = hass.data[DOMAIN][entry.entry_id] | ||
update_interval = entry.data.get(CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL) | ||
|
||
async def _async_fetch_data(): | ||
try: | ||
hkc_alarm = hass.data[DOMAIN][entry.entry_id]["hkc_alarm"] | ||
status = await hass.async_add_executor_job(hkc_alarm.get_system_status) | ||
panel_data = await hass.async_add_executor_job(hkc_alarm.get_panel) | ||
return status, panel_data | ||
except Exception as e: | ||
_logger.error(f"Exception occurred while fetching data: {e}") | ||
raise UpdateFailed(f"Failed to update: {e}") | ||
|
||
|
||
coordinator = DataUpdateCoordinator( | ||
hass, | ||
_logger, | ||
name="hkc_alarm_data", | ||
update_method=_async_fetch_data, | ||
update_interval=timedelta(seconds=update_interval), | ||
) | ||
|
||
await coordinator.async_config_entry_first_refresh() | ||
|
||
device_info = { | ||
"identifiers": {(DOMAIN, entry.entry_id)}, | ||
"name": "HKC Alarm System", | ||
"manufacturer": "HKC", | ||
} | ||
async_add_entities( | ||
[HKCAlarmControlPanel(hkc_alarm, device_info, coordinator)], | ||
True, | ||
) |
Oops, something went wrong.