Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Versions from 0.4x

### v0.54.2

- Bump plugwise to [v0.44.2](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.44.3)
- Implement button to enable auto joining through CirclePlus device page

### v0.54.1 - 2025-06-11

- Implement solution for Issue #259 via plugwise to [v0.44.2](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.44.2)
Expand Down
39 changes: 0 additions & 39 deletions custom_components/plugwise_usb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
DOMAIN,
NODES,
PLUGWISE_USB_PLATFORMS,
SERVICE_AUTO_JOIN,
SERVICE_DISABLE_PRODUCTION,
SERVICE_ENABLE_PRODUCTION,
SERVICE_ENERGY_RESET,
Expand Down Expand Up @@ -112,23 +111,6 @@ async def async_node_discovered(node_event: NodeEvent, mac: str) -> None:
config_entry, PLUGWISE_USB_PLATFORMS
)

# Listen for entry updates
config_entry.async_on_unload(config_entry.add_update_listener(update_listener))

async def enable_auto_joining(call: ServiceCall) -> bool:
"""Enable auto-joining of devices to Plugwise zigbee network."""
if not api_stick.accept_join_request:
raise HomeAssistantError(
"Device joining is blocked, enable newly added entities"
+ " under System options for the Integration"
)

try:
result = await api_stick.set_accept_join_request(True)
except (NodeError, StickError) as exc:
raise HomeAssistantError(f"Failed to enable auto-joining: {exc}") from exc
return result

async def reset_energy_logs(call: ServiceCall) -> bool:
"""Reset energylog collection for a Node."""
mac = call.data[ATTR_MAC]
Expand Down Expand Up @@ -162,7 +144,6 @@ async def disable_production(call: ServiceCall) -> bool:
) from exc
return result

hass.services.async_register(DOMAIN, SERVICE_AUTO_JOIN, enable_auto_joining)
hass.services.async_register(
DOMAIN, SERVICE_ENABLE_PRODUCTION, enable_production, SERVICE_USB_DEVICE_SCHEMA
)
Expand All @@ -184,30 +165,10 @@ async def disable_production(call: ServiceCall) -> bool:
await asyncio.sleep(1)
if api_stick.network_discovered:
break
# Enable/disable automatic joining of available devices when the network is up
if config_entry.pref_disable_new_entities:
_LOGGER.debug("Configuring Circle + NOT to accept any new join requests")
try:
await api_stick.set_accept_join_request(False)
except StickError as exc:
raise HomeAssistantError(f"Disabling auto-joining failed: {exc}") from exc
else:
_LOGGER.debug("Configuring Circle + to automatically accept new join requests")
try:
await api_stick.set_accept_join_request(True)
except StickError as exc:
raise HomeAssistantError(f"Enabling auto-joining failed: {exc}") from exc

return True


async def update_listener(
hass: HomeAssistant, config_entry: PlugwiseUSBConfigEntry
) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(config_entry.entry_id)


async def async_unload_entry(
hass: HomeAssistant, config_entry: PlugwiseUSBConfigEntry
) -> bool:
Expand Down
115 changes: 115 additions & 0 deletions custom_components/plugwise_usb/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Plugwise USB Button component for HomeAssistant."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta
import logging

