Skip to content

Commit e5becfe

Browse files
authored
Add ELV-SH-WSM (#583) (#593)
1 parent 0a2323b commit e5becfe

File tree

9 files changed

+267
-15
lines changed

9 files changed

+267
-15
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Add IOptionalFeatureOperationDays with attribute operationDays
13+
- Add support for ELV-SH-WSM (Watering Actuator) (#583)
1314

1415
## [2.1.0](https://github.com/hahn-th/homematicip-rest-api/compare/2.0.7..2.1.0) 2025-07-15
1516

@@ -48,6 +49,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4849

4950
- Make websocket connection more robust against errors
5051

52+
### Added
53+
54+
- Add support for ELV-SH-WSM (Watering Actuator) (#583)
55+
5156
## [2.0.4] 2025-05-30
5257

5358
### Fixed

homematicip_demo/json_data/home.json

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14797,6 +14797,165 @@
1479714797
"serializedGlobalTradeItemNumber": "3014F7110000000000000WKP",
1479814798
"type": "WALL_MOUNTED_KEY_PAD",
1479914799
"updateState": "UP_TO_DATE"
14800+
},
14801+
"3014F71100000000000SHWSM": {
14802+
"availableFirmwareVersion": "0.0.0",
14803+
"connectionType": "HMIP_RF",
14804+
"deviceArchetype": "HMIP",
14805+
"firmwareVersion": "1.0.10",
14806+
"firmwareVersionInteger": 65546,
14807+
"functionalChannels": {
14808+
"0": {
14809+
"altitude": null,
14810+
"busConfigMismatch": null,
14811+
"coProFaulty": false,
14812+
"coProRestartNeeded": false,
14813+
"coProUpdateFailure": false,
14814+
"configPending": false,
14815+
"controlsMountingOrientation": null,
14816+
"daliBusState": null,
14817+
"dataDecodingFailedError": null,
14818+
"defaultLinkedGroup": [],
14819+
"deviceAliveSignalEnabled": null,
14820+
"deviceCommunicationError": null,
14821+
"deviceDriveError": null,
14822+
"deviceDriveModeError": null,
14823+
"deviceId": "3014F71100000000000SHWSM",
14824+
"deviceOperationMode": null,
14825+
"deviceOverheated": false,
14826+
"deviceOverloaded": false,
14827+
"devicePowerFailureDetected": false,
14828+
"deviceUndervoltage": false,
14829+
"displayContrast": null,
14830+
"displayMode": null,
14831+
"displayMountingOrientation": null,
14832+
"dutyCycle": false,
14833+
"frostProtectionError": false,
14834+
"frostProtectionErrorAcknowledged": null,
14835+
"functionalChannelType": "DEVICE_BASE",
14836+
"groupIndex": 0,
14837+
"groups": [
14838+
"00000000-0000-0000-0000-000000000022"
14839+
],
14840+
"index": 0,
14841+
"inputLayoutMode": null,
14842+
"invertedDisplayColors": null,
14843+
"label": "",
14844+
"lockJammed": null,
14845+
"lowBat": false,
14846+
"mountingModuleError": null,
14847+
"mountingOrientation": null,
14848+
"multicastRoutingEnabled": false,
14849+
"noDataFromLinkyError": null,
14850+
"operationDays": null,
14851+
"particulateMatterSensorCommunicationError": null,
14852+
"particulateMatterSensorError": null,
14853+
"powerShortCircuit": null,
14854+
"profilePeriodLimitReached": null,
14855+
"routerModuleEnabled": false,
14856+
"routerModuleSupported": false,
14857+
"rssiDeviceValue": -46,
14858+
"rssiPeerValue": -43,
14859+
"sensorCommunicationError": null,
14860+
"sensorError": null,
14861+
"shortCircuitDataLine": null,
14862+
"supportedOptionalFeatures": {
14863+
"IFeatureBusConfigMismatch": false,
14864+
"IFeatureDataDecodingFailedError": false,
14865+
"IFeatureDeviceCoProError": false,
14866+
"IFeatureDeviceCoProRestart": false,
14867+
"IFeatureDeviceCoProUpdate": false,
14868+
"IFeatureDeviceCommunicationError": false,
14869+
"IFeatureDeviceDaliBusError": false,
14870+
"IFeatureDeviceDriveError": false,
14871+
"IFeatureDeviceDriveModeError": false,
14872+
"IFeatureDeviceIdentify": false,
14873+
"IFeatureDeviceMountingModuleError": false,
14874+
"IFeatureDeviceOverheated": true,
14875+
"IFeatureDeviceOverloaded": false,
14876+
"IFeatureDeviceParticulateMatterSensorCommunicationError": false,
14877+
"IFeatureDeviceParticulateMatterSensorError": false,
14878+
"IFeatureDevicePowerFailure": false,
14879+
"IFeatureDeviceSensorCommunicationError": false,
14880+
"IFeatureDeviceSensorError": false,
14881+
"IFeatureDeviceTempSensorError": false,
14882+
"IFeatureDeviceTemperatureHumiditySensorCommunicationError": false,
14883+
"IFeatureDeviceTemperatureHumiditySensorError": false,
14884+
"IFeatureDeviceTemperatureOutOfRange": false,
14885+
"IFeatureDeviceUndervoltage": true,
14886+
"IFeatureMulticastRouter": false,
14887+
"IFeatureNoDataFromLinkyError": false,
14888+
"IFeaturePowerShortCircuit": false,
14889+
"IFeatureProfilePeriodLimit": false,
14890+
"IFeatureRssiValue": true,
14891+
"IFeatureShortCircuitDataLine": false,
14892+
"IFeatureTicVersionError": false,
14893+
"IOptionalFeatureAltitude": false,
14894+
"IOptionalFeatureDefaultLinkedGroup": false,
14895+
"IOptionalFeatureDeviceAliveSignalEnabled": false,
14896+
"IOptionalFeatureDeviceErrorLockJammed": false,
14897+
"IOptionalFeatureDeviceFrostProtectionError": true,
14898+
"IOptionalFeatureDeviceInputLayoutMode": false,
14899+
"IOptionalFeatureDeviceOperationMode": false,
14900+
"IOptionalFeatureDeviceSwitchChannelMode": false,
14901+
"IOptionalFeatureDeviceValveError": true,
14902+
"IOptionalFeatureDeviceWaterError": true,
14903+
"IOptionalFeatureDisplayContrast": false,
14904+
"IOptionalFeatureDisplayMode": false,
14905+
"IOptionalFeatureDutyCycle": true,
14906+
"IOptionalFeatureInvertedDisplayColors": false,
14907+
"IOptionalFeatureLowBat": true,
14908+
"IOptionalFeatureMountingOrientation": false,
14909+
"IOptionalFeatureOperationDays": false
14910+
},
14911+
"switchChannelMode": null,
14912+
"temperatureHumiditySensorCommunicationError": null,
14913+
"temperatureHumiditySensorError": null,
14914+
"temperatureOutOfRange": false,
14915+
"temperatureSensorError": null,
14916+
"ticVersionError": null,
14917+
"unreach": false,
14918+
"valveFlowError": false,
14919+
"valveWaterError": false
14920+
},
14921+
"1": {
14922+
"channelRole": "WATERING_ACTUATOR",
14923+
"deviceId": "3014F71100000000000SHWSM",
14924+
"functionalChannelType": "WATERING_ACTUATOR_CHANNEL",
14925+
"groupIndex": 1,
14926+
"groups": [
14927+
"00000000-0000-0000-0000-000000000023"
14928+
],
14929+
"index": 1,
14930+
"label": "",
14931+
"profileMode": "AUTOMATIC",
14932+
"supportedOptionalFeatures": {
14933+
"IFeatureWateringGroupActuatorChannel": true,
14934+
"IFeatureWateringProfileActuatorChannel": true
14935+
},
14936+
"userDesiredProfileMode": "AUTOMATIC",
14937+
"waterFlow": 0.0,
14938+
"waterVolume": 0.0,
14939+
"waterVolumeSinceOpen": 0.0,
14940+
"wateringActive": false,
14941+
"wateringOnTime": 3600.0
14942+
}
14943+
},
14944+
"homeId": "00000000-0000-0000-0000-000000000001",
14945+
"id": "3014F71100000000000SHWSM",
14946+
"label": "Bew\u00e4sserungsaktor",
14947+
"lastStatusUpdate": 1749501203047,
14948+
"liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED",
14949+
"manuallyUpdateForced": false,
14950+
"manufacturerCode": 9,
14951+
"measuredAttributes": {},
14952+
"modelId": 586,
14953+
"modelType": "ELV-SH-WSM",
14954+
"oem": "eQ-3",
14955+
"permanentlyReachable": true,
14956+
"serializedGlobalTradeItemNumber": "3014F71100000000000SHWSM",
14957+
"type": "WATERING_ACTUATOR",
14958+
"updateState": "UP_TO_DATE"
1480014959
}
1480114960
},
1480214961
"groups": {

src/homematicip/base/enums.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ class DeviceType(AutoNameEnum):
293293
WALL_MOUNTED_GARAGE_DOOR_CONTROLLER = auto()
294294
WALL_MOUNTED_UNIVERSAL_ACTUATOR = auto()
295295
WATER_SENSOR = auto()
296+
WATERING_ACTUATOR = auto()
296297
WEATHER_SENSOR = auto()
297298
WEATHER_SENSOR_PLUS = auto()
298299
WEATHER_SENSOR_PRO = auto()
@@ -510,6 +511,7 @@ class FunctionalChannelType(AutoNameEnum):
510511
WALL_MOUNTED_THERMOSTAT_WITHOUT_DISPLAY_CHANNEL = auto()
511512
WALL_MOUNTED_THERMOSTAT_WITH_CARBON_CHANNEL = auto()
512513
WATER_SENSOR_CHANNEL = auto()
514+
WATERING_ACTUATOR_CHANNEL = auto()
513515
WEATHER_SENSOR_CHANNEL = auto()
514516
WEATHER_SENSOR_PLUS_CHANNEL = auto()
515517
WEATHER_SENSOR_PRO_CHANNEL = auto()

src/homematicip/base/functionalChannels.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,3 +2233,52 @@ def from_json(self, js, groups: Iterable[Group]):
22332233
class CodeProtectedSecondaryActionChannel(FunctionalChannel):
22342234
"""this is the representative of the CODE_PROTECTED_SECONDARY_ACTION_CHANNEL channel (HmIP-WKP)"""
22352235
pass
2236+
2237+
2238+
class WateringActuatorChannel(FunctionalChannel):
2239+
"""this is the representative of the WATERING_ACTUATOR_CHANNEL channel"""
2240+
2241+
def __init__(self, device, connection):
2242+
super().__init__(device, connection)
2243+
2244+
self.waterFlow: float | None = None
2245+
self.waterVolume: float | None = None
2246+
self.waterVolumeSinceOpen: float | None = None
2247+
self.wateringActive: bool | None = None
2248+
self.wateringOnTime: float | None = None
2249+
2250+
def from_json(self, js, groups: Iterable[Group]):
2251+
super().from_json(js, groups)
2252+
self.waterFlow = js.get("waterFlow")
2253+
self.waterVolume = js.get("waterVolume")
2254+
self.waterVolumeSinceOpen = js.get("waterVolumeSinceOpen")
2255+
self.wateringActive = js.get("wateringActive")
2256+
self.wateringOnTime = js.get("wateringOnTime")
2257+
2258+
async def reset_water_volume_async(self):
2259+
"""Resets the water volume counter of the specified device."""
2260+
return await functional_channel_commands.reset_water_volume_async(
2261+
self._connection, self.device.id, self.index
2262+
)
2263+
2264+
async def set_watering_switch_state_async(self, on: bool):
2265+
"""Sets the watering switch state of the specified device."""
2266+
return await functional_channel_commands.set_watering_switch_state_async(
2267+
self._connection, self.device.id, self.index, on
2268+
)
2269+
2270+
async def set_watering_switch_state_with_time_async(self, on: bool, seconds: int):
2271+
"""Sets the watering switch state of the specified device with a specified time.
2272+
2273+
:param on: True to turn on, False to turn off.
2274+
:param seconds: The watering time in seconds.
2275+
"""
2276+
return await functional_channel_commands.set_watering_switch_state_with_time_async(
2277+
self._connection, self.device.id, self.index, on, seconds
2278+
)
2279+
2280+
async def toggle_watering_state_async(self):
2281+
"""Toggles the watering state of the specified device."""
2282+
return await functional_channel_commands.toggle_watering_state_async(
2283+
self._connection, self.device.id, self.index
2284+
)

src/homematicip/class_maps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
DeviceType.WALL_MOUNTED_THERMOSTAT_PRO: WallMountedThermostatPro,
100100
DeviceType.WALL_MOUNTED_THERMOSTAT_BASIC_HUMIDITY: WallMountedThermostatBasicHumidity,
101101
DeviceType.WATER_SENSOR: WaterSensor,
102+
DeviceType.WATERING_ACTUATOR: WateringActuator,
102103
DeviceType.WEATHER_SENSOR: WeatherSensor,
103104
DeviceType.WEATHER_SENSOR_PLUS: WeatherSensorPlus,
104105
DeviceType.WEATHER_SENSOR_PRO: WeatherSensorPro,
@@ -258,6 +259,7 @@
258259
FunctionalChannelType.WALL_MOUNTED_THERMOSTAT_WITHOUT_DISPLAY_CHANNEL: WallMountedThermostatWithoutDisplayChannel,
259260
FunctionalChannelType.WALL_MOUNTED_THERMOSTAT_WITH_CARBON_CHANNEL: WallMountedThermostatWithCarbonChannel,
260261
FunctionalChannelType.WATER_SENSOR_CHANNEL: WaterSensorChannel,
262+
FunctionalChannelType.WATERING_ACTUATOR_CHANNEL: WateringActuatorChannel,
261263
FunctionalChannelType.WEATHER_SENSOR_CHANNEL: WeatherSensorChannel,
262264
FunctionalChannelType.WEATHER_SENSOR_PLUS_CHANNEL: WeatherSensorPlusChannel,
263265
FunctionalChannelType.WEATHER_SENSOR_PRO_CHANNEL: WeatherSensorProChannel,

src/homematicip/commands/functional_channel_commands.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ async def reset_passage_counter_async(rest_connection: RestConnection, device_id
3030

3131

3232
async def reset_water_volume_async(rest_connection: RestConnection, device_id: str, channel_index: int):
33+
"""
34+
Resets the water volume counter of the specified device.
35+
36+
:param rest_connection: The REST connection instance.
37+
:param device_id: The device ID.
38+
:param channel_index: The channel index.
39+
"""
3340
data = {
3441
"channelIndex": channel_index,
3542
"deviceId": device_id
@@ -796,13 +803,9 @@ async def set_watering_switch_state_async(rest_connection: RestConnection, devic
796803
Set the watering switch state for a functional channel.
797804
798805
:param rest_connection: The REST connection instance.
799-
:type rest_connection: RestConnection
800806
:param device_id: The device ID.
801-
:type device_id: str
802807
:param channel_index: The channel index.
803-
:type channel_index: int
804808
:param watering_active: The desired watering state (True for active, False for inactive).
805-
:type watering_active: bool
806809
:return: The response from the cloud.
807810
:rtype: dict
808811
"""
@@ -820,15 +823,10 @@ async def set_watering_switch_state_with_time_async(rest_connection: RestConnect
820823
Set the watering switch state for a functional channel with a specific watering time.
821824
822825
:param rest_connection: The REST connection instance.
823-
:type rest_connection: RestConnection
824826
:param device_id: The device ID.
825-
:type device_id: str
826827
:param channel_index: The channel index.
827-
:type channel_index: int
828828
:param watering_active: The desired watering state (True for active, False for inactive).
829-
:type watering_active: bool
830829
:param watering_time: The watering time in seconds.
831-
:type watering_time: float
832830
:return: The response from the cloud.
833831
:rtype: dict
834832
"""
@@ -846,13 +844,9 @@ async def set_display_async(rest_connection: RestConnection, device_id: str, cha
846844
Set the climate control display.
847845
848846
:param rest_connection: The REST connection instance.
849-
:type rest_connection: RestConnection
850847
:param device_id: The device ID.
851-
:type device_id: str
852848
:param channel_index: The channel index.
853-
:type channel_index: int
854849
:param display: The display as string. Possible values are defined in the ClimateControlDisplay enum.
855-
:type display: str
856850
:return: The response from the cloud.
857851
:rtype: dict
858852
"""
@@ -996,3 +990,19 @@ async def start_light_scene_async(rest_connection: RestConnection, device_id: st
996990
"dimLevel": dim_level
997991
}
998992
return await rest_connection.async_post("device/control/startLightScene", data)
993+
994+
async def toggle_watering_state_async(rest_connection: RestConnection, device_id: str, channel_index: int):
995+
"""
996+
Toggle the watering state for a functional channel.
997+
998+
:param rest_connection: The REST connection instance.
999+
:param device_id: The device ID.
1000+
:param channel_index: The channel index.
1001+
:return: The response from the cloud.
1002+
:rtype: dict
1003+
"""
1004+
data = {
1005+
"channelIndex": channel_index,
1006+
"deviceId": device_id
1007+
}
1008+
return await rest_connection.async_post("device/control/toggleWateringState", data)

src/homematicip/device.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2757,3 +2757,7 @@ class MotionDetectorSwitchOutdoor(Device):
27572757

27582758
class WallMountedKeyPad(Device):
27592759
pass
2760+
2761+
2762+
class WateringActuator(Device):
2763+
pass

tests/test_devices.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,3 +1827,8 @@ def test_wall_mounted_keypad(fake_home: Home):
18271827
with no_ssl_verification():
18281828
d = fake_home.search_device_by_id("3014F7110000000000000WKP")
18291829
assert isinstance(d, WallMountedKeyPad)
1830+
1831+
def test_watering_actuator(fake_home: Home):
1832+
with no_ssl_verification():
1833+
d = fake_home.search_device_by_id("3014F71100000000000SHWSM")
1834+
assert isinstance(d, WateringActuator)

tests/test_functional_channels.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from unittest.mock import Mock
1+
from unittest.mock import Mock, patch, AsyncMock
22

33
from homematicip.base.channel_event import ChannelEvent
44
from homematicip.base.functionalChannels import *
@@ -524,4 +524,20 @@ def test_code_protected_secondary_channel(fake_home: Home):
524524
def test_device_blocking_channel(fake_home: Home):
525525
with no_ssl_verification():
526526
ch = fake_home.search_channel("3014F7110000000000000WKP", 0)
527-
assert isinstance(ch, DeviceBlockingChannel)
527+
assert isinstance(ch, DeviceBlockingChannel)
528+
529+
530+
async def test_watering_actuator_channel(fake_home: Home):
531+
with no_ssl_verification():
532+
ch = fake_home.search_channel("3014F71100000000000SHWSM", 1)
533+
assert isinstance(ch, WateringActuatorChannel)
534+
535+
connection_mock = AsyncMock()
536+
ch._connection = connection_mock
537+
538+
await ch.reset_water_volume_async()
539+
await ch.set_watering_switch_state_async(True)
540+
await ch.set_watering_switch_state_with_time_async(True, 100)
541+
await ch.toggle_watering_state_async()
542+
543+
assert len(connection_mock.mock_calls) == 4

0 commit comments

Comments
 (0)