Skip to content

Commit ca12b5c

Browse files
authored
Merge pull request #279 from plugwise/mdi_switch
Implement switch events
2 parents 298816d + 00f0e62 commit ca12b5c

9 files changed

Lines changed: 252 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
## Versions from 0.4x
44

5-
### v0.55.1 - 2025-07-06
5+
### v0.55.3 - 2025-07-08
6+
7+
- Bump plugwise to [v0.44.7](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.44.7)
8+
- Implement switch events
9+
10+
### v0.55.2 - 2025-07-06
611

712
- Bump plugwise to [v0.44.6](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.44.6)
813
- Implement check on initialized to allow entities to be generated for nodes which are temporary offline

custom_components/plugwise_usb/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
Platform.SENSOR,
2929
Platform.SWITCH,
3030
Platform.BUTTON,
31+
Platform.EVENT,
3132
]
3233
CONF_USB_PATH: Final[str] = "usb_path"
3334
SERVICE_DISABLE_PRODUCTION: Final[str] = "disable_production"

custom_components/plugwise_usb/entity.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,13 @@ def device_info(self) -> DeviceInfo:
7070
async def async_added_to_hass(self):
7171
"""Subscribe for push updates."""
7272
await super().async_added_to_hass()
73-
push_features = tuple(
74-
push_feature
75-
for push_feature in PUSHING_FEATURES
76-
if push_feature in self._node_info.features
77-
)
78-
# Subscribe to events
79-
if push_features:
73+
if (
74+
self.entity_description.node_feature in PUSHING_FEATURES
75+
and self.entity_description.node_feature in self._node_info.features
76+
):
8077
self.unsubscribe_push_events = self._subscribe_to_feature_fn(
8178
self.async_push_event,
82-
push_features,
79+
self.entity_description.node_feature,
8380
)
8481