from homeassistant.components.button import (
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.const import EntityCategory, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from plugwise_usb.api import NodeEvent, NodeFeature

from .const import NODES, STICK, UNSUB_NODE_LOADED
from .coordinator import PlugwiseUSBConfigEntry, PlugwiseUSBDataUpdateCoordinator
from .entity import PlugwiseUSBEntity, PlugwiseUSBEntityDescription

_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 2
SCAN_INTERVAL = timedelta(seconds=30)


@dataclass(kw_only=True)
class PlugwiseButtonEntityDescription(
PlugwiseUSBEntityDescription, ButtonEntityDescription
):
"""Describes Plugwise button entity."""

async_button_fn: str = ""


BUTTON_TYPES: tuple[PlugwiseButtonEntityDescription, ...] = (
PlugwiseButtonEntityDescription(
key="enable_auto_join",
translation_key="enable_auto_join",
entity_category=EntityCategory.CONFIG,
async_button_fn="enable_auto_join",
node_feature=NodeFeature.CIRCLEPLUS,
),
)


async def async_setup_entry(
_hass: HomeAssistant,
config_entry: PlugwiseUSBConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the USB buttons from a config entry."""

async def async_add_button(node_event: NodeEvent, mac: str) -> None:
"""Initialize DUC for button."""
if node_event != NodeEvent.LOADED:
return
entities: list[PlugwiseUSBEntity] = []
if (node_duc := config_entry.runtime_data[NODES].get(mac)) is not None:
_LOGGER.debug("Add button entities for %s | duc=%s", mac, node_duc.name)
entities.extend(
[
PlugwiseUSBButtonEntity(node_duc, entity_description)
for entity_description in BUTTON_TYPES
if entity_description.node_feature in node_duc.node.features
]
)
if entities:
async_add_entities(entities)

api_stick = config_entry.runtime_data[STICK]

# Listen for loaded nodes
config_entry.runtime_data[Platform.BUTTON] = {}
config_entry.runtime_data[Platform.BUTTON][UNSUB_NODE_LOADED] = (
api_stick.subscribe_to_node_events(
async_add_button,
(NodeEvent.LOADED,),
)
)

# load any current nodes
for mac, node in api_stick.nodes.items():
if node.is_loaded:
await async_add_button(NodeEvent.LOADED, mac)


async def async_unload_entry(
_hass: HomeAssistant,
config_entry: PlugwiseUSBConfigEntry,
) -> None:
"""Unload a config entry."""
config_entry.runtime_data[Platform.BUTTON][UNSUB_NODE_LOADED]()


class PlugwiseUSBButtonEntity(PlugwiseUSBEntity, ButtonEntity):
"""Representation of a Plugwise USB Data Update Coordinator button."""

def __init__(
self,
node_duc: PlugwiseUSBDataUpdateCoordinator,
entity_description: PlugwiseButtonEntityDescription,
) -> None:
"""Initialize a button entity."""
super().__init__(node_duc, entity_description)
self.async_button_fn = getattr(
node_duc.node, entity_description.async_button_fn
)

async def async_press(self) -> None:
"""Button was pressed."""
await self.async_button_fn()

async def async_added_to_hass(self):
"""Subscribe for push updates."""
Comment on lines +114 to +115
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be removed, there's the same function in entity.py.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned on discord, the overload is required to prevent the execution of the function in entity.py as it breaks functioning of the circle+ as soon as the button is loaded.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah Ok, now I finally understand this, sorry.

2 changes: 1 addition & 1 deletion custom_components/plugwise_usb/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
Platform.BUTTON,
]
CONF_USB_PATH: Final[str] = "usb_path"
SERVICE_AUTO_JOIN: Final[str] = "enable_auto_joining"
SERVICE_DISABLE_PRODUCTION: Final[str] = "disable_production"
SERVICE_ENABLE_PRODUCTION: Final[str] = "enable_production"
SERVICE_ENERGY_RESET: Final[str] = "reset_energy_logs"
Expand Down
4 changes: 2 additions & 2 deletions custom_components/plugwise_usb/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["plugwise_usb"],
"requirements": ["plugwise-usb==0.44.2"],
"version": "0.54.1"
"requirements": ["plugwise-usb==0.44.3"],
"version": "0.54.2"
}
2 changes: 0 additions & 2 deletions custom_components/plugwise_usb/services.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
enable_auto_joining:
description: First enable auto-joining, then reset the Plugwise devices
enable_production:
description: "Enable production logging for a Plugwise USB node by MAC address"
fields:
Expand Down
9 changes: 5 additions & 4 deletions custom_components/plugwise_usb/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
}
},
"services": {
"enable_auto_joining": {
"name": "Enable Plugwise device auto-joining (temporarily)",
"description": "First enable this service then reset the Plugwise device(s) to join"
},
"enable_production":{
"name": "Enable production logging",
"description": "Enter the mac of the Node: (data = mac: 0123456789ABCDEF)",
Expand Down Expand Up @@ -190,6 +186,11 @@
"sed_clock_interval": {
"name": "Clock interval"
}
},
"button": {
"enable_auto_join": {
"name": "Enable Auto-joining (temporarily)"
}
}
}
}
9 changes: 5 additions & 4 deletions custom_components/plugwise_usb/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
}
},
"services": {
"enable_auto_joining": {
"name": "Enable Plugwise device auto-joining (temporarily)",
"description": "First enable this service then reset the Plugwise device(s) to join"
},
"enable_production":{
"name": "Enable production logging",
"description": "Enter the mac of the Node: (data = mac: 0123456789ABCDEF)",
Expand Down Expand Up @@ -190,6 +186,11 @@
"sed_clock_interval": {
"name": "Clock interval"
}
},
"button": {
"enable_auto_join": {
"name": "Enable Auto-joining (temporarily)"
}
}
}
}
9 changes: 5 additions & 4 deletions custom_components/plugwise_usb/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
}
},
"services": {
"enable_auto_joining": {
"name": "Zet Plugwise auto-toevoegen aan (tijdelijk)",
"description": "Voer eerst deze service uit en reset dan het toe te voegen Plugwise apparaat"
},
"enable_production":{
"name": "Zet productie-loggen aan",
"description": "Voer het mac-adres van de Node in: (data = mac: 0123456789ABSDEF)",
Expand Down Expand Up @@ -190,6 +186,11 @@
"sed_clock_interval": {
"name": "Klokinterval"
}
},
"button": {
"enable_auto_join": {
"name": "Zet Plugwise auto-toevoegen aan (tijdelijk)"
}
}
}
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "plugwise_usb-beta"
version = "0.54.0"
version = "0.54.2"
description = "Plugwise USB custom_component (BETA)"
readme = "README.md"
requires-python = ">=3.13"
Expand Down