-
Notifications
You must be signed in to change notification settings - Fork 0
Bootstrap main components repo with crow_alarm_panel and uv dev environment #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
46b4b6a
Add components/crow_alarm_panel, pyproject.toml, test YAML, and updat…
Copilot 790b8c1
Apply suggestions from code review
dan-s-github 5184502
Make address required in crow_alarm_panel config schema
Copilot 68097cd
Update components/crow_alarm_panel/crow_alarm_panel.cpp
dan-s-github 5cae879
Add data size guard in ARMED_STATE handler to prevent out-of-bounds read
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -18,3 +18,7 @@ __pycache__/ | |
|
|
||
| # deployment secrets | ||
| /secrets.yaml | ||
|
|
||
| # uv / Python virtual environment | ||
| .venv/ | ||
| uv.lock | ||
This file contains hidden or 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 |
|---|---|---|
| @@ -1 +1,49 @@ | ||
| # my esphome-components | ||
| # esphome-components | ||
|
|
||
| Custom ESPHome components. The `components/` directory contains components that can be used with the ESPHome `external_components` feature. | ||
|
|
||
| ## Components | ||
|
|
||
| | Component | Description | | ||
| |-----------|-------------| | ||
| | [crow_alarm_panel](components/crow_alarm_panel/README.md) | Integration for Arrowhead Crow alarm panels via the keypad bus | | ||
|
|
||
| ## Local development with uv | ||
|
|
||
| [uv](https://docs.astral.sh/uv/) is used to manage the Python build environment so that components can be compiled and tested locally. | ||
|
|
||
| ### Setup | ||
|
|
||
| 1. Install uv: https://docs.astral.sh/uv/getting-started/installation/ | ||
| 2. Create and activate a virtual environment: | ||
| ```bash | ||
| uv sync | ||
| source .venv/bin/activate # Linux/macOS | ||
| # or | ||
| .venv\Scripts\activate # Windows | ||
| ``` | ||
|
|
||
| ### Validate / compile a component | ||
|
|
||
| A sample test configuration is provided at [`crow_alarm_panel_test.yaml`](crow_alarm_panel_test.yaml). Create a `secrets.yaml` file with your credentials (see ESPHome docs), then run: | ||
|
|
||
| ```bash | ||
| # Validate the configuration | ||
| esphome config crow_alarm_panel_test.yaml | ||
|
|
||
| # Compile the firmware | ||
| esphome compile crow_alarm_panel_test.yaml | ||
| ``` | ||
|
|
||
| ### Using components in your own ESPHome configuration | ||
|
|
||
| Reference the `components/` directory via `external_components`: | ||
|
|
||
| ```yaml | ||
| external_components: | ||
| - source: | ||
| type: git | ||
| url: https://github.com/dan-s-github/esphome-components | ||
| ref: main | ||
| components: [crow_alarm_panel] | ||
| ``` | ||
This file contains hidden or 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,25 @@ | ||
| # Crow Alarm Panel Component | ||
|
|
||
|
|
||
| This component allows reading and decoding most messages sent on the `keypad bus` of an Arrowhead Crow Alarm Panel. | ||
|
|
||
| It requires 2 wires to the panel, `data` and `clock`. These are usually marked `DAT` and `CLK`. | ||
|
|
||
|
|
||
| ## Example YAML | ||
|
|
||
| This example will just log every message it sees on the keypad bus. | ||
|
|
||
| ```yaml | ||
| crow_alarm_panel: | ||
| clock_pin: REPLACEME | ||
| data_pin: REPLACEME | ||
| address: 8 | ||
|
|
||
| on_message: | ||
| - logger.log: | ||
| format: "%02x - %s" | ||
| args: | ||
| - "type" | ||
| - "format_hex_pretty(data).c_str()" | ||
| ``` |
This file contains hidden or 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,69 @@ | ||
| from esphome import pins, automation | ||
| import esphome.codegen as cg | ||
| import esphome.config_validation as cv | ||
| from esphome.components import text_sensor, switch, alarm_control_panel as acp | ||
| from esphome.const import ( | ||
| CONF_ADDRESS, | ||
| CONF_ID, | ||
| CONF_CLOCK_PIN, | ||
| CONF_DATA_PIN, | ||
| CONF_NAME, | ||
| CONF_OUTPUTS, | ||
| ) | ||
|
|
||
| AUTO_LOAD = ["binary_sensor", "text_sensor", "switch", "button", "alarm_control_panel"] | ||
| MULTI_CONF = True | ||
|
|
||
| CONF_ARMED_STATE = "armed_state" | ||
| CONF_CROW_ALARM_PANEL_ID = "crow_alarm_panel_id" | ||
| CONF_NUM_ZONES = "number_of_zones" | ||
| CONF_KEYPADS = "keypads" | ||
| CONF_ON_MESSAGE = "on_message" | ||
|
|
||
| crow_alarm_panel_ns = cg.esphome_ns.namespace("crow_alarm_panel") | ||
|
|
||
| CrowAlarmPanel = crow_alarm_panel_ns.class_("CrowAlarmPanel", cg.Component) | ||
| CrowAlarmControlPanel = crow_alarm_panel_ns.class_( | ||
| "CrowAlarmControlPanel", acp.AlarmControlPanel, cg.Component | ||
| ) | ||
|
|
||
| CONFIG_SCHEMA = cv.Schema( | ||
| { | ||
| cv.GenerateID(): cv.declare_id(CrowAlarmPanel), | ||
| cv.Required(CONF_CLOCK_PIN): pins.internal_gpio_input_pin_schema, | ||
| cv.Required(CONF_DATA_PIN): pins.internal_gpio_input_pin_schema, | ||
| cv.Required(CONF_ADDRESS): cv.int_range(min=0, max=8), | ||
| cv.Optional(CONF_KEYPADS, default=[]): cv.ensure_list( | ||
| cv.Schema( | ||
| { | ||
| cv.Required(CONF_NAME): cv.string, | ||
| cv.Required(CONF_ADDRESS): cv.int_range(min=0, max=8), | ||
| } | ||
|
dan-s-github marked this conversation as resolved.
|
||
| ) | ||
| ), | ||
| cv.Optional(CONF_ON_MESSAGE): automation.validate_automation(single=True), | ||
| } | ||
| ).extend(cv.COMPONENT_SCHEMA) | ||
|
|
||
|
|
||
| async def to_code(config): | ||
| var = cg.new_Pvariable(config[CONF_ID]) | ||
| await cg.register_component(var, config) | ||
|
|
||
| clock_pin = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) | ||
| cg.add(var.set_clock_pin(clock_pin)) | ||
|
|
||
| data_pin = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) | ||
| cg.add(var.set_data_pin(data_pin)) | ||
|
|
||
| cg.add(var.set_keypad_address(config[CONF_ADDRESS])) | ||
|
|
||
| for keypad in config[CONF_KEYPADS]: | ||
| cg.add(var.add_keypad(keypad[CONF_NAME], keypad[CONF_ADDRESS])) | ||
|
|
||
| if CONF_ON_MESSAGE in config: | ||
| await automation.build_automation( | ||
| var.get_on_message_trigger(), | ||
| [(cg.uint8, "type"), (cg.std_vector.template(cg.uint8), "data")], | ||
| config[CONF_ON_MESSAGE], | ||
| ) | ||
39 changes: 39 additions & 0 deletions
39
components/crow_alarm_panel/alarm_control_panel/__init__.py
This file contains hidden or 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,39 @@ | ||
| import esphome.codegen as cg | ||
| import esphome.config_validation as cv | ||
| from esphome.components import alarm_control_panel as acp | ||
| from esphome.const import CONF_CODE, CONF_ID | ||
| from .. import CrowAlarmPanel, CrowAlarmControlPanel, CONF_CROW_ALARM_PANEL_ID | ||
|
|
||
| DEPENDENCIES = ["crow_alarm_panel"] | ||
|
|
||
| CONF_REQUIRE_CODE_TO_ARM = "requires_code_to_arm" | ||
| CONF_REQUIRE_CODE = "requires_code" | ||
|
|
||
| CONFIG_SCHEMA = ( | ||
| acp.alarm_control_panel_schema(CrowAlarmControlPanel) | ||
| .extend( | ||
| { | ||
| cv.GenerateID(): cv.declare_id(CrowAlarmControlPanel), | ||
| cv.GenerateID(CONF_CROW_ALARM_PANEL_ID): cv.use_id(CrowAlarmPanel), | ||
| cv.Optional(CONF_CODE): cv.string, | ||
| cv.Optional(CONF_REQUIRE_CODE_TO_ARM, default=False): cv.boolean, | ||
| cv.Optional(CONF_REQUIRE_CODE, default=True): cv.boolean, | ||
| } | ||
| ) | ||
| .extend(cv.COMPONENT_SCHEMA) | ||
| ) | ||
|
|
||
|
|
||
| async def to_code(config): | ||
| parent = await cg.get_variable(config[CONF_CROW_ALARM_PANEL_ID]) | ||
| var = await acp.new_alarm_control_panel(config) | ||
|
|
||
| cg.add(var.set_parent(parent)) | ||
| cg.add(parent.register_alarm_control_panel(var)) | ||
| cg.add(var.set_requires_code(config[CONF_REQUIRE_CODE])) | ||
| cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRE_CODE_TO_ARM])) | ||
|
|
||
| if CONF_CODE in config: | ||
| cg.add(var.set_code(config[CONF_CODE])) | ||
|
|
||
| await cg.register_component(var, config) |
This file contains hidden or 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 @@ | ||
| import esphome.codegen as cg | ||
| import esphome.config_validation as cv | ||
| from esphome.components import binary_sensor | ||
| from esphome.const import CONF_ID, CONF_TYPE | ||
| from .. import CrowAlarmPanel, CONF_CROW_ALARM_PANEL_ID | ||
|
|
||
| DEPENDENCIES = ["crow_alarm_panel"] | ||
|
|
||
| binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") | ||
| BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) | ||
|
|
||
| CONF_ZONE = "zone" | ||
| CONF_BYPASS = "bypass" | ||
|
|
||
| ZONE_SCHEMA = binary_sensor.binary_sensor_schema().extend( | ||
| { | ||
| cv.GenerateID(): cv.declare_id(BinarySensor), | ||
| cv.GenerateID(CONF_CROW_ALARM_PANEL_ID): cv.use_id(CrowAlarmPanel), | ||
| cv.Required(CONF_ZONE): cv.positive_int, | ||
| } | ||
| ).extend(cv.COMPONENT_SCHEMA) | ||
|
|
||
| CONFIG_SCHEMA = cv.typed_schema( | ||
| { | ||
| CONF_ZONE: ZONE_SCHEMA, | ||
| CONF_BYPASS: ZONE_SCHEMA, | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| def to_code(config): | ||
| paren = yield cg.get_variable(config[CONF_CROW_ALARM_PANEL_ID]) | ||
| type = config[CONF_TYPE] | ||
| var = cg.new_Pvariable(config[CONF_ID]) | ||
|
|
||
| yield binary_sensor.register_binary_sensor(var, config) | ||
|
|
||
| if type == "zone": | ||
| cg.add(paren.register_zone(var, config[CONF_ZONE])) | ||
| elif type == "bypass": | ||
| cg.add(paren.register_zone_bypass(var, config[CONF_ZONE])) |
This file contains hidden or 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,49 @@ | ||
| import esphome.codegen as cg | ||
| import esphome.config_validation as cv | ||
| from esphome.components import button | ||
| from esphome.const import CONF_ID, CONF_TYPE | ||
| from .. import crow_alarm_panel_ns, CrowAlarmPanel, CONF_CROW_ALARM_PANEL_ID | ||
|
|
||
| DEPENDENCIES = ["crow_alarm_panel"] | ||
|
|
||
| CrowAlarmPanelButton = crow_alarm_panel_ns.class_( | ||
| "CrowAlarmPanelButton", button.Button, cg.Component | ||
| ) | ||
|
|
||
| TYPES = ["arm_away", "arm_stay", "disarm"] | ||
|
|
||
| CONF_CODE = "code" | ||
|
|
||
|
|
||
| def _validate_disarm_code(value): | ||
| """Ensure that a code is provided for disarm buttons.""" | ||
| button_type = value.get(CONF_TYPE) | ||
| if button_type == "disarm": | ||
| code = value.get(CONF_CODE) | ||
| if not code: | ||
| raise cv.Invalid("For type 'disarm', a non-empty 'code' must be provided.") | ||
| return value | ||
|
|
||
|
|
||
| CONFIG_SCHEMA = cv.All( | ||
| button.button_schema(CrowAlarmPanelButton).extend( | ||
| { | ||
| cv.GenerateID(CONF_CROW_ALARM_PANEL_ID): cv.use_id(CrowAlarmPanel), | ||
| cv.Required(CONF_TYPE): cv.one_of(*TYPES, lower=True), | ||
| cv.Optional(CONF_CODE): cv.string, # Only needed for disarm | ||
| } | ||
| ).extend(cv.COMPONENT_SCHEMA), | ||
| _validate_disarm_code, | ||
| ) | ||
| async def to_code(config): | ||
| paren = await cg.get_variable(config[CONF_CROW_ALARM_PANEL_ID]) | ||
| var = cg.new_Pvariable(config[CONF_ID]) | ||
|
|
||
| await button.register_button(var, config) | ||
| await cg.register_component(var, config) | ||
|
|
||
| cg.add(var.set_parent(paren)) | ||
| cg.add(var.set_button_type(config[CONF_TYPE])) | ||
|
|
||
| if CONF_CODE in config: | ||
| cg.add(var.set_code(config[CONF_CODE])) |
50 changes: 50 additions & 0 deletions
50
components/crow_alarm_panel/button/crow_alarm_panel_button.h
This file contains hidden or 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,50 @@ | ||
| #pragma once | ||
|
|
||
| #include "esphome/components/button/button.h" | ||
| #include "esphome/core/component.h" | ||
| #include "esphome/core/log.h" | ||
| #include "../crow_alarm_panel.h" | ||
|
|
||
| namespace esphome { | ||
| namespace crow_alarm_panel { | ||
|
|
||
| class CrowAlarmPanelButton : public button::Button, public Component { | ||
| public: | ||
| void set_parent(CrowAlarmPanel *parent) { this->parent_ = parent; } | ||
| void set_button_type(const std::string &type) { this->button_type_ = type; } | ||
| void set_code(const std::string &code) { this->code_ = code; } | ||
|
|
||
| protected: | ||
| void press_action() override { | ||
| if (this->button_type_ == "arm_away") { | ||
| if (this->parent_->is_arm_in_progress()) { | ||
| ESP_LOGW("crow_alarm_panel.button", "Arm operation already in progress, ignoring button press"); | ||
| return; | ||
| } | ||
| this->parent_->arm_away(); | ||
| } else if (this->button_type_ == "arm_stay") { | ||
|
Comment on lines
+11
to
+25
|
||
| if (this->parent_->is_arm_in_progress()) { | ||
| ESP_LOGW("crow_alarm_panel.button", "Arm operation already in progress, ignoring button press"); | ||
| return; | ||
| } | ||
| this->parent_->arm_stay(); | ||
| } else if (this->button_type_ == "disarm") { | ||
| if (!this->parent_->is_armed()) { | ||
| ESP_LOGW("crow_alarm_panel.button", "Cannot disarm - alarm is not armed"); | ||
| return; | ||
| } | ||
| if (this->parent_->is_disarm_in_progress()) { | ||
| ESP_LOGW("crow_alarm_panel.button", "Disarm already in progress, ignoring button press"); | ||
| return; | ||
| } | ||
| this->parent_->disarm(this->code_); | ||
| } | ||
| } | ||
|
|
||
| CrowAlarmPanel *parent_; | ||
| std::string button_type_; | ||
| std::string code_; | ||
| }; | ||
|
|
||
| } // namespace crow_alarm_panel | ||
| } // namespace esphome | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Markdown table has an extra leading
|on the header/separator rows (|| ...), which prevents the table from rendering correctly. Replace||with a single|on those lines.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot I don't see an extra leading |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right — the table in
README.mdis correctly formatted with single|delimiters. The reviewer comment was incorrect; no change is needed here.