Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/elkm1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"documentation": "https://www.home-assistant.io/integrations/elkm1",
"iot_class": "local_push",
"loggers": ["elkm1_lib"],
"quality_scale": "bronze",
"requirements": ["elkm1-lib==2.2.13"]
}
62 changes: 62 additions & 0 deletions homeassistant/components/elkm1/quality_scale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
rules:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we still have the import flow?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many entries wouldn't have a unique id at this point?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    def _keypad_changed(keypad: Element, changeset: dict[str, Any]) -> None:
        if (keypress := changeset.get("last_keypress")) is None:
            return

        hass.bus.async_fire(
            EVENT_ELKM1_KEYPAD_KEY_PRESSED,
            {
                ATTR_KEYPAD_NAME: keypad.name,
                ATTR_KEYPAD_ID: keypad.index + 1,
                ATTR_KEY_NAME: keypress[0],
                ATTR_KEY: keypress[1],
            },
        )

    for keypad in elk.keypads:
        keypad.add_callback(_keypad_changed)

This could become an event entity, making it more visible for the users

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    @property
    def code_format(self) -> CodeFormat | None:
        """Return the alarm code format."""
        return CodeFormat.NUMBER

Can be set to _attr_code_format

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    @property
    def alarm_state(self) -> AlarmControlPanelState | None:
        """Return the state of the element."""
        return self._state

Instead, set it to self._attr_alarm_state

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem for changed by

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem for _unique_id

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_brightness

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra_state_attributes -> Can these be more entities on its own?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class ElkSensor(ElkAttachedEntity, SensorEntity):
    """Base representation of Elk-M1 sensor."""

    _attr_native_value: str | None = None

    async def async_counter_refresh(self) -> None:
        """Refresh the value of a counter from the panel."""
        if not isinstance(self, ElkCounter):
            raise HomeAssistantError("supported only on ElkM1 Counter sensors")
        self._element.get()

    async def async_counter_set(self, value: int | None = None) -> None:
        """Set the value of a counter on the panel."""
        if not isinstance(self, ElkCounter):
            raise HomeAssistantError("supported only on ElkM1 Counter sensors")
        if value is not None:
            self._element.set(value)

    async def async_zone_bypass(self, code: int | None = None) -> None:
        """Bypass zone."""
        if not isinstance(self, ElkZone):
            raise HomeAssistantError("supported only on ElkM1 Zone sensors")
        if code is not None:
            self._element.bypass(code)

    async def async_zone_trigger(self) -> None:
        """Trigger zone."""
        if not isinstance(self, ElkZone):
            raise HomeAssistantError("supported only on ElkM1 Zone sensors")
        self._element.trigger()

Wouldn't it make more sense to make this a button entity for each sensor?

# Bronze
action-setup: done
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to put this in a common fixture

patch(
            "homeassistant.components.elkm1.async_setup_entry",
            return_value=True,
        ) as mock_setup_entry,

(and maybe the async_setup as well)

config-flow: done
dependency-transparency: done
docs-actions: done
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

            try:
                _included(conf[item]["include"], True, config[item]["included"])
                _included(conf[item]["exclude"], False, config[item]["included"])
            except (ValueError, vol.Invalid) as err:
                _LOGGER.error("Config item: %s; %s", item, err)
                return False

Instead of returning False, raise a ConfigEntryError or ConfigEntryNotReady when not recoverable or recoverable respectively

unique-config-entry: done

# Silver
action-exceptions: done
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    async def async_alarm_disarm(self, code: str | None = None) -> None:
        """Send disarm command."""
        if code is not None:
            self._element.disarm(int(code))

All alarm control panel methods are like this, but what happens when code is None? Is that realistic? If so, we should handle that error. Also, what happens if element.disarm fails?

config-entry-unloading: done
docs-configuration-parameters: done
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: todo
parallel-updates: todo
reauthentication-flow: todo
test-coverage: todo

