Skip to content

Commit

Permalink
Initial release v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonmadigan committed Oct 28, 2023
0 parents commit d6f205e
Show file tree
Hide file tree
Showing 16 changed files with 771 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/hacs_validate.yaml
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"
32 changes: 32 additions & 0 deletions .github/workflows/tests.yaml
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.10", "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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pyc
__pycache__
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 1.0.0

* Initial release
59 changes: 59 additions & 0 deletions README.md
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/)
41 changes: 41 additions & 0 deletions custom_components/hkc_alarm/__init__.py
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
148 changes: 148 additions & 0 deletions custom_components/hkc_alarm/alarm_control_panel.py
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,
)
Loading

0 comments on commit d6f205e

Please sign in to comment.