8582
async def async_push_event(self, feature: NodeFeature, state: Any) -> None:
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""Plugwise USB Event component for HomeAssistant."""
2+
3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass
6+
from datetime import timedelta
7+
import logging
8+
9+
from plugwise_usb.api import NodeEvent, NodeFeature
10+
11+
from homeassistant.components.event import (
12+
EventDeviceClass,
13+
EventEntity,
14+
EventEntityDescription,
15+
)
16+
from homeassistant.const import EntityCategory, Platform, UnitOfTime
17+
from homeassistant.core import HomeAssistant, callback
18+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
19+
20+
from .const import NODES, STICK, UNSUB_NODE_LOADED
21+
from .coordinator import PlugwiseUSBConfigEntry, PlugwiseUSBDataUpdateCoordinator
22+
from .entity import PlugwiseUSBEntity, PlugwiseUSBEntityDescription
23+
24+
_LOGGER = logging.getLogger(__name__)
25+
PARALLEL_UPDATES = 2
26+
SCAN_INTERVAL = timedelta(seconds=30)
27+
28+
29+
@dataclass(kw_only=True)
30+
class PlugwiseEventEntityDescription(
31+
PlugwiseUSBEntityDescription, EventEntityDescription
32+
):
33+
"""Describes Plugwise Event entity."""
34+
35+
api_attribute: str = ""
36+
37+
38+
EVENT_TYPES: tuple[PlugwiseEventEntityDescription, ...] = (
39+
PlugwiseEventEntityDescription(
40+
key="button_press_i_group_1",
41+
translation_key="button_press_i_group_1",
42+
node_feature=NodeFeature.SWITCH,
43+
device_class=EventDeviceClass.BUTTON,
44+
api_attribute="state",
45+
event_types=["single_press"],
46+
),
47+
PlugwiseEventEntityDescription(
48+
key="button_press_o_group_1",
49+
translation_key="button_press_o_group_1",
50+
node_feature=NodeFeature.SWITCH,
51+
device_class=EventDeviceClass.BUTTON,
52+
api_attribute="state",
53+
event_types=["single_press"],
54+
),
55+
PlugwiseEventEntityDescription(
56+
key="button_press_i_group_2",
57+
translation_key="button_press_i_group_2",
58+
node_feature=NodeFeature.SWITCH,
59+
device_class=EventDeviceClass.BUTTON,
60+
api_attribute="state",
61+
event_types=["single_press"],
62+
),
63+
PlugwiseEventEntityDescription(
64+
key="button_press_o_group_2",
65+
translation_key="button_press_o_group_2",
66+
node_feature=NodeFeature.SWITCH,
67+
device_class=EventDeviceClass.BUTTON,
68+
api_attribute="state",
69+
event_types=["single_press"],
70+
),
71+
)
72+
73+
74+
async def async_setup_entry(
75+
_hass: HomeAssistant,
76+
config_entry: PlugwiseUSBConfigEntry,
77+
async_add_entities: AddEntitiesCallback,
78+
) -> None:
79+
"""Set up the USB Event from a config entry."""
80+
async def async_add_event(node_event: NodeEvent, mac: str) -> None:
81+
"""Initialize DUC for event."""
82+
if node_event != NodeEvent.LOADED:
83+
return
84+
entities: list[PlugwiseUSBEntity] = []
85+
if (node_duc := config_entry.runtime_data[NODES].get(mac)) is not None:
86+
_LOGGER.debug("Add event entities for %s | duc=%s", mac, node_duc.name)
87+
entities.extend(
88+
[
89+
PlugwiseUSBEventEntity(node_duc, entity_description)
90+
for entity_description in EVENT_TYPES
91+
if entity_description.node_feature in node_duc.node.features
92+
]
93+
)
94+
if entities:
95+
async_add_entities(entities, update_before_add=True)
96+
97+
api_stick = config_entry.runtime_data[STICK]
98+
99+
# Listen for loaded nodes
100+
config_entry.runtime_data[Platform.EVENT] = {}
101+
config_entry.runtime_data[Platform.EVENT][UNSUB_NODE_LOADED] = (
102+
api_stick.subscribe_to_node_events(
103+
async_add_event,
104+
(NodeEvent.LOADED,),
105+
)
106+
)
107+
108+
# load any current nodes
109+
for mac, node in api_stick.nodes.items():
110+
if node.is_loaded:
111+
await async_add_event(NodeEvent.LOADED, mac)
112+
113+
114+
async def async_unload_entry(
115+
config_entry: PlugwiseUSBConfigEntry,
116+
) -> None:
117+
"""Unload a config entry."""
118+
config_entry.runtime_data[Platform.EVENT][UNSUB_NODE_LOADED]()
119+
120+
121+
class PlugwiseUSBEventEntity(PlugwiseUSBEntity, EventEntity):
122+
"""Representation of a Plugwise USB Data Update Coordinator Event entity."""
123+
124+
def __init__(
125+
self,
126+
node_duc: PlugwiseUSBDataUpdateCoordinator,
127+
entity_description: PlugwiseEventEntityDescription,
128+
) -> None:
129+
"""Initialize a event entity."""
130+
super().__init__(node_duc, entity_description)
131+
132+
@callback
133+
def _handle_coordinator_update(self) -> None:
134+
"""Handle updated data from the coordinator."""
135+
data = self.coordinator.data.get(self.entity_description.node_feature, None)
136+
if data is None:
137+
_LOGGER.debug(
138+
"No %s event data for %s",
139+
self.entity_description.node_feature,
140+
self._node_info.mac,
141+
)
142+
return
143+
# SWITCH logic
144+
state_value = getattr(data, "state" )
145+
group_value = getattr(data, "group" )
146+
match self.entity_description.key:
147+
case "button_press_i_group_1":
148+
if state_value is True and group_value == 1:
149+
self._trigger_event(self.entity_description.event_types[0])
150+
self.async_write_ha_state()
151+
return
152+
case "button_press_o_group_1":
153+
if state_value is False and group_value == 1:
154+
self._trigger_event(self.entity_description.event_types[0])
155+
self.async_write_ha_state()
156+
return
157+
case "button_press_i_group_2":
158+
if state_value is True and group_value == 2:
159+
self._trigger_event(self.entity_description.event_types[0])
160+
self.async_write_ha_state()
161+
return
162+
case "button_press_o_group_2":
163+
if state_value is False and group_value == 2:
164+
self._trigger_event(self.entity_description.event_types[0])
165+
self.async_write_ha_state()
166+
return
167+

custom_components/plugwise_usb/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"integration_type": "hub",
99
"iot_class": "local_polling",
1010
"loggers": ["plugwise_usb"],
11-
"requirements": ["plugwise-usb==0.44.6"],
12-
"version": "0.55.2"
11+
"requirements": ["plugwise-usb==0.44.7"],
12+
"version": "0.55.3"
1313
}

custom_components/plugwise_usb/strings.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,48 @@
184184
"energy_reset":{
185185
"name": "Reset energy collection"
186186
}
187+
},
188+
"event": {
189+
"button_press_i_group_1": {
190+
"name": "Button I Pressed Group 1",
191+
"state_attributes": {
192+
"event_type": {
193+
"state": {
194+
"single_press": "Pressed"
195+
}
196+
}
197+
}
198+
},
199+
"button_press_o_group_1": {
200+
"name": "Button O Pressed Group 1",
201+
"state_attributes": {
202+
"event_type": {
203+
"state": {
204+
"single_press": "Pressed"
205+
}
206+
}
207+
}
208+
},
209+
"button_press_i_group_2": {
210+
"name": "Button I Pressed Group 2",
211+
"state_attributes": {
212+
"event_type": {
213+
"state": {
214+
"single_press": "Pressed"
215+
}
216+
}
217+
}
218+
},
219+
"button_press_o_group_2": {
220+
"name": "Button O Pressed Group 2",
221+
"state_attributes": {
222+
"event_type": {
223+
"state": {
224+
"single_press": "Pressed"
225+
}
226+
}
227+
}
228+
}
187229
}
188230
}
189231
}

custom_components/plugwise_usb/translations/en.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,20 @@
184184
"energy_reset":{
185185
"name": "Reset energy collection"
186186
}
187+
},
188+
"event": {
189+
"button_press_i_group_1": {
190+
"name": "Button I Group 1 Pressed"
191+
},
192+
"button_press_o_group_1": {
193+
"name": "Button O Group 1 Pressed"
194+
},
195+
"button_press_i_group_2": {
196+
"name": "Button I Group 2 Pressed"
197+
},
198+
"button_press_o_group_2": {
199+
"name": "Button O Group 2 Pressed"
200+
}
187201
}
188202
}
189203
}

custom_components/plugwise_usb/translations/nl.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,20 @@
184184
"energy_reset":{
185185
"name": "Energieverzameling resetten"
186186
}
187+
},
188+
"event": {
189+
"button_press_i_group_1": {
190+
"name": "Knop I Groep 1 gedrukt"
191+
},
192+
"button_press_o_group_1": {
193+
"name": "Knop O Groep 1 gedrukt"
194+
},
195+
"button_press_i_group_2": {
196+
"name": "Knop I Groep 2 gedrukt"
197+
},
198+
"button_press_o_group_2": {
199+
"name": "Knop O Groep 2 gedrukt"
200+
}
187201
}
188202
}
189203
}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "plugwise_usb-beta"
3-
version = "0.55.2"
3+
version = "0.55.3"
44
description = "Plugwise USB custom_component (BETA)"
55
readme = "README.md"
66
requires-python = ">=3.13"

0 commit comments

Comments
 (0)