# Gold
devices: done
diagnostics: todo
discovery-update-info: done
discovery: done
docs-data-update: todo
docs-examples: done
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: todo
dynamic-devices: todo
entity-category: done
entity-device-class: done
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ElkPanel ->

    def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
        if self._elk.is_connected():
            self._attr_native_value = "Paused" if self._elk.is_paused() else "Connected"
        else:
            self._attr_native_value = "Disconnected"

This can be an enum device class, where all the values are snake_case (would be a breaking change), but with all the options added to _attr_options. This way HA knows what the states can be

entity-disabled-by-default: todo
entity-translations: todo
exception-translations: todo
icon-translations: todo
reconfiguration-flow: done
repair-issues: todo
stale-devices: todo

# Platinum
async-dependency: done
inject-websession:
status: exempt
comment: Integration does not use HTTP/websession (uses elkm1_lib for Elk panel protocol).
strict-typing: done
23 changes: 23 additions & 0 deletions homeassistant/components/elkm1/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
"temperature_unit": "[%key:component::elkm1::config::step::manual_connection::data::temperature_unit%]",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"password": "[%key:component::elkm1::config::step::manual_connection::data_description::password%]",
"protocol": "[%key:component::elkm1::config::step::manual_connection::data_description::protocol%]",
"temperature_unit": "[%key:component::elkm1::config::step::manual_connection::data_description::temperature_unit%]",
"username": "[%key:component::elkm1::config::step::manual_connection::data_description::username%]"
},
"description": "Connect to the discovered system: {mac_address} ({host})",
"title": "[%key:component::elkm1::config::step::user::title%]"
},
Expand All @@ -36,6 +42,14 @@
"temperature_unit": "The temperature unit Elk-M1 uses.",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"address": "IP address, domain name, or serial device path. For serial use format 'tty[:baud]'.",
"password": "The password to authenticate with the Elk-M1 panel.",
"prefix": "Optional unique prefix to distinguish multiple Elk-M1 panels.",
"protocol": "Protocol to use when connecting to the Elk-M1 (secure, non-secure, serial, etc.).",
"temperature_unit": "Unit used by the Elk-M1 for temperature values (C or F).",
"username": "The username to authenticate with the Elk-M1 panel."
},
"description": "The address string must be in the form 'address[:port]' for 'secure' and 'non-secure'. Example: '192.168.1.1'. The port is optional and defaults to 2101 for 'non-secure' and 2601 for 'secure'. For the serial protocol, the address must be in the form 'tty[:baud]'. Example: '/dev/ttyS1'. The baud is optional and defaults to 115200.",
"title": "[%key:component::elkm1::config::step::user::title%]"
},
Expand All @@ -46,13 +60,22 @@
"protocol": "[%key:component::elkm1::config::step::manual_connection::data::protocol%]",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"address": "[%key:component::elkm1::config::step::manual_connection::data_description::address%]",
"password": "[%key:component::elkm1::config::step::manual_connection::data_description::password%]",
"protocol": "[%key:component::elkm1::config::step::manual_connection::data_description::protocol%]",
"username": "[%key:component::elkm1::config::step::manual_connection::data_description::username%]"
},
"description": "[%key:component::elkm1::config::step::manual_connection::description%]",
"title": "Reconfigure Elk-M1 Control"
},
"user": {
"data": {
"device": "[%key:common::config_flow::data::device%]"
},
"data_description": {
"device": "Select a discovered Elk-M1 control system to configure or choose Manual Entry to enter connection details."
},
"description": "Choose a discovered system or 'Manual Entry' if no devices have been discovered.",
"title": "Connect to Elk-M1 Control"
}
Expand Down
2 changes: 0 additions & 2 deletions script/hassfest/quality_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,6 @@ class Rule:
"eight_sleep",
"electrasmart",
"eliqonline",
"elkm1",
"elmax",
"elv",
"elvia",
Expand Down Expand Up @@ -1324,7 +1323,6 @@ class Rule:
"electrasmart",
"elevenlabs",
"eliqonline",
"elkm1",
"elmax",
"elgato",
"elv",
Expand Down
Loading