From c45ba8b5c9e69a94ee2b95de08fbc2dba7bc5d78 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sun, 28 Sep 2025 23:55:12 +0300 Subject: [PATCH 01/18] [sbus] Fix bridge, lux & motion sensors Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/pom.xml | 2 +- .../binding/sbus/internal/handler/SbusContactHandler.java | 3 ++- .../binding/sbus/internal/handler/SbusLuxSensorHandler.java | 5 ++++- .../sbus/internal/handler/SbusMotionSensorHandler.java | 5 ++++- .../binding/sbus/internal/handler/SbusRgbwHandler.java | 6 ++++-- .../binding/sbus/internal/handler/SbusSwitchHandler.java | 3 ++- .../sbus/internal/handler/SbusTemperatureHandler.java | 3 ++- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/pom.xml b/bundles/org.openhab.binding.sbus/pom.xml index a8e5374f27ad5..6ec0736690da9 100644 --- a/bundles/org.openhab.binding.sbus/pom.xml +++ b/bundles/org.openhab.binding.sbus/pom.xml @@ -18,7 +18,7 @@ ro.ciprianpascu j2sbus - 1.6.3 + 1.6.4 diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java index 7be7d34532173..d27e97e8a0757 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java @@ -105,7 +105,8 @@ private boolean[] readContactStatusChannels(SbusService adapter, int subnetId, i // Execute transaction and parse response SbusResponse response = adapter.executeTransaction(request); if (!(response instanceof ReadDryChannelsResponse statusResponse)) { - throw new IllegalStateException("Unexpected response type: " + response.getClass().getSimpleName()); + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } InputRegister[] registers = statusResponse.getRegisters(); diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java index cc168df68011f..7636611739360 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java @@ -126,7 +126,8 @@ private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int SbusResponse response = adapter.executeTransaction(request); if (!(response instanceof ReadNineInOneStatusResponse statusResponse)) { - throw new IllegalStateException("Unexpected response type: " + response.getClass().getSimpleName()); + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } return statusResponse; @@ -140,10 +141,12 @@ protected void processAsyncMessage(SbusResponse response) { if (response instanceof MotionSensorStatusReport report) { // Process motion sensor status report (0x02CA broadcast) updateChannelStatesFromReport(report); + updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for lux handler {}", getThing().getUID()); } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { // Process 9-in-1 status response (0xDB01) updateChannelStatesFromResponse(statusResponse); + updateStatus(ThingStatus.ONLINE); logger.debug("Processed async 9-in-1 status response for lux handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java index 98c3fb106ad1e..6380688bc0b87 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java @@ -126,7 +126,8 @@ private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int SbusResponse response = adapter.executeTransaction(request); if (!(response instanceof ReadNineInOneStatusResponse statusResponse)) { - throw new IllegalStateException("Unexpected response type: " + response.getClass().getSimpleName()); + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } return statusResponse; @@ -140,10 +141,12 @@ protected void processAsyncMessage(SbusResponse response) { if (response instanceof MotionSensorStatusReport report) { // Process motion sensor status report (0x02CA broadcast) updateChannelStatesFromReport(report); + updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for handler {}", getThing().getUID()); } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { // Process 9-in-1 status response (0xDB01) updateChannelStatesFromResponse(statusResponse); + updateStatus(ThingStatus.ONLINE); logger.debug("Processed async 9-in-1 status response for handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusRgbwHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusRgbwHandler.java index 72499f2d35dab..1f606b91752ee 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusRgbwHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusRgbwHandler.java @@ -295,7 +295,8 @@ private int[] readStatusChannels(SbusService adapter, int subnetId, int deviceId // Execute transaction and parse response SbusResponse response = adapter.executeTransaction(request); if (!(response instanceof ReadStatusChannelsResponse statusResponse)) { - throw new IllegalStateException("Unexpected response type: " + response.getClass().getSimpleName()); + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } InputRegister[] registers = statusResponse.getRegisters(); int[] statusValues = new int[registers.length]; @@ -328,7 +329,8 @@ private int[] readRgbw(SbusService adapter, int subnetId, int deviceId, int chan // Execute transaction and parse response SbusResponse response = adapter.executeTransaction(request); if (!(response instanceof ReadRgbwResponse rgbwResponse)) { - throw new IllegalStateException("Unexpected response type: " + response.getClass().getSimpleName()); + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } InputRegister[] registers = rgbwResponse.getRegisters(); int[] rgbwValues = new int[Math.min(4, registers.length)]; diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusSwitchHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusSwitchHandler.java index a5c4cddccb4db..63363db327807 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusSwitchHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusSwitchHandler.java @@ -191,7 +191,8 @@ private int[] readStatusChannels(SbusService adapter, int subnetId, int deviceId // Execute transaction and parse response SbusResponse response = adapter.executeTransaction(request); if (!(response instanceof ReadStatusChannelsResponse statusResponse)) { - throw new IllegalStateException("Unexpected response type: " + response.getClass().getSimpleName()); + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } InputRegister[] registers = statusResponse.getRegisters(); int[] statuses = new int[registers.length]; diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusTemperatureHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusTemperatureHandler.java index b9bc18df758c3..dc021fdbebb0a 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusTemperatureHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusTemperatureHandler.java @@ -138,7 +138,8 @@ private float[] readTemperatures(SbusService adapter, int subnetId, int deviceId // Execute transaction and parse response SbusResponse response = adapter.executeTransaction(request); if (!(response instanceof ReadTemperatureResponse tempResponse)) { - throw new IllegalStateException("Unexpected response type: " + response.getClass().getSimpleName()); + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } InputRegister[] registers = tempResponse.getRegisters(); float[] temperatures = new float[registers.length]; From ca9a7515a6bd6da1c2cc026016c084600102e6a4 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Mon, 29 Sep 2025 20:24:45 +0300 Subject: [PATCH 02/18] [sbus] harvest contact information from 9in1 sensors Signed-off-by: Ciprian Pascu --- .../internal/handler/SbusContactHandler.java | 92 +++++++++++++++++-- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java index d27e97e8a0757..d6241ad477abf 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java @@ -25,15 +25,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; import ro.ciprianpascu.sbus.msg.ReadDryChannelsRequest; import ro.ciprianpascu.sbus.msg.ReadDryChannelsResponse; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; import ro.ciprianpascu.sbus.msg.ReadStatusChannelsResponse; import ro.ciprianpascu.sbus.msg.SbusResponse; import ro.ciprianpascu.sbus.procimg.InputRegister; /** * The {@link SbusContactHandler} is responsible for handling commands for Sbus contact devices. - * It supports reading the current contact state (open/closed). + * It supports reading the current contact state (open/closed) from both traditional contact sensors + * and dry contact information from 9-in-1 sensor devices. * * @author Ciprian Pascu - Initial contribution */ @@ -125,10 +128,16 @@ protected void processAsyncMessage(SbusResponse response) { if (response instanceof ReadStatusChannelsResponse statusResponse) { // Process status channel response using existing logic boolean[] statuses = extractContactStatuses(statusResponse); - - // Update channel states based on async message updateChannelStatesFromStatuses(statuses); logger.debug("Processed async contact status message for handler {}", getThing().getUID()); + } else if (response instanceof MotionSensorStatusReport report) { + // Process dry contact information from 9-in-1 sensor broadcast + updateContactsFromMotionReport(report); + logger.debug("Processed async motion sensor status report for contact handler {}", getThing().getUID()); + } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + // Process dry contact information from 9-in-1 sensor polling + updateContactsFromNineInOneResponse(statusResponse); + logger.debug("Processed async 9-in-1 status response for contact handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in contact handler {}: {}", getThing().getUID(), @@ -138,13 +147,20 @@ protected void processAsyncMessage(SbusResponse response) { @Override protected boolean isMessageRelevant(SbusResponse response) { - if (!(response instanceof ReadStatusChannelsResponse)) { - return false; + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + + if (response instanceof ReadStatusChannelsResponse) { + // Traditional contact sensor messages + return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; + } else if (response instanceof MotionSensorStatusReport) { + // 9-in-1 sensor broadcast messages - check source device + return response.getSourceSubnetID() == config.subnetId && response.getSourceUnitID() == config.id; + } else if (response instanceof ReadNineInOneStatusResponse) { + // 9-in-1 sensor polling responses + return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; } - // Check if the message is for this device based on subnet and unit ID - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; + return false; } /** @@ -178,4 +194,64 @@ private boolean[] extractContactStatuses(ReadStatusChannelsResponse response) { } return statuses; } + + // 9-in-1 Sensor Dry Contact Support Methods + + /** + * Update contact channels from motion sensor status report dry contact data. + * + * @param report the motion sensor status report containing dry contact data + */ + private void updateContactsFromMotionReport(MotionSensorStatusReport report) { + // Update contact channels based on dry contact status from motion sensor status report + for (Channel channel : getThing().getChannels()) { + if (!isLinked(channel.getUID())) { + continue; + } + + // Only process contact channels + if (channel.getChannelTypeUID() != null && "contact-channel".equals(channel.getChannelTypeUID().getId())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + + // Motion sensor status report has dry contact status - use channel number or default to 0 + int contactIndex = channelConfig.channelNumber > 0 ? channelConfig.channelNumber - 1 : 0; + boolean isOpen = report.getDryContactStatus(contactIndex) > 0; + updateState(channel.getUID(), isOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } + } + + logger.debug("Updated contact states from motion sensor status report"); + } + + /** + * Update contact channels from 9-in-1 sensor response dry contact data. + * + * @param response the 9-in-1 sensor response containing dry contact data + */ + private void updateContactsFromNineInOneResponse(ReadNineInOneStatusResponse response) { + // Update contact channels based on dry contact status from 9-in-1 sensor + for (Channel channel : getThing().getChannels()) { + if (!isLinked(channel.getUID())) { + continue; + } + + // Only process contact channels + if (channel.getChannelTypeUID() != null && "contact-channel".equals(channel.getChannelTypeUID().getId())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + boolean isOpen = false; + + // Map channel number to appropriate dry contact (1 or 2) + if (channelConfig.channelNumber == 1) { + isOpen = response.getDryContact1Status() > 0; + } else if (channelConfig.channelNumber == 2) { + isOpen = response.getDryContact2Status() > 0; + } + + updateState(channel.getUID(), isOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } + } + + logger.debug("Updated contact states from 9-in-1 sensor response - DryContact1: {}, DryContact2: {}", + response.getDryContact1Status(), response.getDryContact2Status()); + } } From cda581ff475fe9ae4bfdfd63ed4fe94414999c46 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Mon, 29 Sep 2025 22:13:51 +0300 Subject: [PATCH 03/18] [sbus] One multi-sensor thing for contact, lux & motion Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 101 +++++-- .../binding/sbus/BindingConstants.java | 3 +- .../handler/Sbus9in1ContactHandler.java | 216 ++++++++++++++ .../handler/Sbus9in1SensorsHandler.java | 268 ++++++++++++++++++ .../internal/handler/SbusContactHandler.java | 87 +----- .../internal/handler/SbusHandlerFactory.java | 12 +- .../resources/OH-INF/thing/thing-types.xml | 41 +-- 7 files changed, 576 insertions(+), 152 deletions(-) create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index 62b2b14efdf8a..5e018c10f0393 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -10,9 +10,8 @@ The binding supports various device types including RGB/RGBW controllers, temper - `rgbw` - RGB/RGBW Controllers for color and brightness control - `temperature` - Temperature Sensors for monitoring environmental conditions - `switch` - Switch Controllers for basic on/off and dimming control -- `contact-sensor` - Contact Sensors for monitoring open/closed states -- `motion-sensor` - Motion Sensors for detecting movement (9-in-1 devices) -- `lux-sensor` - Light Level Sensors for monitoring illuminance (LUX values) +- `contact-sensor` - Traditional Contact Sensors for monitoring open/closed states +- `multi-sensor` - Multi-Sensor (9-in-1) devices with motion, lux, and dry contact capabilities ## Discovery @@ -39,7 +38,7 @@ The Sbus Bridge has the following configuration parameters: ### Device Configuration -All device types (RGBW Controller, Contact Sensor, Switch, Temperature, Motion Sensor, Lux Sensor) share the same configuration parameters: +All device types share the same basic configuration parameters: | Name | Type | Description | Default | Required | Advanced | |:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| @@ -47,7 +46,12 @@ All device types (RGBW Controller, Contact Sensor, Switch, Temperature, Motion S | id | integer | Device ID | N/A | yes | no | | refresh | integer | Refresh interval in seconds (0 = listen-only mode) | 30 | no | yes | -**Note:** Setting `refresh=0` enables listen-only mode for all device types. In this mode, handlers only process asynchronous broadcast messages without actively polling the devices. This is particularly useful for Motion Sensor and Lux Sensor devices that broadcast status updates (0x02CA) from 9-in-1 devices. +**Device Type Specific Notes:** + +- **Traditional Contact Sensors (`contact-sensor`)**: Use ReadDryChannelsRequest protocol for simple contact monitoring +- **Multi-Sensor Devices (`multi-sensor`)**: Use ReadNineInOneStatusRequest protocol and support motion, lux, and dry contact channels from a single physical device + +**Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where handlers only process asynchronous broadcast messages (MotionSensorStatusReport) without actively polling. This is particularly useful for 9-in-1 sensor devices that broadcast status updates. ## Channels @@ -79,23 +83,29 @@ The color channel of RGBW controllers supports these additional parameters: | dimmer | Dimmer | RW | ON/OFF state with timer transition | | paired | Rollershutter | RW | UP/DOWN/STOP control for two paired channels (e.g., rollershutters)| -### Contact Sensor Channels +### Contact Sensor Channels (Traditional) | Channel | Type | Read/Write | Description | |:--------|:--------|:----------:|:----------------------------------------------------------| -| contact | Contact | R | Contact state (OPEN/CLOSED) | +| contact | Contact | R | Contact state (OPEN/CLOSED) for traditional contact sensors | + +### Multi-Sensor (9-in-1) Channels -### Motion Sensor Channels +Multi-sensor devices support multiple channel types from a single physical device: + +| Channel | Type | Read/Write | Description | +|:--------|:--------|:----------:|:----------------------------------------------------------| +| contact | Contact | R | Dry contact state (OPEN/CLOSED) - use channelNumber parameter to specify which contact (1 or 2) | +| motion | Switch | R | Motion detection state (ON=motion detected, OFF=no motion)| +| lux | Number | R | Light level in LUX units | -| Channel | Type | Read/Write | Description | -|:--------|:-------|:----------:|:----------------------------------------------------------| -| motion | Switch | R | Motion detection state (ON=motion detected, OFF=no motion) | +The contact channel supports these additional parameters: -### Lux Sensor Channels +| Parameter | Type | Description | Default | Required | Advanced | +|:--------------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| +| channelNumber | integer | The dry contact number on the 9-in-1 device (1 or 2) | 1 | no | no | -| Channel | Type | Read/Write | Description | -|:--------|:-------|:----------:|:----------------------------------------------------------| -| lux | Number | R | Light level in LUX units | +**Note:** You can configure any combination of these channels on a single `multi-sensor` thing to match your 9-in-1 device capabilities. ## Full Example @@ -127,18 +137,25 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type paired-channel : third_switch [ channelNumber=3, pairedChannelNumber=4 ] } + // Traditional contact sensor Thing contact-sensor contact1 [ id=80, refresh=30 ] { Channels: Type contact-channel : contact [ channelNumber=1 ] } - Thing motion-sensor motion1 [ id=85, refresh=0 ] { + // 9-in-1 multi-sensor device with all capabilities + Thing multi-sensor multisensor1 [ id=85, refresh=0 ] { Channels: - Type motion-channel : motion + Type contact-channel : contact1 [ channelNumber=1 ] // First dry contact + Type contact-channel : contact2 [ channelNumber=2 ] // Second dry contact + Type motion-channel : motion // Motion detection + Type lux-channel : lux // Light level } - Thing lux-sensor lux1 [ id=85, refresh=0 ] { + // 9-in-1 sensor with only motion and lux (no contacts) + Thing multi-sensor motionlux1 [ id=86, refresh=0 ] { Channels: + Type motion-channel : motion Type lux-channel : lux } } @@ -161,14 +178,18 @@ Group gLight "RGBW Light" ["Lighting"] Color rgbwColor "Color" (gLight) ["Control", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:color" } Switch rgbwPower "Power" (gLight) ["Switch", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:power" } -// Contact Sensor +// Traditional Contact Sensor Contact Door_Contact "Door [%s]" { channel="sbus:contact-sensor:mybridge:contact1:contact" } -// Motion Sensor (listen-only mode) -Switch Motion_Sensor "Motion [%s]" { channel="sbus:motion-sensor:mybridge:motion1:motion" } +// 9-in-1 Multi-Sensor Items +Contact Sensor_Contact1 "Sensor Contact 1 [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:contact1" } +Contact Sensor_Contact2 "Sensor Contact 2 [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:contact2" } +Switch Motion_Sensor "Motion [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:motion" } +Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:multi-sensor:mybridge:multisensor1:lux" } -// Lux Sensor (listen-only mode) -Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:lux-sensor:mybridge:lux1:lux" } +// Motion and Lux only sensor +Switch Motion_Only "Motion [%s]" { channel="sbus:multi-sensor:mybridge:motionlux1:motion" } +Number Lux_Only "Light Level [%.0f lux]" { channel="sbus:multi-sensor:mybridge:motionlux1:lux" } ``` ### Sitemap Configuration @@ -177,16 +198,48 @@ Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:lux-sensor:mybr sitemap sbus label="Sbus Demo" { Frame label="Sbus Controls" { - Colorpicker item=Light_RGB + Colorpicker item=rgbwColor + Switch item=rgbwPower Text item=Temp_Sensor Switch item=Light_Switch Rollershutter item=Rollershutter_Switch + } + + Frame label="Sensors" { Text item=Door_Contact + Text item=Sensor_Contact1 + Text item=Sensor_Contact2 + Text item=Motion_Sensor + Text item=Lux_Sensor } } ## Usage Notes +### Sensor Device Types + +The binding supports two distinct types of sensor devices: + +#### Traditional Contact Sensors (`contact-sensor`) +- **Protocol**: Uses `ReadDryChannelsRequest/Response` +- **Use Case**: Simple contact sensors with basic open/closed detection +- **Channels**: Contact channels only +- **Configuration**: Standard device configuration with channel numbers + +#### Multi-Sensor (9-in-1) Devices (`multi-sensor`) +- **Protocol**: Uses `ReadNineInOneStatusRequest/Response` and `MotionSensorStatusReport` broadcasts +- **Use Case**: Advanced sensor devices combining multiple sensor types in one physical unit +- **Channels**: Any combination of contact, motion, and lux channels +- **Configuration**: Single device configuration with multiple channel types +- **Benefits**: + - Single polling job for all sensor data + - Efficient communication with one physical device + - Supports broadcast status updates for real-time responsiveness + +**Choosing the Right Type:** +- Use `contact-sensor` for traditional, simple contact sensors +- Use `multi-sensor` for 9-in-1 devices that provide motion detection, light level sensing, and/or dry contact monitoring + ### RGB vs. RGBW Mode The `enableWhite` parameter for color channels controls whether the white component is used: diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java index 32870e6cfdf2d..796accacf8e67 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java @@ -37,8 +37,7 @@ private BindingConstants() { public static final ThingTypeUID THING_TYPE_TEMPERATURE = new ThingTypeUID(BINDING_ID, "temperature"); public static final ThingTypeUID THING_TYPE_RGBW = new ThingTypeUID(BINDING_ID, "rgbw"); public static final ThingTypeUID THING_TYPE_CONTACT_SENSOR = new ThingTypeUID(BINDING_ID, "contact-sensor"); - public static final ThingTypeUID THING_TYPE_MOTION_SENSOR = new ThingTypeUID(BINDING_ID, "motion-sensor"); - public static final ThingTypeUID THING_TYPE_LUX_SENSOR = new ThingTypeUID(BINDING_ID, "lux-sensor"); + public static final ThingTypeUID THING_TYPE_MULTI_SENSOR = new ThingTypeUID(BINDING_ID, "multi-sensor"); // Channel IDs for Switch Device public static final String CHANNEL_SWITCH_STATE = "state"; diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java new file mode 100644 index 0000000000000..95820fa64f7df --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.handler; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.sbus.BindingConstants; +import org.openhab.binding.sbus.internal.SbusService; +import org.openhab.binding.sbus.internal.config.SbusChannelConfig; +import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusRequest; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; +import ro.ciprianpascu.sbus.msg.SbusResponse; + +/** + * The {@link Sbus9in1ContactHandler} is responsible for handling contact sensor channels + * from 9-in-1 sensor devices. It supports reading dry contact states and can operate in both + * polling and listen-only modes. When refresh is set to 0, it operates in listen-only mode using + * MotionSensorStatusReport broadcasts from 9-in-1 devices. + * + * @author Ciprian Pascu - Initial contribution + */ +public class Sbus9in1ContactHandler extends AbstractSbusHandler { + + private final Logger logger = LoggerFactory.getLogger(Sbus9in1ContactHandler.class); + + public Sbus9in1ContactHandler(Thing thing) { + super(thing); + } + + @Override + protected void initializeChannels() { + // Create contact channels based on configured channels + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID())) { + // Contact channels are already defined in the thing configuration + logger.debug("Initialized contact channel: {}", channelUID.getId()); + } + } + } + + @Override + protected void pollDevice() { + final SbusService adapter = super.sbusAdapter; + if (adapter == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.device.adapter-not-initialized"); + return; + } + + try { + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + ReadNineInOneStatusResponse response = readNineInOneStatus(adapter, config.subnetId, config.id); + + // Update channel states from response + updateChannelStatesFromResponse(response); + updateStatus(ThingStatus.ONLINE); + } catch (IllegalStateException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/error.device.communication"); + logger.warn("Error polling 9-in-1 contact sensor device {}: {}", getThing().getUID(), e.getMessage()); + } + } + + /** + * Update channel states based on sensor response data. + * + * @param response the sensor response containing contact data + */ + private void updateChannelStatesFromResponse(ReadNineInOneStatusResponse response) { + // Update contact channels from 9-in-1 response + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + + // Use channelNumber to determine which dry contact (1 or 2, default to 1) + int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; + boolean contactState = false; + + if (channelNumber == 1) { + contactState = response.getDryContact1Status() > 0; + } else if (channelNumber == 2) { + contactState = response.getDryContact2Status() > 0; + } + + OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + updateState(channelUID, state); + + logger.debug("Updated 9-in-1 contact channel {} (number {}) state: {}", channelUID.getId(), + channelNumber, state); + } + } + } + + /** + * Update channel states based on motion sensor status report data. + * + * @param report the motion sensor status report containing contact data + */ + private void updateChannelStatesFromReport(MotionSensorStatusReport report) { + // Update contact channels from motion sensor status report + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + + // Use channelNumber to determine which dry contact (1 or 2, default to 1) + int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; + boolean contactState = false; + + if (channelNumber == 1) { + contactState = report.getDryContactStatus(0) > 0; // First dry contact (index 0) + } else if (channelNumber == 2) { + contactState = report.getDryContactStatus(1) > 0; // Second dry contact (index 1) + } + + OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + updateState(channelUID, state); + + logger.debug("Updated 9-in-1 contact channel {} (number {}) state from report: {}", channelUID.getId(), + channelNumber, state); + } + } + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + // 9-in-1 contact sensors are read-only devices, no commands to handle + logger.debug("9-in-1 contact sensor is read-only, ignoring command {} for channel {}", command, channelUID); + } + + /** + * Reads 9-in-1 sensor status from an SBUS device. + * + * @param adapter the SBUS service adapter + * @param subnetId the subnet ID of the device + * @param deviceId the device ID + * @return ReadNineInOneStatusResponse containing sensor data + * @throws IllegalStateException if the SBUS transaction fails + */ + private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int subnetId, int deviceId) + throws IllegalStateException { + ReadNineInOneStatusRequest request = new ReadNineInOneStatusRequest(); + request.setSubnetID(subnetId); + request.setUnitID(deviceId); + + SbusResponse response = adapter.executeTransaction(request); + if (!(response instanceof ReadNineInOneStatusResponse statusResponse)) { + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); + } + + return statusResponse; + } + + // Async Message Handling + + @Override + protected void processAsyncMessage(SbusResponse response) { + try { + if (response instanceof MotionSensorStatusReport report) { + // Process motion sensor status report (0x02CA broadcast) + updateChannelStatesFromReport(report); + updateStatus(ThingStatus.ONLINE); + logger.debug("Processed async motion sensor status report for 9-in-1 contact handler {}", + getThing().getUID()); + } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + // Process 9-in-1 status response (0xDB01) + updateChannelStatesFromResponse(statusResponse); + updateStatus(ThingStatus.ONLINE); + logger.debug("Processed async 9-in-1 status response for 9-in-1 contact handler {}", + getThing().getUID()); + } + } catch (IllegalStateException | IllegalArgumentException e) { + logger.warn("Error processing async message in 9-in-1 contact sensor handler {}: {}", getThing().getUID(), + e.getMessage()); + } + } + + @Override + protected boolean isMessageRelevant(SbusResponse response) { + if (response instanceof MotionSensorStatusReport) { + // Motion sensor status reports are broadcast messages (to FF:FF) + // They are relevant if they come from our device + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + return response.getSourceSubnetID() == config.subnetId && response.getSourceUnitID() == config.id; + } else if (response instanceof ReadNineInOneStatusResponse) { + // Check if the response is for this device based on subnet and unit ID + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; + } + return false; + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java new file mode 100644 index 0000000000000..17150165f59f0 --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.handler; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.sbus.internal.SbusService; +import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusRequest; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; +import ro.ciprianpascu.sbus.msg.SbusResponse; + +/** + * The {@link Sbus9in1SensorsHandler} is responsible for coordinating 9-in-1 sensor devices. + * It acts as a central coordinator that polls the 9-in-1 sensor and routes the data + * to specialized handlers (contact, motion, lux) based on configured channels. + * This handler manages the communication with the physical device while delegating + * channel-specific processing to the appropriate specialized handlers. + * + * @author Ciprian Pascu - Initial contribution + */ +public class Sbus9in1SensorsHandler extends AbstractSbusHandler { + + private final Logger logger = LoggerFactory.getLogger(Sbus9in1SensorsHandler.class); + + // Specialized handlers for different sensor types + private Sbus9in1ContactHandler contactHandler; + private SbusMotionSensorHandler motionHandler; + private SbusLuxSensorHandler luxHandler; + + public Sbus9in1SensorsHandler(Thing thing) { + super(thing); + } + + @Override + protected void initializeChannels() { + // Create specialized handlers based on configured channels + createSpecializedHandlers(); + + // Initialize channels for each specialized handler + if (contactHandler != null) { + contactHandler.initializeChannels(); + } + if (motionHandler != null) { + motionHandler.initializeChannels(); + } + if (luxHandler != null) { + luxHandler.initializeChannels(); + } + } + + @Override + protected void pollDevice() { + final SbusService adapter = super.sbusAdapter; + if (adapter == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.device.adapter-not-initialized"); + return; + } + + try { + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + ReadNineInOneStatusResponse response = readNineInOneStatus(adapter, config.subnetId, config.id); + + // Route the response to all active specialized handlers + routeResponseToHandlers(response); + + updateStatus(ThingStatus.ONLINE); + } catch (IllegalStateException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/error.device.communication"); + logger.warn("Error polling 9-in-1 sensor device {}: {}", getThing().getUID(), e.getMessage()); + } + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + // 9-in-1 sensors are read-only devices, no commands to handle + logger.debug("9-in-1 sensor is read-only, ignoring command {} for channel {}", command, channelUID); + } + + @Override + protected void processAsyncMessage(SbusResponse response) { + try { + if (response instanceof MotionSensorStatusReport report) { + // Route motion sensor status report to all active handlers + routeReportToHandlers(report); + updateStatus(ThingStatus.ONLINE); + logger.debug("Processed async motion sensor status report for sensor handler {}", getThing().getUID()); + } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + // Route 9-in-1 status response to all active handlers + routeResponseToHandlers(statusResponse); + updateStatus(ThingStatus.ONLINE); + logger.debug("Processed async 9-in-1 status response for sensor handler {}", getThing().getUID()); + } + } catch (IllegalStateException | IllegalArgumentException e) { + logger.warn("Error processing async message in sensor handler {}: {}", getThing().getUID(), e.getMessage()); + } + } + + @Override + protected boolean isMessageRelevant(SbusResponse response) { + if (response instanceof MotionSensorStatusReport) { + // Motion sensor status reports are broadcast messages (to FF:FF) + // They are relevant if they come from our device + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + return response.getSourceSubnetID() == config.subnetId && response.getSourceUnitID() == config.id; + } else if (response instanceof ReadNineInOneStatusResponse) { + // Check if the response is for this device based on subnet and unit ID + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; + } + return false; + } + + /** + * Create specialized handlers based on the configured channels. + */ + private void createSpecializedHandlers() { + // Check configured channels and create appropriate handlers + for (org.openhab.core.thing.Channel channel : getThing().getChannels()) { + if (channel.getChannelTypeUID() != null) { + String channelType = channel.getChannelTypeUID().getId(); + + // Create contact handler for contact channels + if ("contact-channel".equals(channelType) && contactHandler == null) { + contactHandler = new Sbus9in1ContactHandler(getThing()); + logger.debug("Created 9-in-1 contact handler for sensor {}", getThing().getUID()); + } + + // Create motion handler for motion channels + if ("motion-channel".equals(channelType) && motionHandler == null) { + motionHandler = new SbusMotionSensorHandler(getThing()); + logger.debug("Created motion handler for sensor {}", getThing().getUID()); + } + + // Create lux handler for lux channels + if ("lux-channel".equals(channelType) && luxHandler == null) { + luxHandler = new SbusLuxSensorHandler(getThing()); + logger.debug("Created lux handler for sensor {}", getThing().getUID()); + } + } + } + } + + private List getConfiguredChannelTypes() { + List channelTypes = new ArrayList<>(); + + getThing().getChannels().forEach(channel -> { + if (channel.getChannelTypeUID() != null) { + String channelType = channel.getChannelTypeUID().getId(); + if (!channelTypes.contains(channelType)) { + channelTypes.add(channelType); + } + } + }); + + return channelTypes; + } + + // Message Routing Methods + + /** + * Route ReadNineInOneStatusResponse to appropriate specialized handlers. + */ + private void routeResponseToHandlers(ReadNineInOneStatusResponse response) { + // Route to contact handler if it exists + if (contactHandler != null) { + contactHandler.processAsyncMessage(response); + } + + // Route to motion handler if it exists + if (motionHandler != null) { + motionHandler.processAsyncMessage(response); + } + + // Route to lux handler if it exists + if (luxHandler != null) { + luxHandler.processAsyncMessage(response); + } + } + + /** + * Route MotionSensorStatusReport to appropriate specialized handlers. + */ + private void routeReportToHandlers(MotionSensorStatusReport report) { + // Route to contact handler if it exists + if (contactHandler != null) { + contactHandler.processAsyncMessage(report); + } + + // Route to motion handler if it exists + if (motionHandler != null) { + motionHandler.processAsyncMessage(report); + } + + // Route to lux handler if it exists + if (luxHandler != null) { + luxHandler.processAsyncMessage(report); + } + } + + // SBUS Protocol Methods + + /** + * Reads 9-in-1 sensor status from an SBUS device. + * + * @param adapter the SBUS service adapter + * @param subnetId the subnet ID of the device + * @param deviceId the device ID + * @return ReadNineInOneStatusResponse containing sensor data + * @throws IllegalStateException if the SBUS transaction fails + */ + private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int subnetId, int deviceId) + throws IllegalStateException { + ReadNineInOneStatusRequest request = new ReadNineInOneStatusRequest(); + request.setSubnetID(subnetId); + request.setUnitID(deviceId); + + SbusResponse response = adapter.executeTransaction(request); + if (!(response instanceof ReadNineInOneStatusResponse statusResponse)) { + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); + } + + return statusResponse; + } + + @Override + public void dispose() { + // Dispose specialized handlers + if (contactHandler != null) { + contactHandler.dispose(); + contactHandler = null; + } + if (motionHandler != null) { + motionHandler.dispose(); + motionHandler = null; + } + if (luxHandler != null) { + luxHandler.dispose(); + luxHandler = null; + } + + super.dispose(); + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java index d6241ad477abf..b1e44e2b3c541 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java @@ -25,18 +25,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; import ro.ciprianpascu.sbus.msg.ReadDryChannelsRequest; import ro.ciprianpascu.sbus.msg.ReadDryChannelsResponse; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; import ro.ciprianpascu.sbus.msg.ReadStatusChannelsResponse; import ro.ciprianpascu.sbus.msg.SbusResponse; import ro.ciprianpascu.sbus.procimg.InputRegister; /** - * The {@link SbusContactHandler} is responsible for handling commands for Sbus contact devices. - * It supports reading the current contact state (open/closed) from both traditional contact sensors - * and dry contact information from 9-in-1 sensor devices. + * The {@link SbusContactHandler} is responsible for handling traditional Sbus contact sensor devices. + * It supports reading the current contact state (open/closed) using the ReadDryChannelsRequest protocol. + * For 9-in-1 sensor devices, use the Sbus9in1ContactHandler instead. * * @author Ciprian Pascu - Initial contribution */ @@ -129,15 +127,8 @@ protected void processAsyncMessage(SbusResponse response) { // Process status channel response using existing logic boolean[] statuses = extractContactStatuses(statusResponse); updateChannelStatesFromStatuses(statuses); + updateStatus(ThingStatus.ONLINE); logger.debug("Processed async contact status message for handler {}", getThing().getUID()); - } else if (response instanceof MotionSensorStatusReport report) { - // Process dry contact information from 9-in-1 sensor broadcast - updateContactsFromMotionReport(report); - logger.debug("Processed async motion sensor status report for contact handler {}", getThing().getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // Process dry contact information from 9-in-1 sensor polling - updateContactsFromNineInOneResponse(statusResponse); - logger.debug("Processed async 9-in-1 status response for contact handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in contact handler {}: {}", getThing().getUID(), @@ -147,19 +138,11 @@ protected void processAsyncMessage(SbusResponse response) { @Override protected boolean isMessageRelevant(SbusResponse response) { - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - if (response instanceof ReadStatusChannelsResponse) { // Traditional contact sensor messages - return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; - } else if (response instanceof MotionSensorStatusReport) { - // 9-in-1 sensor broadcast messages - check source device - return response.getSourceSubnetID() == config.subnetId && response.getSourceUnitID() == config.id; - } else if (response instanceof ReadNineInOneStatusResponse) { - // 9-in-1 sensor polling responses + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; } - return false; } @@ -194,64 +177,4 @@ private boolean[] extractContactStatuses(ReadStatusChannelsResponse response) { } return statuses; } - - // 9-in-1 Sensor Dry Contact Support Methods - - /** - * Update contact channels from motion sensor status report dry contact data. - * - * @param report the motion sensor status report containing dry contact data - */ - private void updateContactsFromMotionReport(MotionSensorStatusReport report) { - // Update contact channels based on dry contact status from motion sensor status report - for (Channel channel : getThing().getChannels()) { - if (!isLinked(channel.getUID())) { - continue; - } - - // Only process contact channels - if (channel.getChannelTypeUID() != null && "contact-channel".equals(channel.getChannelTypeUID().getId())) { - SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); - - // Motion sensor status report has dry contact status - use channel number or default to 0 - int contactIndex = channelConfig.channelNumber > 0 ? channelConfig.channelNumber - 1 : 0; - boolean isOpen = report.getDryContactStatus(contactIndex) > 0; - updateState(channel.getUID(), isOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED); - } - } - - logger.debug("Updated contact states from motion sensor status report"); - } - - /** - * Update contact channels from 9-in-1 sensor response dry contact data. - * - * @param response the 9-in-1 sensor response containing dry contact data - */ - private void updateContactsFromNineInOneResponse(ReadNineInOneStatusResponse response) { - // Update contact channels based on dry contact status from 9-in-1 sensor - for (Channel channel : getThing().getChannels()) { - if (!isLinked(channel.getUID())) { - continue; - } - - // Only process contact channels - if (channel.getChannelTypeUID() != null && "contact-channel".equals(channel.getChannelTypeUID().getId())) { - SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); - boolean isOpen = false; - - // Map channel number to appropriate dry contact (1 or 2) - if (channelConfig.channelNumber == 1) { - isOpen = response.getDryContact1Status() > 0; - } else if (channelConfig.channelNumber == 2) { - isOpen = response.getDryContact2Status() > 0; - } - - updateState(channel.getUID(), isOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED); - } - } - - logger.debug("Updated contact states from 9-in-1 sensor response - DryContact1: {}, DryContact2: {}", - response.getDryContact1Status(), response.getDryContact2Status()); - } } diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java index 7b3af7159c5cc..d89b9baa0f4b6 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java @@ -40,8 +40,7 @@ public class SbusHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(SbusHandlerFactory.class); private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_UDP_BRIDGE, THING_TYPE_SWITCH, - THING_TYPE_TEMPERATURE, THING_TYPE_RGBW, THING_TYPE_CONTACT_SENSOR, THING_TYPE_MOTION_SENSOR, - THING_TYPE_LUX_SENSOR); + THING_TYPE_TEMPERATURE, THING_TYPE_RGBW, THING_TYPE_CONTACT_SENSOR, THING_TYPE_MULTI_SENSOR); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -69,12 +68,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } else if (thingTypeUID.equals(THING_TYPE_CONTACT_SENSOR)) { logger.debug("Creating Sbus contact sensor handler for thing {}", thing.getUID()); return new SbusContactHandler(thing); - } else if (thingTypeUID.equals(THING_TYPE_MOTION_SENSOR)) { - logger.debug("Creating Sbus motion sensor handler for thing {}", thing.getUID()); - return new SbusMotionSensorHandler(thing); - } else if (thingTypeUID.equals(THING_TYPE_LUX_SENSOR)) { - logger.debug("Creating Sbus lux sensor handler for thing {}", thing.getUID()); - return new SbusLuxSensorHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_MULTI_SENSOR)) { + logger.debug("Creating Sbus multi-sensor handler for thing {}", thing.getUID()); + return new Sbus9in1SensorsHandler(thing); } logger.debug("Unknown thing type: {}", thingTypeUID); diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index 4e4080fc8618f..cf603f1636df6 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -129,45 +129,14 @@ - - + + - - Sbus motion sensor device (9-in-1 devices) - MotionDetector - - - - Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. - 1 - - - - - - - - The ID of the Sbus device - - - - Refresh interval in seconds (0 = listen-only mode for broadcast messages) - 0 - s - - - - - - - - - - - Sbus light level sensor device (9-in-1 devices) - IlluminanceSensor + + Sbus 9-in-1 sensor device with motion, lux, and dry contact capabilities + Sensor From df000babcb3706c73ba77ffac59b59671916965f Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Mon, 29 Sep 2025 22:33:24 +0300 Subject: [PATCH 04/18] [sbus] One multi-sensor thing for contact, lux & motion. Fix SAT Signed-off-by: Ciprian Pascu --- .../binding/sbus/internal/handler/AbstractSbusHandler.java | 2 ++ .../sbus/internal/handler/Sbus9in1ContactHandler.java | 5 +++-- .../sbus/internal/handler/Sbus9in1SensorsHandler.java | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java index 5c04316ae1eae..f8e8a84d222d4 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java @@ -17,6 +17,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; @@ -43,6 +44,7 @@ * * @author Ciprian Pascu - Initial contribution */ +@NonNullByDefault public abstract class AbstractSbusHandler extends BaseThingHandler implements SbusMessageListener { protected final Logger logger = LoggerFactory.getLogger(getClass()); diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java index 95820fa64f7df..956106d98b16d 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.sbus.internal.handler; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.sbus.BindingConstants; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusChannelConfig; @@ -40,6 +40,7 @@ * * @author Ciprian Pascu - Initial contribution */ +@NonNullByDefault public class Sbus9in1ContactHandler extends AbstractSbusHandler { private final Logger logger = LoggerFactory.getLogger(Sbus9in1ContactHandler.class); @@ -146,7 +147,7 @@ private void updateChannelStatesFromReport(MotionSensorStatusReport report) { } @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + public void handleCommand(ChannelUID channelUID, Command command) { // 9-in-1 contact sensors are read-only devices, no commands to handle logger.debug("9-in-1 contact sensor is read-only, ignoring command {} for channel {}", command, channelUID); } diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java index 17150165f59f0..42cd27c7ce2f0 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java @@ -15,7 +15,7 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; import org.openhab.core.thing.ChannelUID; @@ -40,6 +40,7 @@ * * @author Ciprian Pascu - Initial contribution */ +@NonNullByDefault public class Sbus9in1SensorsHandler extends AbstractSbusHandler { private final Logger logger = LoggerFactory.getLogger(Sbus9in1SensorsHandler.class); @@ -95,7 +96,7 @@ protected void pollDevice() { } @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + public void handleCommand(ChannelUID channelUID, Command command) { // 9-in-1 sensors are read-only devices, no commands to handle logger.debug("9-in-1 sensor is read-only, ignoring command {} for channel {}", command, channelUID); } From 65d52bd1280376ee3d6a31e4e55a7b99f1503ad6 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Mon, 29 Sep 2025 22:56:16 +0300 Subject: [PATCH 05/18] [sbus] One multi-sensor thing for contact, lux & motion. Fix SAT Signed-off-by: Ciprian Pascu --- .../sbus/internal/handler/AbstractSbusHandler.java | 2 -- .../sbus/internal/handler/Sbus9in1ContactHandler.java | 11 ++++++----- .../sbus/internal/handler/Sbus9in1SensorsHandler.java | 9 ++++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java index f8e8a84d222d4..5c04316ae1eae 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java @@ -17,7 +17,6 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; @@ -44,7 +43,6 @@ * * @author Ciprian Pascu - Initial contribution */ -@NonNullByDefault public abstract class AbstractSbusHandler extends BaseThingHandler implements SbusMessageListener { protected final Logger logger = LoggerFactory.getLogger(getClass()); diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java index 956106d98b16d..e1c232a192d58 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.sbus.internal.handler; -import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.sbus.BindingConstants; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusChannelConfig; @@ -40,7 +39,6 @@ * * @author Ciprian Pascu - Initial contribution */ -@NonNullByDefault public class Sbus9in1ContactHandler extends AbstractSbusHandler { private final Logger logger = LoggerFactory.getLogger(Sbus9in1ContactHandler.class); @@ -54,7 +52,8 @@ protected void initializeChannels() { // Create contact channels based on configured channels for (Channel channel : getThing().getChannels()) { ChannelUID channelUID = channel.getUID(); - if (BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID())) { + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { // Contact channels are already defined in the thing configuration logger.debug("Initialized contact channel: {}", channelUID.getId()); } @@ -93,7 +92,8 @@ private void updateChannelStatesFromResponse(ReadNineInOneStatusResponse respons // Update contact channels from 9-in-1 response for (Channel channel : getThing().getChannels()) { ChannelUID channelUID = channel.getUID(); - if (BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID())) { + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); // Use channelNumber to determine which dry contact (1 or 2, default to 1) @@ -124,7 +124,8 @@ private void updateChannelStatesFromReport(MotionSensorStatusReport report) { // Update contact channels from motion sensor status report for (Channel channel : getThing().getChannels()) { ChannelUID channelUID = channel.getUID(); - if (BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID())) { + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); // Use channelNumber to determine which dry contact (1 or 2, default to 1) diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java index 42cd27c7ce2f0..5bf5488d2dc24 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java @@ -15,7 +15,7 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; import org.openhab.core.thing.ChannelUID; @@ -40,15 +40,14 @@ * * @author Ciprian Pascu - Initial contribution */ -@NonNullByDefault public class Sbus9in1SensorsHandler extends AbstractSbusHandler { private final Logger logger = LoggerFactory.getLogger(Sbus9in1SensorsHandler.class); // Specialized handlers for different sensor types - private Sbus9in1ContactHandler contactHandler; - private SbusMotionSensorHandler motionHandler; - private SbusLuxSensorHandler luxHandler; + private @Nullable Sbus9in1ContactHandler contactHandler; + private @Nullable SbusMotionSensorHandler motionHandler; + private @Nullable SbusLuxSensorHandler luxHandler; public Sbus9in1SensorsHandler(Thing thing) { super(thing); From 5c203b8b8452b706c1127e3c15ad268d37eafa43 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Tue, 30 Sep 2025 20:39:13 +0300 Subject: [PATCH 06/18] Update bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ciprian Pascu --- .../internal/handler/Sbus9in1SensorsHandler.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java index 5bf5488d2dc24..39e6ba312800a 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java @@ -164,21 +164,6 @@ private void createSpecializedHandlers() { } } - private List getConfiguredChannelTypes() { - List channelTypes = new ArrayList<>(); - - getThing().getChannels().forEach(channel -> { - if (channel.getChannelTypeUID() != null) { - String channelType = channel.getChannelTypeUID().getId(); - if (!channelTypes.contains(channelType)) { - channelTypes.add(channelType); - } - } - }); - - return channelTypes; - } - // Message Routing Methods /** From 6e1c79e4556291f6ef40982d4f58b7bb2d1a8f3b Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Tue, 30 Sep 2025 20:52:54 +0300 Subject: [PATCH 07/18] [sbus] Fix SAT Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 1 + .../binding/sbus/internal/handler/Sbus9in1SensorsHandler.java | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index 5e018c10f0393..ac8df76281036 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -213,6 +213,7 @@ sitemap sbus label="Sbus Demo" Text item=Lux_Sensor } } +``` ## Usage Notes diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java index 39e6ba312800a..7322c9df69043 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java @@ -12,9 +12,6 @@ */ package org.openhab.binding.sbus.internal.handler; -import java.util.ArrayList; -import java.util.List; - import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; From 8fb963c9b00e901435404fb43ca795844d04136c Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Tue, 30 Sep 2025 22:10:05 +0300 Subject: [PATCH 08/18] [sbus] More refactoring for the 9in1 thing Signed-off-by: Ciprian Pascu --- .../internal/handler/AbstractSbusHandler.java | 11 + .../handler/Sbus9in1ContactHandler.java | 218 ------------------ .../handler/Sbus9in1SensorsHandler.java | 150 ++++++------ .../internal/helper/AbstractSbusHelper.java | 70 ++++++ .../internal/helper/SbusContactHelper.java | 133 +++++++++++ .../sbus/internal/helper/SbusLuxHelper.java | 111 +++++++++ .../internal/helper/SbusMotionHelper.java | 110 +++++++++ .../resources/OH-INF/i18n/sbus.properties | 1 + 8 files changed, 501 insertions(+), 303 deletions(-) delete mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java index 5c04316ae1eae..f42311ffa21ee 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java @@ -122,6 +122,17 @@ protected void createChannel(String channelId, String channelTypeId) { updateThing(thingBuilder.build()); } + /** + * Public method for helpers to update channel states. + * This exposes the protected updateState method to helper classes. + * + * @param channelUID the channel to update + * @param state the new state value + */ + public void updateChannelState(ChannelUID channelUID, org.openhab.core.types.State state) { + updateState(channelUID, state); + } + /** * Start polling the device for updates based on the configured refresh interval. */ diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java deleted file mode 100644 index e1c232a192d58..0000000000000 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.sbus.internal.handler; - -import org.openhab.binding.sbus.BindingConstants; -import org.openhab.binding.sbus.internal.SbusService; -import org.openhab.binding.sbus.internal.config.SbusChannelConfig; -import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; -import org.openhab.core.library.types.OpenClosedType; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusRequest; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; -import ro.ciprianpascu.sbus.msg.SbusResponse; - -/** - * The {@link Sbus9in1ContactHandler} is responsible for handling contact sensor channels - * from 9-in-1 sensor devices. It supports reading dry contact states and can operate in both - * polling and listen-only modes. When refresh is set to 0, it operates in listen-only mode using - * MotionSensorStatusReport broadcasts from 9-in-1 devices. - * - * @author Ciprian Pascu - Initial contribution - */ -public class Sbus9in1ContactHandler extends AbstractSbusHandler { - - private final Logger logger = LoggerFactory.getLogger(Sbus9in1ContactHandler.class); - - public Sbus9in1ContactHandler(Thing thing) { - super(thing); - } - - @Override - protected void initializeChannels() { - // Create contact channels based on configured channels - for (Channel channel : getThing().getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { - // Contact channels are already defined in the thing configuration - logger.debug("Initialized contact channel: {}", channelUID.getId()); - } - } - } - - @Override - protected void pollDevice() { - final SbusService adapter = super.sbusAdapter; - if (adapter == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/error.device.adapter-not-initialized"); - return; - } - - try { - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - ReadNineInOneStatusResponse response = readNineInOneStatus(adapter, config.subnetId, config.id); - - // Update channel states from response - updateChannelStatesFromResponse(response); - updateStatus(ThingStatus.ONLINE); - } catch (IllegalStateException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/error.device.communication"); - logger.warn("Error polling 9-in-1 contact sensor device {}: {}", getThing().getUID(), e.getMessage()); - } - } - - /** - * Update channel states based on sensor response data. - * - * @param response the sensor response containing contact data - */ - private void updateChannelStatesFromResponse(ReadNineInOneStatusResponse response) { - // Update contact channels from 9-in-1 response - for (Channel channel : getThing().getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { - SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); - - // Use channelNumber to determine which dry contact (1 or 2, default to 1) - int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; - boolean contactState = false; - - if (channelNumber == 1) { - contactState = response.getDryContact1Status() > 0; - } else if (channelNumber == 2) { - contactState = response.getDryContact2Status() > 0; - } - - OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; - updateState(channelUID, state); - - logger.debug("Updated 9-in-1 contact channel {} (number {}) state: {}", channelUID.getId(), - channelNumber, state); - } - } - } - - /** - * Update channel states based on motion sensor status report data. - * - * @param report the motion sensor status report containing contact data - */ - private void updateChannelStatesFromReport(MotionSensorStatusReport report) { - // Update contact channels from motion sensor status report - for (Channel channel : getThing().getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { - SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); - - // Use channelNumber to determine which dry contact (1 or 2, default to 1) - int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; - boolean contactState = false; - - if (channelNumber == 1) { - contactState = report.getDryContactStatus(0) > 0; // First dry contact (index 0) - } else if (channelNumber == 2) { - contactState = report.getDryContactStatus(1) > 0; // Second dry contact (index 1) - } - - OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; - updateState(channelUID, state); - - logger.debug("Updated 9-in-1 contact channel {} (number {}) state from report: {}", channelUID.getId(), - channelNumber, state); - } - } - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - // 9-in-1 contact sensors are read-only devices, no commands to handle - logger.debug("9-in-1 contact sensor is read-only, ignoring command {} for channel {}", command, channelUID); - } - - /** - * Reads 9-in-1 sensor status from an SBUS device. - * - * @param adapter the SBUS service adapter - * @param subnetId the subnet ID of the device - * @param deviceId the device ID - * @return ReadNineInOneStatusResponse containing sensor data - * @throws IllegalStateException if the SBUS transaction fails - */ - private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int subnetId, int deviceId) - throws IllegalStateException { - ReadNineInOneStatusRequest request = new ReadNineInOneStatusRequest(); - request.setSubnetID(subnetId); - request.setUnitID(deviceId); - - SbusResponse response = adapter.executeTransaction(request); - if (!(response instanceof ReadNineInOneStatusResponse statusResponse)) { - throw new IllegalStateException( - "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); - } - - return statusResponse; - } - - // Async Message Handling - - @Override - protected void processAsyncMessage(SbusResponse response) { - try { - if (response instanceof MotionSensorStatusReport report) { - // Process motion sensor status report (0x02CA broadcast) - updateChannelStatesFromReport(report); - updateStatus(ThingStatus.ONLINE); - logger.debug("Processed async motion sensor status report for 9-in-1 contact handler {}", - getThing().getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // Process 9-in-1 status response (0xDB01) - updateChannelStatesFromResponse(statusResponse); - updateStatus(ThingStatus.ONLINE); - logger.debug("Processed async 9-in-1 status response for 9-in-1 contact handler {}", - getThing().getUID()); - } - } catch (IllegalStateException | IllegalArgumentException e) { - logger.warn("Error processing async message in 9-in-1 contact sensor handler {}: {}", getThing().getUID(), - e.getMessage()); - } - } - - @Override - protected boolean isMessageRelevant(SbusResponse response) { - if (response instanceof MotionSensorStatusReport) { - // Motion sensor status reports are broadcast messages (to FF:FF) - // They are relevant if they come from our device - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - return response.getSourceSubnetID() == config.subnetId && response.getSourceUnitID() == config.id; - } else if (response instanceof ReadNineInOneStatusResponse) { - // Check if the response is for this device based on subnet and unit ID - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; - } - return false; - } -} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java index 7322c9df69043..45cd42a782f1d 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java @@ -15,6 +15,9 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; +import org.openhab.binding.sbus.internal.helper.SbusContactHelper; +import org.openhab.binding.sbus.internal.helper.SbusLuxHelper; +import org.openhab.binding.sbus.internal.helper.SbusMotionHelper; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -41,10 +44,10 @@ public class Sbus9in1SensorsHandler extends AbstractSbusHandler { private final Logger logger = LoggerFactory.getLogger(Sbus9in1SensorsHandler.class); - // Specialized handlers for different sensor types - private @Nullable Sbus9in1ContactHandler contactHandler; - private @Nullable SbusMotionSensorHandler motionHandler; - private @Nullable SbusLuxSensorHandler luxHandler; + // Specialized helpers for different sensor types + private @Nullable SbusContactHelper contactHelper; + private @Nullable SbusMotionHelper motionHelper; + private @Nullable SbusLuxHelper luxHelper; public Sbus9in1SensorsHandler(Thing thing) { super(thing); @@ -52,18 +55,18 @@ public Sbus9in1SensorsHandler(Thing thing) { @Override protected void initializeChannels() { - // Create specialized handlers based on configured channels - createSpecializedHandlers(); + // Create specialized helpers based on configured channels + createSpecializedHelpers(); - // Initialize channels for each specialized handler - if (contactHandler != null) { - contactHandler.initializeChannels(); + // Initialize helpers + if (contactHelper != null) { + contactHelper.initialize(); } - if (motionHandler != null) { - motionHandler.initializeChannels(); + if (motionHelper != null) { + motionHelper.initialize(); } - if (luxHandler != null) { - luxHandler.initializeChannels(); + if (luxHelper != null) { + luxHelper.initialize(); } } @@ -80,8 +83,8 @@ protected void pollDevice() { SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); ReadNineInOneStatusResponse response = readNineInOneStatus(adapter, config.subnetId, config.id); - // Route the response to all active specialized handlers - routeResponseToHandlers(response); + // Route the response to all active specialized helpers + routeMessageToHelpers(response); updateStatus(ThingStatus.ONLINE); } catch (IllegalStateException e) { @@ -101,13 +104,13 @@ public void handleCommand(ChannelUID channelUID, Command command) { protected void processAsyncMessage(SbusResponse response) { try { if (response instanceof MotionSensorStatusReport report) { - // Route motion sensor status report to all active handlers - routeReportToHandlers(report); + // Route motion sensor status report to all active helpers + routeMessageToHelpers(report); updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for sensor handler {}", getThing().getUID()); } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // Route 9-in-1 status response to all active handlers - routeResponseToHandlers(statusResponse); + // Route 9-in-1 status response to all active helpers + routeMessageToHelpers(statusResponse); updateStatus(ThingStatus.ONLINE); logger.debug("Processed async 9-in-1 status response for sensor handler {}", getThing().getUID()); } @@ -132,74 +135,51 @@ protected boolean isMessageRelevant(SbusResponse response) { } /** - * Create specialized handlers based on the configured channels. + * Create specialized helpers based on the configured channels. */ - private void createSpecializedHandlers() { - // Check configured channels and create appropriate handlers - for (org.openhab.core.thing.Channel channel : getThing().getChannels()) { - if (channel.getChannelTypeUID() != null) { - String channelType = channel.getChannelTypeUID().getId(); - - // Create contact handler for contact channels - if ("contact-channel".equals(channelType) && contactHandler == null) { - contactHandler = new Sbus9in1ContactHandler(getThing()); - logger.debug("Created 9-in-1 contact handler for sensor {}", getThing().getUID()); - } - - // Create motion handler for motion channels - if ("motion-channel".equals(channelType) && motionHandler == null) { - motionHandler = new SbusMotionSensorHandler(getThing()); - logger.debug("Created motion handler for sensor {}", getThing().getUID()); - } - - // Create lux handler for lux channels - if ("lux-channel".equals(channelType) && luxHandler == null) { - luxHandler = new SbusLuxSensorHandler(getThing()); - logger.debug("Created lux handler for sensor {}", getThing().getUID()); - } - } - } - } - - // Message Routing Methods - - /** - * Route ReadNineInOneStatusResponse to appropriate specialized handlers. - */ - private void routeResponseToHandlers(ReadNineInOneStatusResponse response) { - // Route to contact handler if it exists - if (contactHandler != null) { - contactHandler.processAsyncMessage(response); + private void createSpecializedHelpers() { + // Create contact helper if there are contact channels + SbusContactHelper tempContactHelper = new SbusContactHelper(getThing(), this); + if (tempContactHelper.hasRelevantChannels()) { + contactHelper = tempContactHelper; + logger.debug("Created contact helper for sensor {}", getThing().getUID()); } - // Route to motion handler if it exists - if (motionHandler != null) { - motionHandler.processAsyncMessage(response); + // Create motion helper if there are motion channels + SbusMotionHelper tempMotionHelper = new SbusMotionHelper(getThing(), this); + if (tempMotionHelper.hasRelevantChannels()) { + motionHelper = tempMotionHelper; + logger.debug("Created motion helper for sensor {}", getThing().getUID()); } - // Route to lux handler if it exists - if (luxHandler != null) { - luxHandler.processAsyncMessage(response); + // Create lux helper if there are lux channels + SbusLuxHelper tempLuxHelper = new SbusLuxHelper(getThing(), this); + if (tempLuxHelper.hasRelevantChannels()) { + luxHelper = tempLuxHelper; + logger.debug("Created lux helper for sensor {}", getThing().getUID()); } } + // Message Routing Methods + /** - * Route MotionSensorStatusReport to appropriate specialized handlers. + * Route SBUS async messages to appropriate specialized helpers. + * This method uses the generic processAsyncMessage() method in each helper. */ - private void routeReportToHandlers(MotionSensorStatusReport report) { - // Route to contact handler if it exists - if (contactHandler != null) { - contactHandler.processAsyncMessage(report); + private void routeMessageToHelpers(SbusResponse response) { + // Route to contact helper if it exists + if (contactHelper != null) { + contactHelper.processMessage(response); } - // Route to motion handler if it exists - if (motionHandler != null) { - motionHandler.processAsyncMessage(report); + // Route to motion helper if it exists + if (motionHelper != null) { + motionHelper.processMessage(response); } - // Route to lux handler if it exists - if (luxHandler != null) { - luxHandler.processAsyncMessage(report); + // Route to lux helper if it exists + if (luxHelper != null) { + luxHelper.processMessage(response); } } @@ -231,18 +211,18 @@ private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int @Override public void dispose() { - // Dispose specialized handlers - if (contactHandler != null) { - contactHandler.dispose(); - contactHandler = null; - } - if (motionHandler != null) { - motionHandler.dispose(); - motionHandler = null; - } - if (luxHandler != null) { - luxHandler.dispose(); - luxHandler = null; + // Dispose specialized helpers + if (contactHelper != null) { + contactHelper.dispose(); + contactHelper = null; + } + if (motionHelper != null) { + motionHelper.dispose(); + motionHelper = null; + } + if (luxHelper != null) { + luxHelper.dispose(); + luxHelper = null; } super.dispose(); diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java new file mode 100644 index 0000000000000..82cda8c6211fd --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.helper; + +import org.openhab.binding.sbus.internal.handler.AbstractSbusHandler; +import org.openhab.core.thing.Thing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ro.ciprianpascu.sbus.msg.SbusResponse; + +/** + * The {@link AbstractSbusHelper} is a base class for SBUS sensor helpers. + * Unlike full handlers, these helpers are lightweight processors that only handle + * data processing without managing their own lifecycle, polling, or message listening. + * They are managed by the main Sbus9in1SensorsHandler coordinator. + * + * @author Ciprian Pascu - Initial contribution + */ +public abstract class AbstractSbusHelper { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + protected final Thing thing; + protected final AbstractSbusHandler coordinator; + + public AbstractSbusHelper(Thing thing, AbstractSbusHandler coordinator) { + this.thing = thing; + this.coordinator = coordinator; + } + + /** + * Initialize the helper. This should set up any necessary state but + * should NOT register listeners or start polling jobs. + */ + public abstract void initialize(); + + /** + * Process an asynchronous SBUS message. + * This method should use pattern matching to handle different message types. + * + * @param response the SBUS response message to process + */ + public abstract void processMessage(SbusResponse response); + + /** + * Check if this helper handles any of the configured channels. + * + * @return true if this helper should be active for the current thing configuration + */ + public abstract boolean hasRelevantChannels(); + + /** + * Dispose any resources held by this helper. + * This should NOT dispose the thing or update thing status. + */ + public void dispose() { + // Default implementation does nothing + logger.debug("Disposed SBUS helper for {}", thing.getUID()); + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java new file mode 100644 index 0000000000000..0081eb674abee --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.helper; + +import org.openhab.binding.sbus.BindingConstants; +import org.openhab.binding.sbus.internal.config.SbusChannelConfig; +import org.openhab.binding.sbus.internal.handler.Sbus9in1SensorsHandler; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; + +import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; +import ro.ciprianpascu.sbus.msg.SbusResponse; + +/** + * The {@link SbusContactHelper} is a helper class for processing contact sensor channels + * from 9-in-1 sensor devices. It processes dry contact states from ReadNineInOneStatusResponse + * and MotionSensorStatusReport messages. This is a lightweight helper that does not manage + * its own lifecycle, polling, or message listening - it is coordinated by Sbus9in1SensorsHandler. + * + * @author Ciprian Pascu - Initial contribution + */ +public class SbusContactHelper extends AbstractSbusHelper { + + public SbusContactHelper(Thing thing, Sbus9in1SensorsHandler coordinator) { + super(thing, coordinator); + } + + @Override + public void initialize() { + // Initialize contact channel processing + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { + // Contact channels are already defined in the thing configuration + logger.debug("Initialized contact channel: {}", channelUID.getId()); + } + } + } + + @Override + public void processMessage(SbusResponse response) { + try { + if (response instanceof MotionSensorStatusReport report) { + processMotionSensorReport(report); + logger.debug("Processed async motion sensor status report for contact helper {}", thing.getUID()); + } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + process9in1Response(statusResponse); + logger.debug("Processed async 9-in-1 status response for contact helper {}", thing.getUID()); + } + } catch (IllegalStateException | IllegalArgumentException e) { + logger.warn("Error processing async message in contact helper {}: {}", thing.getUID(), e.getMessage()); + } + } + + @Override + public boolean hasRelevantChannels() { + for (Channel channel : thing.getChannels()) { + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { + return true; + } + } + return false; + } + + private void process9in1Response(ReadNineInOneStatusResponse response) { + // Update contact channels from 9-in-1 response + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + + // Use channelNumber to determine which dry contact (1 or 2, default to 1) + int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; + boolean contactState = false; + + if (channelNumber == 1) { + contactState = response.getDryContact1Status() > 0; + } else if (channelNumber == 2) { + contactState = response.getDryContact2Status() > 0; + } + + OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + coordinator.updateChannelState(channelUID, state); + + logger.debug("Updated 9-in-1 contact channel {} (number {}) state: {}", channelUID.getId(), + channelNumber, state); + } + } + } + + private void processMotionSensorReport(MotionSensorStatusReport report) { + // Update contact channels from motion sensor status report + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + + // Use channelNumber to determine which dry contact (1 or 2, default to 1) + int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; + boolean contactState = false; + + if (channelNumber == 1) { + contactState = report.getDryContactStatus(0) > 0; // First dry contact (index 0) + } else if (channelNumber == 2) { + contactState = report.getDryContactStatus(1) > 0; // Second dry contact (index 1) + } + + OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + coordinator.updateChannelState(channelUID, state); + + logger.debug("Updated 9-in-1 contact channel {} (number {}) state from report: {}", channelUID.getId(), + channelNumber, state); + } + } + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java new file mode 100644 index 0000000000000..8c2831b50b431 --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.helper; + +import org.openhab.binding.sbus.BindingConstants; +import org.openhab.binding.sbus.internal.handler.Sbus9in1SensorsHandler; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; + +import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; +import ro.ciprianpascu.sbus.msg.SbusResponse; + +/** + * The {@link SbusLuxHelper} is a helper class for processing lux sensor channels + * from 9-in-1 sensor devices. It processes lux values from ReadNineInOneStatusResponse + * and MotionSensorStatusReport messages. This is a lightweight helper that does not manage + * its own lifecycle, polling, or message listening - it is coordinated by Sbus9in1SensorsHandler. + * + * @author Ciprian Pascu - Initial contribution + */ +public class SbusLuxHelper extends AbstractSbusHelper { + + public SbusLuxHelper(Thing thing, Sbus9in1SensorsHandler coordinator) { + super(thing, coordinator); + } + + @Override + public void initialize() { + // Initialize lux channel processing + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { + // Lux channels are already defined in the thing configuration + logger.debug("Initialized lux channel: {}", channelUID.getId()); + } + } + } + + @Override + public void processMessage(SbusResponse response) { + try { + if (response instanceof MotionSensorStatusReport report) { + processMotionSensorReport(report); + logger.debug("Processed async motion sensor status report for lux helper {}", thing.getUID()); + } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + process9in1Response(statusResponse); + logger.debug("Processed async 9-in-1 status response for lux helper {}", thing.getUID()); + } + } catch (IllegalStateException | IllegalArgumentException e) { + logger.warn("Error processing async message in lux helper {}: {}", thing.getUID(), e.getMessage()); + } + } + + @Override + public boolean hasRelevantChannels() { + for (Channel channel : thing.getChannels()) { + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { + return true; + } + } + return false; + } + + public void process9in1Response(ReadNineInOneStatusResponse response) { + // Update lux channels from 9-in-1 response + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { + + int luxValue = response.getLuxValue(); + QuantityType state = new QuantityType<>(luxValue, Units.LUX); + coordinator.updateChannelState(channelUID, state); + + logger.debug("Updated 9-in-1 lux channel {} state: {} lux", channelUID.getId(), luxValue); + } + } + } + + public void processMotionSensorReport(MotionSensorStatusReport report) { + // Update lux channels from motion sensor status report + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { + + int luxValue = report.getLuxValue(); + QuantityType state = new QuantityType<>(luxValue, Units.LUX); + coordinator.updateChannelState(channelUID, state); + + logger.debug("Updated 9-in-1 lux channel {} state from report: {} lux", channelUID.getId(), luxValue); + } + } + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java new file mode 100644 index 0000000000000..bf3b1b94b6c89 --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.helper; + +import org.openhab.binding.sbus.BindingConstants; +import org.openhab.binding.sbus.internal.handler.Sbus9in1SensorsHandler; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; + +import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; +import ro.ciprianpascu.sbus.msg.SbusResponse; + +/** + * The {@link SbusMotionHelper} is a helper class for processing motion sensor channels + * from 9-in-1 sensor devices. It processes motion states from ReadNineInOneStatusResponse + * and MotionSensorStatusReport messages. This is a lightweight helper that does not manage + * its own lifecycle, polling, or message listening - it is coordinated by Sbus9in1SensorsHandler. + * + * @author Ciprian Pascu - Initial contribution + */ +public class SbusMotionHelper extends AbstractSbusHelper { + + public SbusMotionHelper(Thing thing, Sbus9in1SensorsHandler coordinator) { + super(thing, coordinator); + } + + @Override + public void initialize() { + // Initialize motion channel processing + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { + // Motion channels are already defined in the thing configuration + logger.debug("Initialized motion channel: {}", channelUID.getId()); + } + } + } + + @Override + public void processMessage(SbusResponse response) { + try { + if (response instanceof MotionSensorStatusReport report) { + processMotionSensorReport(report); + logger.debug("Processed async motion sensor status report for motion helper {}", thing.getUID()); + } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + process9in1Response(statusResponse); + logger.debug("Processed async 9-in-1 status response for motion helper {}", thing.getUID()); + } + } catch (IllegalStateException | IllegalArgumentException e) { + logger.warn("Error processing async message in motion helper {}: {}", thing.getUID(), e.getMessage()); + } + } + + @Override + public boolean hasRelevantChannels() { + for (Channel channel : thing.getChannels()) { + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { + return true; + } + } + return false; + } + + public void process9in1Response(ReadNineInOneStatusResponse response) { + // Update motion channels from 9-in-1 response + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { + + boolean motionDetected = response.getMotionStatus() > 0; + OnOffType state = motionDetected ? OnOffType.ON : OnOffType.OFF; + coordinator.updateChannelState(channelUID, state); + + logger.debug("Updated 9-in-1 motion channel {} state: {}", channelUID.getId(), state); + } + } + } + + public void processMotionSensorReport(MotionSensorStatusReport report) { + // Update motion channels from motion sensor status report + for (Channel channel : thing.getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { + + boolean motionDetected = report.getMotionStatus() > 0; + OnOffType state = motionDetected ? OnOffType.ON : OnOffType.OFF; + coordinator.updateChannelState(channelUID, state); + + logger.debug("Updated 9-in-1 motion channel {} state from report: {}", channelUID.getId(), state); + } + } + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index c8984bbed8d26..8c1f02bfa62ea 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -129,6 +129,7 @@ error.device.bridge-not-initialized = Bridge connection not initialized error.channel.invalid-number = Channel {0} has invalid channel number configuration error.rgbw.too-many-switches = Only one switch channel is allowed for RGBW thing {0} error.device.adapter-not-initialized = Sbus adapter not initialized +error.device.communication = Communication error with device error.device.read-state = Error reading device state error.device.send-command = Error sending command to device From 28830d2b612ecec53650d439512fa6a0a01e073c Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Tue, 30 Sep 2025 22:15:42 +0300 Subject: [PATCH 09/18] [sbus] More refactoring for the 9in1 thing Signed-off-by: Ciprian Pascu --- .../binding/sbus/internal/helper/AbstractSbusHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java index 82cda8c6211fd..4427d5d26d6df 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java @@ -23,7 +23,7 @@ * The {@link AbstractSbusHelper} is a base class for SBUS sensor helpers. * Unlike full handlers, these helpers are lightweight processors that only handle * data processing without managing their own lifecycle, polling, or message listening. - * They are managed by the main Sbus9in1SensorsHandler coordinator. + * They are managed by the main Sbus*Handler coordinator. * * @author Ciprian Pascu - Initial contribution */ From 5fad97bfee99019f3b8f7fd985dc3f6c6e27548c Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sat, 4 Oct 2025 14:16:26 +0300 Subject: [PATCH 10/18] [sbus] Documentation changes Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 24 +++++++++---------- .../resources/OH-INF/i18n/sbus.properties | 14 +++++++++++ .../resources/OH-INF/thing/thing-types.xml | 6 ++--- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index ac8df76281036..0da0638ec36a9 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -11,7 +11,7 @@ The binding supports various device types including RGB/RGBW controllers, temper - `temperature` - Temperature Sensors for monitoring environmental conditions - `switch` - Switch Controllers for basic on/off and dimming control - `contact-sensor` - Traditional Contact Sensors for monitoring open/closed states -- `multi-sensor` - Multi-Sensor (9-in-1) devices with motion, lux, and dry contact capabilities +- `multi-sensor` - Multi-Sensor devices with motion, lux, and dry contact capabilities ## Discovery @@ -48,10 +48,10 @@ All device types share the same basic configuration parameters: **Device Type Specific Notes:** -- **Traditional Contact Sensors (`contact-sensor`)**: Use ReadDryChannelsRequest protocol for simple contact monitoring -- **Multi-Sensor Devices (`multi-sensor`)**: Use ReadNineInOneStatusRequest protocol and support motion, lux, and dry contact channels from a single physical device +- **Contact Sensors (`contact-sensor`)**: For standalone contact/dry contact sensor devices that monitor open/closed states +- **Multi-Sensor Devices (`multi-sensor`)**: For multi-function sensor devices that combine motion detection, light level measurement, and dry contact monitoring in a single device -**Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where handlers only process asynchronous broadcast messages (MotionSensorStatusReport) without actively polling. This is particularly useful for 9-in-1 sensor devices that broadcast status updates. +**Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where the binding only processes broadcast messages from the device without actively polling. This is useful for devices that automatically broadcast their status updates. ## Channels @@ -89,7 +89,7 @@ The color channel of RGBW controllers supports these additional parameters: |:--------|:--------|:----------:|:----------------------------------------------------------| | contact | Contact | R | Contact state (OPEN/CLOSED) for traditional contact sensors | -### Multi-Sensor (9-in-1) Channels +### Multi-Sensor Channels Multi-sensor devices support multiple channel types from a single physical device: @@ -103,9 +103,9 @@ The contact channel supports these additional parameters: | Parameter | Type | Description | Default | Required | Advanced | |:--------------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| channelNumber | integer | The dry contact number on the 9-in-1 device (1 or 2) | 1 | no | no | +| channelNumber | integer | The dry contact number on the multi-sensor device | 1 | no | no | -**Note:** You can configure any combination of these channels on a single `multi-sensor` thing to match your 9-in-1 device capabilities. +**Note:** You can configure any combination of these channels on a single `multi-sensor` thing to match your device capabilities. ## Full Example @@ -143,7 +143,7 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type contact-channel : contact [ channelNumber=1 ] } - // 9-in-1 multi-sensor device with all capabilities + // multi-sensor device with all capabilities Thing multi-sensor multisensor1 [ id=85, refresh=0 ] { Channels: Type contact-channel : contact1 [ channelNumber=1 ] // First dry contact @@ -152,7 +152,7 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type lux-channel : lux // Light level } - // 9-in-1 sensor with only motion and lux (no contacts) + // multi-sensor with only motion and lux (no contacts) Thing multi-sensor motionlux1 [ id=86, refresh=0 ] { Channels: Type motion-channel : motion @@ -181,7 +181,7 @@ Switch rgbwPower "Power" (gLight) ["Switch", "Light"] // Traditional Contact Sensor Contact Door_Contact "Door [%s]" { channel="sbus:contact-sensor:mybridge:contact1:contact" } -// 9-in-1 Multi-Sensor Items +// Multi-Sensor Items Contact Sensor_Contact1 "Sensor Contact 1 [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:contact1" } Contact Sensor_Contact2 "Sensor Contact 2 [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:contact2" } Switch Motion_Sensor "Motion [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:motion" } @@ -227,7 +227,7 @@ The binding supports two distinct types of sensor devices: - **Channels**: Contact channels only - **Configuration**: Standard device configuration with channel numbers -#### Multi-Sensor (9-in-1) Devices (`multi-sensor`) +#### Multi-Sensor Devices (`multi-sensor`) - **Protocol**: Uses `ReadNineInOneStatusRequest/Response` and `MotionSensorStatusReport` broadcasts - **Use Case**: Advanced sensor devices combining multiple sensor types in one physical unit - **Channels**: Any combination of contact, motion, and lux channels @@ -239,7 +239,7 @@ The binding supports two distinct types of sensor devices: **Choosing the Right Type:** - Use `contact-sensor` for traditional, simple contact sensors -- Use `multi-sensor` for 9-in-1 devices that provide motion detection, light level sensing, and/or dry contact monitoring +- Use `multi-sensor` for devices that provide motion detection, light level sensing, and/or dry contact monitoring ### RGB vs. RGBW Mode diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index 8c1f02bfa62ea..294cf712f6e77 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -13,6 +13,8 @@ thing-type.sbus.temperature.label = Sbus Temperature Sensor thing-type.sbus.temperature.description = Sbus temperature sensor device thing-type.sbus.contact.label = Sbus Contact Sensor thing-type.sbus.contact.description = Sbus contact sensor device +thing-type.sbus.multi-sensor.label = Sbus Multi-Sensor +thing-type.sbus.multi-sensor.description = Sbus sensor device with motion, lux, and dry contact capabilities thing-type.sbus.udp.label = Sbus UDP Bridge thing-type.sbus.udp.description = Endpoint for Sbus UDP slaves @@ -50,6 +52,14 @@ thing-type.config.sbus.temperature.subnetId.label = SubnetId thing-type.config.sbus.temperature.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. thing-type.config.sbus.temperature.subnetId.option.1 = 1 thing-type.config.sbus.temperature.subnetId.option.255 = 255 +thing-type.config.sbus.multi-sensor.id.label = Device ID +thing-type.config.sbus.multi-sensor.id.description = The ID of the Sbus device +thing-type.config.sbus.multi-sensor.refresh.label = Refresh Interval +thing-type.config.sbus.multi-sensor.refresh.description = Refresh interval in seconds (0 = listen-only mode for broadcast messages) +thing-type.config.sbus.multi-sensor.subnetId.label = SubnetId +thing-type.config.sbus.multi-sensor.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. +thing-type.config.sbus.multi-sensor.subnetId.option.1 = 1 +thing-type.config.sbus.multi-sensor.subnetId.option.255 = 255 thing-type.config.sbus.udp.host.label = IP Address or Hostname thing-type.config.sbus.udp.host.description = Network address of the device thing-type.config.sbus.udp.port.label = Port @@ -69,6 +79,10 @@ channel-type.sbus.temperature-channel.label = Temperature channel-type.sbus.temperature-channel.description = Temperature reading from the device channel-type.sbus.contact-channel.label = Contact State channel-type.sbus.contact-channel.description = Contact state (OPEN/CLOSED) +channel-type.sbus.motion-channel.label = Motion Detection +channel-type.sbus.motion-channel.description = Motion detection state (ON=motion detected, OFF=no motion) +channel-type.sbus.lux-channel.label = Light Level +channel-type.sbus.lux-channel.description = Light level in LUX units # channel types config diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index cf603f1636df6..ea07e194b2116 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -129,13 +129,13 @@ - + - - Sbus 9-in-1 sensor device with motion, lux, and dry contact capabilities + + Sbus multi-sensor device with motion, lux, and dry contact capabilities Sensor From 528176c92d8814230769b9a04c42386313a972be Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sat, 4 Oct 2025 14:24:30 +0300 Subject: [PATCH 11/18] [sbus] Documentation changes Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index 0da0638ec36a9..f1f96f034f9f1 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -36,9 +36,9 @@ The Sbus Bridge has the following configuration parameters: | port | integer | UDP port number | 6000 | no | no | | timeout | integer | Response timeout in milliseconds | 3000 | no | yes | -### Device Configuration +### Device Thing Configuration -All device types share the same basic configuration parameters: +All device thing types share the same basic configuration parameters: | Name | Type | Description | Default | Required | Advanced | |:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| @@ -46,7 +46,7 @@ All device types share the same basic configuration parameters: | id | integer | Device ID | N/A | yes | no | | refresh | integer | Refresh interval in seconds (0 = listen-only mode) | 30 | no | yes | -**Device Type Specific Notes:** +**Thing Type Specific Notes:** - **Contact Sensors (`contact-sensor`)**: For standalone contact/dry contact sensor devices that monitor open/closed states - **Multi-Sensor Devices (`multi-sensor`)**: For multi-function sensor devices that combine motion detection, light level measurement, and dry contact monitoring in a single device From 35a7fc87879124b1582100922c2231bfe75b44f0 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sat, 18 Oct 2025 05:09:55 +0300 Subject: [PATCH 12/18] [sbus] treat sensors as first class things. removed multi-sensor concept Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 148 +++++------ .../binding/sbus/BindingConstants.java | 3 +- .../internal/config/ContactSensorType.java | 63 +++++ .../internal/config/SbusContactConfig.java | 38 +++ .../handler/Sbus9in1ContactHandler.java | 211 ++++++++++++++++ .../handler/Sbus9in1SensorsHandler.java | 230 ------------------ .../internal/handler/SbusHandlerFactory.java | 27 +- .../internal/helper/AbstractSbusHelper.java | 70 ------ .../internal/helper/SbusContactHelper.java | 133 ---------- .../sbus/internal/helper/SbusLuxHelper.java | 111 --------- .../internal/helper/SbusMotionHelper.java | 110 --------- .../resources/OH-INF/i18n/sbus.properties | 28 ++- .../resources/OH-INF/thing/thing-types.xml | 73 +++++- 13 files changed, 501 insertions(+), 744 deletions(-) create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java create mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java delete mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java delete mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java delete mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java delete mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java delete mode 100644 bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index f1f96f034f9f1..7791613367458 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -1,8 +1,8 @@ # Sbus Binding -This binding integrates Sbus devices with openHAB, allowing control and monitoring of Sbus-compatible devices over UDP. -Sbus is a protocol used for home automation devices that communicate over UDP networks. -The binding supports various device types including RGB/RGBW controllers, temperature sensors, and switch controllers. +This binding integrates Sbus-compatible hardware with openHAB, allowing control and monitoring over UDP networks. +Sbus is a protocol used for home automation that communicates via UDP broadcast messages. +The binding supports various thing types including RGB/RGBW controllers, temperature sensors, switch controllers, and multiple sensor types. ## Supported Things @@ -10,12 +10,14 @@ The binding supports various device types including RGB/RGBW controllers, temper - `rgbw` - RGB/RGBW Controllers for color and brightness control - `temperature` - Temperature Sensors for monitoring environmental conditions - `switch` - Switch Controllers for basic on/off and dimming control -- `contact-sensor` - Traditional Contact Sensors for monitoring open/closed states -- `multi-sensor` - Multi-Sensor devices with motion, lux, and dry contact capabilities +- `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both MIX24 and 9-in-1 sensor types) +- `motion-sensor` - Motion Sensors for detecting movement +- `lux-sensor` - Light Level Sensors for measuring illuminance +- `multi-sensor` - (Deprecated) Multi-Sensor things with motion, lux, and dry contact capabilities ## Discovery -Sbus devices communicate via UDP broadcast, but manual configuration is required to set up the devices in openHAB. +Sbus hardware communicates via UDP broadcast, but manual configuration is required to set up things in openHAB. Auto-discovery is not supported at this moment. ## Binding Configuration @@ -32,26 +34,29 @@ The Sbus Bridge has the following configuration parameters: | Name | Type | Description | Default | Required | Advanced | |:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| host | text | IP address of the Sbus device (typically broadcast) | N/A | yes | no | +| host | text | IP address for Sbus communication (typically broadcast) | N/A | yes | no | | port | integer | UDP port number | 6000 | no | no | | timeout | integer | Response timeout in milliseconds | 3000 | no | yes | -### Device Thing Configuration +### Thing Configuration -All device thing types share the same basic configuration parameters: +Most thing types share the same basic configuration parameters: | Name | Type | Description | Default | Required | Advanced | |:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| subnetId| integer | Subnet ID the device is part of | N/A | yes | no | -| id | integer | Device ID | N/A | yes | no | +| subnetId| integer | Subnet ID | 1 | yes | no | +| id | integer | Unit ID | N/A | yes | no | | refresh | integer | Refresh interval in seconds (0 = listen-only mode) | 30 | no | yes | -**Thing Type Specific Notes:** +**Contact Sensor Additional Configuration:** + +The `contact-sensor` thing type has an additional `type` parameter: -- **Contact Sensors (`contact-sensor`)**: For standalone contact/dry contact sensor devices that monitor open/closed states -- **Multi-Sensor Devices (`multi-sensor`)**: For multi-function sensor devices that combine motion detection, light level measurement, and dry contact monitoring in a single device +| Name | Type | Description | Default | Required | Advanced | +|:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| +| type | text | Sensor type: `mix24` (traditional) or `9in1` (9-in-1 sensor) | mix24 | no | no | -**Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where the binding only processes broadcast messages from the device without actively polling. This is useful for devices that automatically broadcast their status updates. +**Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where the binding only processes broadcast messages without actively polling. This is useful for sensors that automatically broadcast their status updates. ## Channels @@ -66,7 +71,7 @@ The color channel of RGBW controllers supports these additional parameters: | Parameter | Type | Description | Default | Required | Advanced | |:------------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| channelNumber | integer | The physical channel number on the Sbus device | N/A | yes | no | +| channelNumber | integer | The physical channel number | N/A | yes | no | | enableWhite | boolean | Controls the white component support for RGB palette | true | no | yes | ### Temperature Sensor Channels @@ -83,29 +88,29 @@ The color channel of RGBW controllers supports these additional parameters: | dimmer | Dimmer | RW | ON/OFF state with timer transition | | paired | Rollershutter | RW | UP/DOWN/STOP control for two paired channels (e.g., rollershutters)| -### Contact Sensor Channels (Traditional) +### Contact Sensor Channels | Channel | Type | Read/Write | Description | |:--------|:--------|:----------:|:----------------------------------------------------------| -| contact | Contact | R | Contact state (OPEN/CLOSED) for traditional contact sensors | +| contact | Contact | R | Contact state (OPEN/CLOSED) | -### Multi-Sensor Channels +The contact channel supports a `channelNumber` parameter: -Multi-sensor devices support multiple channel types from a single physical device: +| Parameter | Type | Description | Default | Required | Advanced | +|:--------------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| +| channelNumber | integer | The dry contact number | 1 | no | no | + +### Motion Sensor Channels | Channel | Type | Read/Write | Description | |:--------|:--------|:----------:|:----------------------------------------------------------| -| contact | Contact | R | Dry contact state (OPEN/CLOSED) - use channelNumber parameter to specify which contact (1 or 2) | | motion | Switch | R | Motion detection state (ON=motion detected, OFF=no motion)| -| lux | Number | R | Light level in LUX units | -The contact channel supports these additional parameters: +### Lux Sensor Channels -| Parameter | Type | Description | Default | Required | Advanced | -|:--------------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| channelNumber | integer | The dry contact number on the multi-sensor device | 1 | no | no | - -**Note:** You can configure any combination of these channels on a single `multi-sensor` thing to match your device capabilities. +| Channel | Type | Read/Write | Description | +|:--------|:--------|:----------:|:----------------------------------------------------------| +| lux | Number | R | Light level in LUX units | ## Full Example @@ -137,25 +142,19 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type paired-channel : third_switch [ channelNumber=3, pairedChannelNumber=4 ] } - // Traditional contact sensor - Thing contact-sensor contact1 [ id=80, refresh=30 ] { + Thing contact-sensor contact1 [ type="mix24", id=80, refresh=30 ] { Channels: Type contact-channel : contact [ channelNumber=1 ] } + - // multi-sensor device with all capabilities - Thing multi-sensor multisensor1 [ id=85, refresh=0 ] { + Thing motion-sensor sensor_motion [ id=85, refresh=0 ] { Channels: - Type contact-channel : contact1 [ channelNumber=1 ] // First dry contact - Type contact-channel : contact2 [ channelNumber=2 ] // Second dry contact - Type motion-channel : motion // Motion detection - Type lux-channel : lux // Light level + Type motion-channel : motion } - // multi-sensor with only motion and lux (no contacts) - Thing multi-sensor motionlux1 [ id=86, refresh=0 ] { + Thing lux-sensor sensor_lux [ id=85, refresh=0 ] { Channels: - Type motion-channel : motion Type lux-channel : lux } } @@ -178,18 +177,14 @@ Group gLight "RGBW Light" ["Lighting"] Color rgbwColor "Color" (gLight) ["Control", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:color" } Switch rgbwPower "Power" (gLight) ["Switch", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:power" } -// Traditional Contact Sensor +// Traditional MIX24 Contact Sensor Contact Door_Contact "Door [%s]" { channel="sbus:contact-sensor:mybridge:contact1:contact" } -// Multi-Sensor Items -Contact Sensor_Contact1 "Sensor Contact 1 [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:contact1" } -Contact Sensor_Contact2 "Sensor Contact 2 [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:contact2" } -Switch Motion_Sensor "Motion [%s]" { channel="sbus:multi-sensor:mybridge:multisensor1:motion" } -Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:multi-sensor:mybridge:multisensor1:lux" } - -// Motion and Lux only sensor -Switch Motion_Only "Motion [%s]" { channel="sbus:multi-sensor:mybridge:motionlux1:motion" } -Number Lux_Only "Light Level [%.0f lux]" { channel="sbus:multi-sensor:mybridge:motionlux1:lux" } +// 9-in-1 Sensor Items (from physical sensor with ID 85) +Contact Sensor_Contact1 "Sensor Contact 1 [%s]" { channel="sbus:contact-sensor:mybridge:sensor_contact:contact1" } +Contact Sensor_Contact2 "Sensor Contact 2 [%s]" { channel="sbus:contact-sensor:mybridge:sensor_contact:contact2" } +Switch Motion_Sensor "Motion [%s]" { channel="sbus:motion-sensor:mybridge:sensor_motion:motion" } +Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:lux-sensor:mybridge:sensor_lux:lux" } ``` ### Sitemap Configuration @@ -217,29 +212,38 @@ sitemap sbus label="Sbus Demo" ## Usage Notes -### Sensor Device Types - -The binding supports two distinct types of sensor devices: - -#### Traditional Contact Sensors (`contact-sensor`) -- **Protocol**: Uses `ReadDryChannelsRequest/Response` -- **Use Case**: Simple contact sensors with basic open/closed detection -- **Channels**: Contact channels only -- **Configuration**: Standard device configuration with channel numbers - -#### Multi-Sensor Devices (`multi-sensor`) -- **Protocol**: Uses `ReadNineInOneStatusRequest/Response` and `MotionSensorStatusReport` broadcasts -- **Use Case**: Advanced sensor devices combining multiple sensor types in one physical unit -- **Channels**: Any combination of contact, motion, and lux channels -- **Configuration**: Single device configuration with multiple channel types -- **Benefits**: - - Single polling job for all sensor data - - Efficient communication with one physical device - - Supports broadcast status updates for real-time responsiveness - -**Choosing the Right Type:** -- Use `contact-sensor` for traditional, simple contact sensors -- Use `multi-sensor` for devices that provide motion detection, light level sensing, and/or dry contact monitoring +### 9-in-1 Sensor Configuration + +9-in-1 sensors are multi-function sensors that combine motion detection, light level measurement, and dry contact monitoring in a single physical unit. To configure a 9-in-1 sensor in openHAB, you need to create **three separate things** that all reference the same physical sensor: + +1. **contact-sensor** (type: `9in1`) - For dry contact channels +2. **motion-sensor** - For motion detection +3. **lux-sensor** - For light level sensing + +All three things must use the **same subnet ID and unit ID** to represent the same physical sensor. + +**Example for a 9-in-1 sensor with ID 85:** + +```java +Thing contact-sensor sensor_contact [ type="9in1", id=85, refresh=0 ] +Thing motion-sensor sensor_motion [ id=85, refresh=0 ] +Thing lux-sensor sensor_lux [ id=85, refresh=0 ] +``` + +**Benefits of this approach:** +- Clear separation of concerns - each thing handles one sensor type +- Flexible configuration - only create the things you need +- Follows openHAB best practices for thing organization +- Each thing can be configured independently + +### Contact Sensor Types + +The `contact-sensor` thing type supports two different sensor types via the `type` parameter: + +- **`mix24`** (default): Traditional MIX24 contact sensors using `ReadDryChannelsRequest/Response` protocol +- **`9in1`**: 9-in-1 sensor dry contacts using `ReadNineInOneStatusRequest/Response` protocol + +Choose the appropriate type based on your hardware. ### RGB vs. RGBW Mode diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java index 796accacf8e67..32870e6cfdf2d 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java @@ -37,7 +37,8 @@ private BindingConstants() { public static final ThingTypeUID THING_TYPE_TEMPERATURE = new ThingTypeUID(BINDING_ID, "temperature"); public static final ThingTypeUID THING_TYPE_RGBW = new ThingTypeUID(BINDING_ID, "rgbw"); public static final ThingTypeUID THING_TYPE_CONTACT_SENSOR = new ThingTypeUID(BINDING_ID, "contact-sensor"); - public static final ThingTypeUID THING_TYPE_MULTI_SENSOR = new ThingTypeUID(BINDING_ID, "multi-sensor"); + public static final ThingTypeUID THING_TYPE_MOTION_SENSOR = new ThingTypeUID(BINDING_ID, "motion-sensor"); + public static final ThingTypeUID THING_TYPE_LUX_SENSOR = new ThingTypeUID(BINDING_ID, "lux-sensor"); // Channel IDs for Switch Device public static final String CHANNEL_SWITCH_STATE = "state"; diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java new file mode 100644 index 0000000000000..8f77fe49030d4 --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ContactSensorType} enum defines the types of contact sensors supported by the binding. + * + * @author Ciprian Pascu - Initial contribution + */ +@NonNullByDefault +public enum ContactSensorType { + /** + * Traditional MIX24 contact sensor using ReadDryChannelsRequest/Response protocol + */ + MIX24("mix24"), + + /** + * 9-in-1 sensor with dry contacts using ReadNineInOneStatusRequest/Response protocol + */ + NINE_IN_ONE("9in1"); + + private final String configValue; + + ContactSensorType(String configValue) { + this.configValue = configValue; + } + + /** + * Get the configuration value for this sensor type. + * + * @return the configuration value + */ + public String getConfigValue() { + return configValue; + } + + /** + * Parse a configuration value into a ContactSensorType. + * + * @param value the configuration value + * @return the corresponding ContactSensorType, or MIX24 if not recognized + */ + public static ContactSensorType fromConfigValue(String value) { + for (ContactSensorType type : values()) { + if (type.configValue.equals(value)) { + return type; + } + } + return MIX24; // Default to MIX24 for backward compatibility or null values + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java new file mode 100644 index 0000000000000..5c39e5839609a --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link SbusContactConfig} class extends device configuration with contact sensor specific parameters. + * + * @author Ciprian Pascu - Initial contribution + */ +@NonNullByDefault +public class SbusContactConfig extends SbusDeviceConfig { + /** + * Sensor type for contact sensor. + * String value is used for configuration binding, converted to enum via ContactSensorType.fromConfigValue() + */ + public String type = ContactSensorType.MIX24.getConfigValue(); // Default to traditional behavior + + /** + * Get the sensor type as an enum. + * + * @return the ContactSensorType enum value + */ + public ContactSensorType getSensorType() { + return ContactSensorType.fromConfigValue(type); + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java new file mode 100644 index 0000000000000..b56a93f7fc3a3 --- /dev/null +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.sbus.internal.handler; + +import org.openhab.binding.sbus.BindingConstants; +import org.openhab.binding.sbus.internal.SbusService; +import org.openhab.binding.sbus.internal.config.SbusChannelConfig; +import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusRequest; +import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; +import ro.ciprianpascu.sbus.msg.SbusResponse; + +/** + * The {@link Sbus9in1ContactHandler} handles 9-in-1 sensor devices with dry contact channels. + * It uses ReadNineInOneStatusRequest/Response protocol for polling and processes + * MotionSensorStatusReport for async updates. + * + * @author Ciprian Pascu - Initial contribution + */ +public class Sbus9in1ContactHandler extends AbstractSbusHandler { + + private final Logger logger = LoggerFactory.getLogger(Sbus9in1ContactHandler.class); + + public Sbus9in1ContactHandler(Thing thing) { + super(thing); + } + + @Override + protected void initializeChannels() { + // Validate channel configuration + for (Channel channel : getThing().getChannels()) { + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { + logger.debug("Initialized 9-in-1 contact channel: {}", channel.getUID().getId()); + } + } + } + + @Override + protected void pollDevice() { + final SbusService adapter = super.sbusAdapter; + if (adapter == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.device.adapter-not-initialized"); + return; + } + + try { + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + ReadNineInOneStatusResponse response = readNineInOneStatus(adapter, config.subnetId, config.id); + + // Update all contact channels from the response + updateContactChannelsFromResponse(response); + + updateStatus(ThingStatus.ONLINE); + } catch (IllegalStateException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/error.device.communication"); + logger.warn("Error polling 9-in-1 contact sensor {}: {}", getThing().getUID(), e.getMessage()); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // Contact sensors are read-only + logger.debug("9-in-1 contact sensor is read-only, ignoring command {} for channel {}", command, channelUID); + } + + @Override + protected void processAsyncMessage(SbusResponse response) { + try { + if (response instanceof MotionSensorStatusReport report) { + // Process motion sensor status report for dry contact updates + updateContactChannelsFromReport(report); + updateStatus(ThingStatus.ONLINE); + logger.debug("Processed async motion sensor status report for 9-in-1 contact handler {}", + getThing().getUID()); + } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + // Process 9-in-1 status response + updateContactChannelsFromResponse(statusResponse); + updateStatus(ThingStatus.ONLINE); + logger.debug("Processed async 9-in-1 status response for contact handler {}", getThing().getUID()); + } + } catch (IllegalStateException | IllegalArgumentException e) { + logger.warn("Error processing async message in 9-in-1 contact handler {}: {}", getThing().getUID(), + e.getMessage()); + } + } + + @Override + protected boolean isMessageRelevant(SbusResponse response) { + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + + if (response instanceof MotionSensorStatusReport) { + // Motion sensor status reports are broadcast messages + // They are relevant if they come from our device + return response.getSourceSubnetID() == config.subnetId && response.getSourceUnitID() == config.id; + } else if (response instanceof ReadNineInOneStatusResponse) { + // Check if the response is for this device + return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; + } + return false; + } + + /** + * Read 9-in-1 sensor status from device. + * + * @param adapter the SBUS service adapter + * @param subnetId the subnet ID + * @param deviceId the device ID + * @return ReadNineInOneStatusResponse + * @throws IllegalStateException if communication fails + */ + private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int subnetId, int deviceId) + throws IllegalStateException { + ReadNineInOneStatusRequest request = new ReadNineInOneStatusRequest(); + request.setSubnetID(subnetId); + request.setUnitID(deviceId); + + SbusResponse response = adapter.executeTransaction(request); + if (!(response instanceof ReadNineInOneStatusResponse statusResponse)) { + throw new IllegalStateException( + "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); + } + + return statusResponse; + } + + /** + * Update contact channels from ReadNineInOneStatusResponse. + * + * @param response the 9-in-1 status response + */ + private void updateContactChannelsFromResponse(ReadNineInOneStatusResponse response) { + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + + // Use channelNumber to determine which dry contact (1 or 2, default to 1) + int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; + boolean contactState = false; + + if (channelNumber == 1) { + contactState = response.getDryContact1Status() > 0; + } else if (channelNumber == 2) { + contactState = response.getDryContact2Status() > 0; + } + + OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + updateState(channelUID, state); + + logger.debug("Updated 9-in-1 contact channel {} (number {}) state: {}", channelUID.getId(), + channelNumber, state); + } + } + } + + /** + * Update contact channels from MotionSensorStatusReport. + * + * @param report the motion sensor status report + */ + private void updateContactChannelsFromReport(MotionSensorStatusReport report) { + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (channel.getChannelTypeUID() != null + && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + + // Use channelNumber to determine which dry contact (1 or 2, default to 1) + int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; + boolean contactState = false; + + if (channelNumber == 1) { + contactState = report.getDryContactStatus(0) > 0; // First dry contact (index 0) + } else if (channelNumber == 2) { + contactState = report.getDryContactStatus(1) > 0; // Second dry contact (index 1) + } + + OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + updateState(channelUID, state); + + logger.debug("Updated 9-in-1 contact channel {} (number {}) state from report: {}", channelUID.getId(), + channelNumber, state); + } + } + } +} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java deleted file mode 100644 index 45cd42a782f1d..0000000000000 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1SensorsHandler.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.sbus.internal.handler; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.sbus.internal.SbusService; -import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; -import org.openhab.binding.sbus.internal.helper.SbusContactHelper; -import org.openhab.binding.sbus.internal.helper.SbusLuxHelper; -import org.openhab.binding.sbus.internal.helper.SbusMotionHelper; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusRequest; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; -import ro.ciprianpascu.sbus.msg.SbusResponse; - -/** - * The {@link Sbus9in1SensorsHandler} is responsible for coordinating 9-in-1 sensor devices. - * It acts as a central coordinator that polls the 9-in-1 sensor and routes the data - * to specialized handlers (contact, motion, lux) based on configured channels. - * This handler manages the communication with the physical device while delegating - * channel-specific processing to the appropriate specialized handlers. - * - * @author Ciprian Pascu - Initial contribution - */ -public class Sbus9in1SensorsHandler extends AbstractSbusHandler { - - private final Logger logger = LoggerFactory.getLogger(Sbus9in1SensorsHandler.class); - - // Specialized helpers for different sensor types - private @Nullable SbusContactHelper contactHelper; - private @Nullable SbusMotionHelper motionHelper; - private @Nullable SbusLuxHelper luxHelper; - - public Sbus9in1SensorsHandler(Thing thing) { - super(thing); - } - - @Override - protected void initializeChannels() { - // Create specialized helpers based on configured channels - createSpecializedHelpers(); - - // Initialize helpers - if (contactHelper != null) { - contactHelper.initialize(); - } - if (motionHelper != null) { - motionHelper.initialize(); - } - if (luxHelper != null) { - luxHelper.initialize(); - } - } - - @Override - protected void pollDevice() { - final SbusService adapter = super.sbusAdapter; - if (adapter == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/error.device.adapter-not-initialized"); - return; - } - - try { - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - ReadNineInOneStatusResponse response = readNineInOneStatus(adapter, config.subnetId, config.id); - - // Route the response to all active specialized helpers - routeMessageToHelpers(response); - - updateStatus(ThingStatus.ONLINE); - } catch (IllegalStateException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/error.device.communication"); - logger.warn("Error polling 9-in-1 sensor device {}: {}", getThing().getUID(), e.getMessage()); - } - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - // 9-in-1 sensors are read-only devices, no commands to handle - logger.debug("9-in-1 sensor is read-only, ignoring command {} for channel {}", command, channelUID); - } - - @Override - protected void processAsyncMessage(SbusResponse response) { - try { - if (response instanceof MotionSensorStatusReport report) { - // Route motion sensor status report to all active helpers - routeMessageToHelpers(report); - updateStatus(ThingStatus.ONLINE); - logger.debug("Processed async motion sensor status report for sensor handler {}", getThing().getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // Route 9-in-1 status response to all active helpers - routeMessageToHelpers(statusResponse); - updateStatus(ThingStatus.ONLINE); - logger.debug("Processed async 9-in-1 status response for sensor handler {}", getThing().getUID()); - } - } catch (IllegalStateException | IllegalArgumentException e) { - logger.warn("Error processing async message in sensor handler {}: {}", getThing().getUID(), e.getMessage()); - } - } - - @Override - protected boolean isMessageRelevant(SbusResponse response) { - if (response instanceof MotionSensorStatusReport) { - // Motion sensor status reports are broadcast messages (to FF:FF) - // They are relevant if they come from our device - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - return response.getSourceSubnetID() == config.subnetId && response.getSourceUnitID() == config.id; - } else if (response instanceof ReadNineInOneStatusResponse) { - // Check if the response is for this device based on subnet and unit ID - SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); - return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; - } - return false; - } - - /** - * Create specialized helpers based on the configured channels. - */ - private void createSpecializedHelpers() { - // Create contact helper if there are contact channels - SbusContactHelper tempContactHelper = new SbusContactHelper(getThing(), this); - if (tempContactHelper.hasRelevantChannels()) { - contactHelper = tempContactHelper; - logger.debug("Created contact helper for sensor {}", getThing().getUID()); - } - - // Create motion helper if there are motion channels - SbusMotionHelper tempMotionHelper = new SbusMotionHelper(getThing(), this); - if (tempMotionHelper.hasRelevantChannels()) { - motionHelper = tempMotionHelper; - logger.debug("Created motion helper for sensor {}", getThing().getUID()); - } - - // Create lux helper if there are lux channels - SbusLuxHelper tempLuxHelper = new SbusLuxHelper(getThing(), this); - if (tempLuxHelper.hasRelevantChannels()) { - luxHelper = tempLuxHelper; - logger.debug("Created lux helper for sensor {}", getThing().getUID()); - } - } - - // Message Routing Methods - - /** - * Route SBUS async messages to appropriate specialized helpers. - * This method uses the generic processAsyncMessage() method in each helper. - */ - private void routeMessageToHelpers(SbusResponse response) { - // Route to contact helper if it exists - if (contactHelper != null) { - contactHelper.processMessage(response); - } - - // Route to motion helper if it exists - if (motionHelper != null) { - motionHelper.processMessage(response); - } - - // Route to lux helper if it exists - if (luxHelper != null) { - luxHelper.processMessage(response); - } - } - - // SBUS Protocol Methods - - /** - * Reads 9-in-1 sensor status from an SBUS device. - * - * @param adapter the SBUS service adapter - * @param subnetId the subnet ID of the device - * @param deviceId the device ID - * @return ReadNineInOneStatusResponse containing sensor data - * @throws IllegalStateException if the SBUS transaction fails - */ - private ReadNineInOneStatusResponse readNineInOneStatus(SbusService adapter, int subnetId, int deviceId) - throws IllegalStateException { - ReadNineInOneStatusRequest request = new ReadNineInOneStatusRequest(); - request.setSubnetID(subnetId); - request.setUnitID(deviceId); - - SbusResponse response = adapter.executeTransaction(request); - if (!(response instanceof ReadNineInOneStatusResponse statusResponse)) { - throw new IllegalStateException( - "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); - } - - return statusResponse; - } - - @Override - public void dispose() { - // Dispose specialized helpers - if (contactHelper != null) { - contactHelper.dispose(); - contactHelper = null; - } - if (motionHelper != null) { - motionHelper.dispose(); - motionHelper = null; - } - if (luxHelper != null) { - luxHelper.dispose(); - luxHelper = null; - } - - super.dispose(); - } -} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java index d89b9baa0f4b6..7e62d5db960ee 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java @@ -18,6 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.sbus.internal.config.ContactSensorType; +import org.openhab.binding.sbus.internal.config.SbusContactConfig; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -40,7 +42,8 @@ public class SbusHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(SbusHandlerFactory.class); private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_UDP_BRIDGE, THING_TYPE_SWITCH, - THING_TYPE_TEMPERATURE, THING_TYPE_RGBW, THING_TYPE_CONTACT_SENSOR, THING_TYPE_MULTI_SENSOR); + THING_TYPE_TEMPERATURE, THING_TYPE_RGBW, THING_TYPE_CONTACT_SENSOR, THING_TYPE_MOTION_SENSOR, + THING_TYPE_LUX_SENSOR); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -66,11 +69,23 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { logger.debug("Creating Sbus RGBW handler for thing {}", thing.getUID()); return new SbusRgbwHandler(thing); } else if (thingTypeUID.equals(THING_TYPE_CONTACT_SENSOR)) { - logger.debug("Creating Sbus contact sensor handler for thing {}", thing.getUID()); - return new SbusContactHandler(thing); - } else if (thingTypeUID.equals(THING_TYPE_MULTI_SENSOR)) { - logger.debug("Creating Sbus multi-sensor handler for thing {}", thing.getUID()); - return new Sbus9in1SensorsHandler(thing); + // Determine which contact handler to create based on sensor type configuration + SbusContactConfig config = thing.getConfiguration().as(SbusContactConfig.class); + ContactSensorType sensorType = config.getSensorType(); + + if (sensorType == ContactSensorType.NINE_IN_ONE) { + logger.debug("Creating Sbus 9-in-1 contact sensor handler for thing {}", thing.getUID()); + return new Sbus9in1ContactHandler(thing); + } else { + logger.debug("Creating Sbus MIX24 contact sensor handler for thing {}", thing.getUID()); + return new SbusContactHandler(thing); + } + } else if (thingTypeUID.equals(THING_TYPE_MOTION_SENSOR)) { + logger.debug("Creating Sbus motion sensor handler for thing {}", thing.getUID()); + return new SbusMotionSensorHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_LUX_SENSOR)) { + logger.debug("Creating Sbus lux sensor handler for thing {}", thing.getUID()); + return new SbusLuxSensorHandler(thing); } logger.debug("Unknown thing type: {}", thingTypeUID); diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java deleted file mode 100644 index 4427d5d26d6df..0000000000000 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/AbstractSbusHelper.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.sbus.internal.helper; - -import org.openhab.binding.sbus.internal.handler.AbstractSbusHandler; -import org.openhab.core.thing.Thing; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ro.ciprianpascu.sbus.msg.SbusResponse; - -/** - * The {@link AbstractSbusHelper} is a base class for SBUS sensor helpers. - * Unlike full handlers, these helpers are lightweight processors that only handle - * data processing without managing their own lifecycle, polling, or message listening. - * They are managed by the main Sbus*Handler coordinator. - * - * @author Ciprian Pascu - Initial contribution - */ -public abstract class AbstractSbusHelper { - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - protected final Thing thing; - protected final AbstractSbusHandler coordinator; - - public AbstractSbusHelper(Thing thing, AbstractSbusHandler coordinator) { - this.thing = thing; - this.coordinator = coordinator; - } - - /** - * Initialize the helper. This should set up any necessary state but - * should NOT register listeners or start polling jobs. - */ - public abstract void initialize(); - - /** - * Process an asynchronous SBUS message. - * This method should use pattern matching to handle different message types. - * - * @param response the SBUS response message to process - */ - public abstract void processMessage(SbusResponse response); - - /** - * Check if this helper handles any of the configured channels. - * - * @return true if this helper should be active for the current thing configuration - */ - public abstract boolean hasRelevantChannels(); - - /** - * Dispose any resources held by this helper. - * This should NOT dispose the thing or update thing status. - */ - public void dispose() { - // Default implementation does nothing - logger.debug("Disposed SBUS helper for {}", thing.getUID()); - } -} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java deleted file mode 100644 index 0081eb674abee..0000000000000 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusContactHelper.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.sbus.internal.helper; - -import org.openhab.binding.sbus.BindingConstants; -import org.openhab.binding.sbus.internal.config.SbusChannelConfig; -import org.openhab.binding.sbus.internal.handler.Sbus9in1SensorsHandler; -import org.openhab.core.library.types.OpenClosedType; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; - -import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; -import ro.ciprianpascu.sbus.msg.SbusResponse; - -/** - * The {@link SbusContactHelper} is a helper class for processing contact sensor channels - * from 9-in-1 sensor devices. It processes dry contact states from ReadNineInOneStatusResponse - * and MotionSensorStatusReport messages. This is a lightweight helper that does not manage - * its own lifecycle, polling, or message listening - it is coordinated by Sbus9in1SensorsHandler. - * - * @author Ciprian Pascu - Initial contribution - */ -public class SbusContactHelper extends AbstractSbusHelper { - - public SbusContactHelper(Thing thing, Sbus9in1SensorsHandler coordinator) { - super(thing, coordinator); - } - - @Override - public void initialize() { - // Initialize contact channel processing - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { - // Contact channels are already defined in the thing configuration - logger.debug("Initialized contact channel: {}", channelUID.getId()); - } - } - } - - @Override - public void processMessage(SbusResponse response) { - try { - if (response instanceof MotionSensorStatusReport report) { - processMotionSensorReport(report); - logger.debug("Processed async motion sensor status report for contact helper {}", thing.getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - process9in1Response(statusResponse); - logger.debug("Processed async 9-in-1 status response for contact helper {}", thing.getUID()); - } - } catch (IllegalStateException | IllegalArgumentException e) { - logger.warn("Error processing async message in contact helper {}: {}", thing.getUID(), e.getMessage()); - } - } - - @Override - public boolean hasRelevantChannels() { - for (Channel channel : thing.getChannels()) { - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { - return true; - } - } - return false; - } - - private void process9in1Response(ReadNineInOneStatusResponse response) { - // Update contact channels from 9-in-1 response - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { - SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); - - // Use channelNumber to determine which dry contact (1 or 2, default to 1) - int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; - boolean contactState = false; - - if (channelNumber == 1) { - contactState = response.getDryContact1Status() > 0; - } else if (channelNumber == 2) { - contactState = response.getDryContact2Status() > 0; - } - - OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; - coordinator.updateChannelState(channelUID, state); - - logger.debug("Updated 9-in-1 contact channel {} (number {}) state: {}", channelUID.getId(), - channelNumber, state); - } - } - } - - private void processMotionSensorReport(MotionSensorStatusReport report) { - // Update contact channels from motion sensor status report - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_CONTACT.equals(channel.getChannelTypeUID().getId())) { - SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); - - // Use channelNumber to determine which dry contact (1 or 2, default to 1) - int channelNumber = channelConfig.channelNumber > 0 ? channelConfig.channelNumber : 1; - boolean contactState = false; - - if (channelNumber == 1) { - contactState = report.getDryContactStatus(0) > 0; // First dry contact (index 0) - } else if (channelNumber == 2) { - contactState = report.getDryContactStatus(1) > 0; // Second dry contact (index 1) - } - - OpenClosedType state = contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED; - coordinator.updateChannelState(channelUID, state); - - logger.debug("Updated 9-in-1 contact channel {} (number {}) state from report: {}", channelUID.getId(), - channelNumber, state); - } - } - } -} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java deleted file mode 100644 index 8c2831b50b431..0000000000000 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusLuxHelper.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.sbus.internal.helper; - -import org.openhab.binding.sbus.BindingConstants; -import org.openhab.binding.sbus.internal.handler.Sbus9in1SensorsHandler; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; - -import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; -import ro.ciprianpascu.sbus.msg.SbusResponse; - -/** - * The {@link SbusLuxHelper} is a helper class for processing lux sensor channels - * from 9-in-1 sensor devices. It processes lux values from ReadNineInOneStatusResponse - * and MotionSensorStatusReport messages. This is a lightweight helper that does not manage - * its own lifecycle, polling, or message listening - it is coordinated by Sbus9in1SensorsHandler. - * - * @author Ciprian Pascu - Initial contribution - */ -public class SbusLuxHelper extends AbstractSbusHelper { - - public SbusLuxHelper(Thing thing, Sbus9in1SensorsHandler coordinator) { - super(thing, coordinator); - } - - @Override - public void initialize() { - // Initialize lux channel processing - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { - // Lux channels are already defined in the thing configuration - logger.debug("Initialized lux channel: {}", channelUID.getId()); - } - } - } - - @Override - public void processMessage(SbusResponse response) { - try { - if (response instanceof MotionSensorStatusReport report) { - processMotionSensorReport(report); - logger.debug("Processed async motion sensor status report for lux helper {}", thing.getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - process9in1Response(statusResponse); - logger.debug("Processed async 9-in-1 status response for lux helper {}", thing.getUID()); - } - } catch (IllegalStateException | IllegalArgumentException e) { - logger.warn("Error processing async message in lux helper {}: {}", thing.getUID(), e.getMessage()); - } - } - - @Override - public boolean hasRelevantChannels() { - for (Channel channel : thing.getChannels()) { - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { - return true; - } - } - return false; - } - - public void process9in1Response(ReadNineInOneStatusResponse response) { - // Update lux channels from 9-in-1 response - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { - - int luxValue = response.getLuxValue(); - QuantityType state = new QuantityType<>(luxValue, Units.LUX); - coordinator.updateChannelState(channelUID, state); - - logger.debug("Updated 9-in-1 lux channel {} state: {} lux", channelUID.getId(), luxValue); - } - } - } - - public void processMotionSensorReport(MotionSensorStatusReport report) { - // Update lux channels from motion sensor status report - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_LUX.equals(channel.getChannelTypeUID().getId())) { - - int luxValue = report.getLuxValue(); - QuantityType state = new QuantityType<>(luxValue, Units.LUX); - coordinator.updateChannelState(channelUID, state); - - logger.debug("Updated 9-in-1 lux channel {} state from report: {} lux", channelUID.getId(), luxValue); - } - } - } -} diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java deleted file mode 100644 index bf3b1b94b6c89..0000000000000 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/helper/SbusMotionHelper.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.sbus.internal.helper; - -import org.openhab.binding.sbus.BindingConstants; -import org.openhab.binding.sbus.internal.handler.Sbus9in1SensorsHandler; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; - -import ro.ciprianpascu.sbus.msg.MotionSensorStatusReport; -import ro.ciprianpascu.sbus.msg.ReadNineInOneStatusResponse; -import ro.ciprianpascu.sbus.msg.SbusResponse; - -/** - * The {@link SbusMotionHelper} is a helper class for processing motion sensor channels - * from 9-in-1 sensor devices. It processes motion states from ReadNineInOneStatusResponse - * and MotionSensorStatusReport messages. This is a lightweight helper that does not manage - * its own lifecycle, polling, or message listening - it is coordinated by Sbus9in1SensorsHandler. - * - * @author Ciprian Pascu - Initial contribution - */ -public class SbusMotionHelper extends AbstractSbusHelper { - - public SbusMotionHelper(Thing thing, Sbus9in1SensorsHandler coordinator) { - super(thing, coordinator); - } - - @Override - public void initialize() { - // Initialize motion channel processing - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { - // Motion channels are already defined in the thing configuration - logger.debug("Initialized motion channel: {}", channelUID.getId()); - } - } - } - - @Override - public void processMessage(SbusResponse response) { - try { - if (response instanceof MotionSensorStatusReport report) { - processMotionSensorReport(report); - logger.debug("Processed async motion sensor status report for motion helper {}", thing.getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - process9in1Response(statusResponse); - logger.debug("Processed async 9-in-1 status response for motion helper {}", thing.getUID()); - } - } catch (IllegalStateException | IllegalArgumentException e) { - logger.warn("Error processing async message in motion helper {}: {}", thing.getUID(), e.getMessage()); - } - } - - @Override - public boolean hasRelevantChannels() { - for (Channel channel : thing.getChannels()) { - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { - return true; - } - } - return false; - } - - public void process9in1Response(ReadNineInOneStatusResponse response) { - // Update motion channels from 9-in-1 response - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { - - boolean motionDetected = response.getMotionStatus() > 0; - OnOffType state = motionDetected ? OnOffType.ON : OnOffType.OFF; - coordinator.updateChannelState(channelUID, state); - - logger.debug("Updated 9-in-1 motion channel {} state: {}", channelUID.getId(), state); - } - } - } - - public void processMotionSensorReport(MotionSensorStatusReport report) { - // Update motion channels from motion sensor status report - for (Channel channel : thing.getChannels()) { - ChannelUID channelUID = channel.getUID(); - if (channel.getChannelTypeUID() != null - && BindingConstants.CHANNEL_TYPE_MOTION.equals(channel.getChannelTypeUID().getId())) { - - boolean motionDetected = report.getMotionStatus() > 0; - OnOffType state = motionDetected ? OnOffType.ON : OnOffType.OFF; - coordinator.updateChannelState(channelUID, state); - - logger.debug("Updated 9-in-1 motion channel {} state from report: {}", channelUID.getId(), state); - } - } - } -} diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index 294cf712f6e77..74e442eb52f15 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -11,8 +11,12 @@ thing-type.sbus.switch.label = Sbus Switch thing-type.sbus.switch.description = Sbus switch device thing-type.sbus.temperature.label = Sbus Temperature Sensor thing-type.sbus.temperature.description = Sbus temperature sensor device -thing-type.sbus.contact.label = Sbus Contact Sensor -thing-type.sbus.contact.description = Sbus contact sensor device +thing-type.sbus.contact-sensor.label = Sbus Contact Sensor +thing-type.sbus.contact-sensor.description = Sbus contact sensor device +thing-type.sbus.motion-sensor.label = Sbus Motion Sensor +thing-type.sbus.motion-sensor.description = Sbus motion sensor device +thing-type.sbus.lux-sensor.label = Sbus Lux Sensor +thing-type.sbus.lux-sensor.description = Sbus light level sensor device thing-type.sbus.multi-sensor.label = Sbus Multi-Sensor thing-type.sbus.multi-sensor.description = Sbus sensor device with motion, lux, and dry contact capabilities thing-type.sbus.udp.label = Sbus UDP Bridge @@ -28,14 +32,18 @@ thing-type.config.sbus.rgbw.subnetId.label = SubnetId thing-type.config.sbus.rgbw.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. thing-type.config.sbus.rgbw.subnetId.option.1 = 1 thing-type.config.sbus.rgbw.subnetId.option.255 = 255 -thing-type.config.sbus.contact.id.label = Device ID -thing-type.config.sbus.contact.id.description = The ID of the Sbus device -thing-type.config.sbus.contact.refresh.label = Refresh Interval -thing-type.config.sbus.contact.refresh.description = Refresh interval in seconds -thing-type.config.sbus.contact.subnetId.label = SubnetId -thing-type.config.sbus.contact.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. -thing-type.config.sbus.contact.subnetId.option.1 = 1 -thing-type.config.sbus.contact.subnetId.option.255 = 255 +thing-type.config.sbus.contact-sensor.type.label = Device Type +thing-type.config.sbus.contact-sensor.type.description = Type of contact sensor device +thing-type.config.sbus.contact-sensor.type.option.mix24 = MIX24 (Traditional) +thing-type.config.sbus.contact-sensor.type.option.9in1 = 9-in-1 Sensor +thing-type.config.sbus.contact-sensor.id.label = Device ID +thing-type.config.sbus.contact-sensor.id.description = The ID of the Sbus device +thing-type.config.sbus.contact-sensor.refresh.label = Refresh Interval +thing-type.config.sbus.contact-sensor.refresh.description = Refresh interval in seconds +thing-type.config.sbus.contact-sensor.subnetId.label = SubnetId +thing-type.config.sbus.contact-sensor.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. +thing-type.config.sbus.contact-sensor.subnetId.option.1 = 1 +thing-type.config.sbus.contact-sensor.subnetId.option.255 = 255 thing-type.config.sbus.switch.id.label = Device ID thing-type.config.sbus.switch.id.description = The ID of the Sbus device thing-type.config.sbus.switch.refresh.label = Refresh Interval diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index ea07e194b2116..c4a05622ae0ee 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -107,6 +107,15 @@ Sbus contact sensor device ContactSensor + + + Type of contact sensor device + + + + + mix24 + Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. @@ -129,7 +138,69 @@ - + + + + + + + Sbus motion sensor device + MotionDetector + + + + Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. + 1 + + + + + + + + The ID of the Sbus device + + + + Refresh interval in seconds (0 = listen-only mode for broadcast messages) + 0 + s + + + + + + + + + + + Sbus light level sensor device + Sensor + + + + Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. + 1 + + + + + + + + The ID of the Sbus device + + + + Refresh interval in seconds (0 = listen-only mode for broadcast messages) + 0 + s + + + + + From a4148132ed8d267336cbd419dbbe66e4cd125275 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sat, 18 Oct 2025 05:14:00 +0300 Subject: [PATCH 13/18] [sbus] treat sensors as first class things. removed multi-sensor concept Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 1 - .../resources/OH-INF/i18n/sbus.properties | 10 ------ .../resources/OH-INF/thing/thing-types.xml | 31 ------------------- 3 files changed, 42 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index 7791613367458..256a5f8a2b7e3 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -13,7 +13,6 @@ The binding supports various thing types including RGB/RGBW controllers, tempera - `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both MIX24 and 9-in-1 sensor types) - `motion-sensor` - Motion Sensors for detecting movement - `lux-sensor` - Light Level Sensors for measuring illuminance -- `multi-sensor` - (Deprecated) Multi-Sensor things with motion, lux, and dry contact capabilities ## Discovery diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index 74e442eb52f15..29bf4d25b0454 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -17,8 +17,6 @@ thing-type.sbus.motion-sensor.label = Sbus Motion Sensor thing-type.sbus.motion-sensor.description = Sbus motion sensor device thing-type.sbus.lux-sensor.label = Sbus Lux Sensor thing-type.sbus.lux-sensor.description = Sbus light level sensor device -thing-type.sbus.multi-sensor.label = Sbus Multi-Sensor -thing-type.sbus.multi-sensor.description = Sbus sensor device with motion, lux, and dry contact capabilities thing-type.sbus.udp.label = Sbus UDP Bridge thing-type.sbus.udp.description = Endpoint for Sbus UDP slaves @@ -60,14 +58,6 @@ thing-type.config.sbus.temperature.subnetId.label = SubnetId thing-type.config.sbus.temperature.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. thing-type.config.sbus.temperature.subnetId.option.1 = 1 thing-type.config.sbus.temperature.subnetId.option.255 = 255 -thing-type.config.sbus.multi-sensor.id.label = Device ID -thing-type.config.sbus.multi-sensor.id.description = The ID of the Sbus device -thing-type.config.sbus.multi-sensor.refresh.label = Refresh Interval -thing-type.config.sbus.multi-sensor.refresh.description = Refresh interval in seconds (0 = listen-only mode for broadcast messages) -thing-type.config.sbus.multi-sensor.subnetId.label = SubnetId -thing-type.config.sbus.multi-sensor.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. -thing-type.config.sbus.multi-sensor.subnetId.option.1 = 1 -thing-type.config.sbus.multi-sensor.subnetId.option.255 = 255 thing-type.config.sbus.udp.host.label = IP Address or Hostname thing-type.config.sbus.udp.host.description = Network address of the device thing-type.config.sbus.udp.port.label = Port diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index c4a05622ae0ee..53bee5e914a13 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -200,37 +200,6 @@ - - - - - - - Sbus multi-sensor device with motion, lux, and dry contact capabilities - Sensor - - - - Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. - 1 - - - - - - - - The ID of the Sbus device - - - - Refresh interval in seconds (0 = listen-only mode for broadcast messages) - 0 - s - - - - Switch From 09ac0de88d47a19298cbe1926b8eeb11111a6d71 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sat, 18 Oct 2025 05:45:33 +0300 Subject: [PATCH 14/18] [sbus] treat sensors as first class things. removed multi-sensor concept Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 45 ++++++++++--------- .../internal/config/ContactSensorType.java | 8 ++-- .../internal/config/SbusContactConfig.java | 2 +- .../internal/handler/SbusHandlerFactory.java | 2 +- .../resources/OH-INF/i18n/sbus.properties | 2 +- .../resources/OH-INF/thing/thing-types.xml | 16 ++++++- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index 256a5f8a2b7e3..902022b196f30 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -10,7 +10,7 @@ The binding supports various thing types including RGB/RGBW controllers, tempera - `rgbw` - RGB/RGBW Controllers for color and brightness control - `temperature` - Temperature Sensors for monitoring environmental conditions - `switch` - Switch Controllers for basic on/off and dimming control -- `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both MIX24 and 9-in-1 sensor types) +- `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both 24Z and 9-in-1 sensor types) - `motion-sensor` - Motion Sensors for detecting movement - `lux-sensor` - Light Level Sensors for measuring illuminance @@ -53,7 +53,7 @@ The `contact-sensor` thing type has an additional `type` parameter: | Name | Type | Description | Default | Required | Advanced | |:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| type | text | Sensor type: `mix24` (traditional) or `9in1` (9-in-1 sensor) | mix24 | no | no | +| type | text | Sensor type: `24z` (traditional) or `9in1` (9-in-1 sensor) | 24z | no | no | **Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where the binding only processes broadcast messages without actively polling. This is useful for sensors that automatically broadcast their status updates. @@ -93,12 +93,6 @@ The color channel of RGBW controllers supports these additional parameters: |:--------|:--------|:----------:|:----------------------------------------------------------| | contact | Contact | R | Contact state (OPEN/CLOSED) | -The contact channel supports a `channelNumber` parameter: - -| Parameter | Type | Description | Default | Required | Advanced | -|:--------------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| channelNumber | integer | The dry contact number | 1 | no | no | - ### Motion Sensor Channels | Channel | Type | Read/Write | Description | @@ -111,6 +105,8 @@ The contact channel supports a `channelNumber` parameter: |:--------|:--------|:----------:|:----------------------------------------------------------| | lux | Number | R | Light level in LUX units | +**Note:** All sensor channels require a `channelNumber` parameter to specify the physical channel number. + ## Full Example ### Thing Configuration @@ -141,7 +137,7 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type paired-channel : third_switch [ channelNumber=3, pairedChannelNumber=4 ] } - Thing contact-sensor contact1 [ type="mix24", id=80, refresh=30 ] { + Thing contact-sensor contact1 [ type="24z", id=80, refresh=30 ] { Channels: Type contact-channel : contact [ channelNumber=1 ] } @@ -149,12 +145,12 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Thing motion-sensor sensor_motion [ id=85, refresh=0 ] { Channels: - Type motion-channel : motion + Type motion-channel : motion [ channelNumber=1 ] } Thing lux-sensor sensor_lux [ id=85, refresh=0 ] { Channels: - Type lux-channel : lux + Type lux-channel : lux [ channelNumber=1 ] } } ``` @@ -176,13 +172,10 @@ Group gLight "RGBW Light" ["Lighting"] Color rgbwColor "Color" (gLight) ["Control", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:color" } Switch rgbwPower "Power" (gLight) ["Switch", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:power" } -// Traditional MIX24 Contact Sensor Contact Door_Contact "Door [%s]" { channel="sbus:contact-sensor:mybridge:contact1:contact" } -// 9-in-1 Sensor Items (from physical sensor with ID 85) -Contact Sensor_Contact1 "Sensor Contact 1 [%s]" { channel="sbus:contact-sensor:mybridge:sensor_contact:contact1" } -Contact Sensor_Contact2 "Sensor Contact 2 [%s]" { channel="sbus:contact-sensor:mybridge:sensor_contact:contact2" } Switch Motion_Sensor "Motion [%s]" { channel="sbus:motion-sensor:mybridge:sensor_motion:motion" } + Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:lux-sensor:mybridge:sensor_lux:lux" } ``` @@ -224,9 +217,21 @@ All three things must use the **same subnet ID and unit ID** to represent the sa **Example for a 9-in-1 sensor with ID 85:** ```java -Thing contact-sensor sensor_contact [ type="9in1", id=85, refresh=0 ] -Thing motion-sensor sensor_motion [ id=85, refresh=0 ] -Thing lux-sensor sensor_lux [ id=85, refresh=0 ] +Thing contact-sensor sensor_contact [ type="9in1", id=85, refresh=0 ] { + Channels: + Type contact-channel : contact1 [ channelNumber=1 ] + Type contact-channel : contact2 [ channelNumber=2 ] +} + +Thing motion-sensor sensor_motion [ id=85, refresh=0 ] { + Channels: + Type motion-channel : motion [ channelNumber=1 ] +} + +Thing lux-sensor sensor_lux [ id=85, refresh=0 ] { + Channels: + Type lux-channel : lux [ channelNumber=1 ] +} ``` **Benefits of this approach:** @@ -239,8 +244,8 @@ Thing lux-sensor sensor_lux [ id=85, refresh=0 ] The `contact-sensor` thing type supports two different sensor types via the `type` parameter: -- **`mix24`** (default): Traditional MIX24 contact sensors using `ReadDryChannelsRequest/Response` protocol -- **`9in1`**: 9-in-1 sensor dry contacts using `ReadNineInOneStatusRequest/Response` protocol +- **`24z`** (default): Traditional 24Z contact sensors +- **`9in1`**: 9-in-1 sensor dry contacts Choose the appropriate type based on your hardware. diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java index 8f77fe49030d4..d390ff3c9fe5c 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java @@ -22,9 +22,9 @@ @NonNullByDefault public enum ContactSensorType { /** - * Traditional MIX24 contact sensor using ReadDryChannelsRequest/Response protocol + * Traditional 24Z contact sensor using ReadDryChannelsRequest/Response protocol */ - MIX24("mix24"), + SENSOR_24Z("24z"), /** * 9-in-1 sensor with dry contacts using ReadNineInOneStatusRequest/Response protocol @@ -50,7 +50,7 @@ public String getConfigValue() { * Parse a configuration value into a ContactSensorType. * * @param value the configuration value - * @return the corresponding ContactSensorType, or MIX24 if not recognized + * @return the corresponding ContactSensorType, or SENSOR_24Z if not recognized */ public static ContactSensorType fromConfigValue(String value) { for (ContactSensorType type : values()) { @@ -58,6 +58,6 @@ public static ContactSensorType fromConfigValue(String value) { return type; } } - return MIX24; // Default to MIX24 for backward compatibility or null values + return SENSOR_24Z; // Default to 24Z for backward compatibility or null values } } diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java index 5c39e5839609a..96258b2bda3a5 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java @@ -25,7 +25,7 @@ public class SbusContactConfig extends SbusDeviceConfig { * Sensor type for contact sensor. * String value is used for configuration binding, converted to enum via ContactSensorType.fromConfigValue() */ - public String type = ContactSensorType.MIX24.getConfigValue(); // Default to traditional behavior + public String type = ContactSensorType.SENSOR_24Z.getConfigValue(); // Default to traditional behavior /** * Get the sensor type as an enum. diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java index 7e62d5db960ee..e42a1a4f7a21c 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java @@ -77,7 +77,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { logger.debug("Creating Sbus 9-in-1 contact sensor handler for thing {}", thing.getUID()); return new Sbus9in1ContactHandler(thing); } else { - logger.debug("Creating Sbus MIX24 contact sensor handler for thing {}", thing.getUID()); + logger.debug("Creating Sbus 24Z contact sensor handler for thing {}", thing.getUID()); return new SbusContactHandler(thing); } } else if (thingTypeUID.equals(THING_TYPE_MOTION_SENSOR)) { diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index 29bf4d25b0454..23b9f6e707741 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -32,7 +32,7 @@ thing-type.config.sbus.rgbw.subnetId.option.1 = 1 thing-type.config.sbus.rgbw.subnetId.option.255 = 255 thing-type.config.sbus.contact-sensor.type.label = Device Type thing-type.config.sbus.contact-sensor.type.description = Type of contact sensor device -thing-type.config.sbus.contact-sensor.type.option.mix24 = MIX24 (Traditional) +thing-type.config.sbus.contact-sensor.type.option.24z = 24Z (Traditional) thing-type.config.sbus.contact-sensor.type.option.9in1 = 9-in-1 Sensor thing-type.config.sbus.contact-sensor.id.label = Device ID thing-type.config.sbus.contact-sensor.id.description = The ID of the Sbus device diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index 53bee5e914a13..fafd5bc79540a 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -111,10 +111,10 @@ Type of contact sensor device - + - mix24 + 24z @@ -341,6 +341,12 @@ Motion + + + + The physical channel number on the Sbus device + + @@ -353,6 +359,12 @@ Light + + + + The physical channel number on the Sbus device + + From 50142a39fa8d7a37ed4a78281cf2de41dbb4d0f6 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sat, 18 Oct 2025 06:00:32 +0300 Subject: [PATCH 15/18] [sbus] renamed temperature to temperature-sensor Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 15 ++++---- .../binding/sbus/BindingConstants.java | 3 +- .../internal/handler/SbusHandlerFactory.java | 8 ++-- .../resources/OH-INF/i18n/sbus.properties | 14 ++++++- .../resources/OH-INF/thing/thing-types.xml | 37 +++++++++++++++++-- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index 902022b196f30..b139d70cdbd87 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -8,11 +8,12 @@ The binding supports various thing types including RGB/RGBW controllers, tempera - `udp` - Sbus Bridge for UDP communication - `rgbw` - RGB/RGBW Controllers for color and brightness control -- `temperature` - Temperature Sensors for monitoring environmental conditions +- `temperature-sensor` - Temperature Sensors for monitoring environmental conditions - `switch` - Switch Controllers for basic on/off and dimming control - `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both 24Z and 9-in-1 sensor types) - `motion-sensor` - Motion Sensors for detecting movement - `lux-sensor` - Light Level Sensors for measuring illuminance +- `temperature` - (Deprecated) Use `temperature-sensor` instead ## Discovery @@ -125,11 +126,6 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type switch-channel : power [ channelNumber=1 ] } - Thing temperature temp1 [ id=62, refresh=30 ] { - Channels: - Type temperature-channel : temperature [ channelNumber=1 ] - } - Thing switch switch1 [ id=75, refresh=30 ] { Channels: Type switch-channel : first_switch [ channelNumber=1 ] @@ -137,6 +133,11 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type paired-channel : third_switch [ channelNumber=3, pairedChannelNumber=4 ] } + Thing temperature-sensor temp1 [ id=62, refresh=30 ] { + Channels: + Type temperature-channel : temperature [ channelNumber=1 ] + } + Thing contact-sensor contact1 [ type="24z", id=80, refresh=30 ] { Channels: Type contact-channel : contact [ channelNumber=1 ] @@ -159,7 +160,7 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { ```java // Temperature Sensor -Number:Temperature Temp_Sensor "Temperature [%.1f °C]" { channel="sbus:temperature:mybridge:temp1:temperature" } +Number:Temperature Temp_Sensor "Temperature [%.1f °C]" { channel="sbus:temperature-sensor:mybridge:temp1:temperature" } // Basic Switch Switch Light_Switch "Switch" { channel="sbus:switch:mybridge:switch1:switch" } diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java index 32870e6cfdf2d..d6c8618d1f5bf 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/BindingConstants.java @@ -34,7 +34,8 @@ private BindingConstants() { // Thing Types public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch"); - public static final ThingTypeUID THING_TYPE_TEMPERATURE = new ThingTypeUID(BINDING_ID, "temperature"); + public static final ThingTypeUID THING_TYPE_TEMPERATURE = new ThingTypeUID(BINDING_ID, "temperature"); // Deprecated + public static final ThingTypeUID THING_TYPE_TEMPERATURE_SENSOR = new ThingTypeUID(BINDING_ID, "temperature-sensor"); public static final ThingTypeUID THING_TYPE_RGBW = new ThingTypeUID(BINDING_ID, "rgbw"); public static final ThingTypeUID THING_TYPE_CONTACT_SENSOR = new ThingTypeUID(BINDING_ID, "contact-sensor"); public static final ThingTypeUID THING_TYPE_MOTION_SENSOR = new ThingTypeUID(BINDING_ID, "motion-sensor"); diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java index e42a1a4f7a21c..d2e9b61c5ad4c 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java @@ -42,8 +42,8 @@ public class SbusHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(SbusHandlerFactory.class); private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_UDP_BRIDGE, THING_TYPE_SWITCH, - THING_TYPE_TEMPERATURE, THING_TYPE_RGBW, THING_TYPE_CONTACT_SENSOR, THING_TYPE_MOTION_SENSOR, - THING_TYPE_LUX_SENSOR); + THING_TYPE_TEMPERATURE, THING_TYPE_TEMPERATURE_SENSOR, THING_TYPE_RGBW, THING_TYPE_CONTACT_SENSOR, + THING_TYPE_MOTION_SENSOR, THING_TYPE_LUX_SENSOR); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -62,8 +62,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { if (thingTypeUID.equals(THING_TYPE_SWITCH)) { logger.debug("Creating Sbus switch handler for thing {}", thing.getUID()); return new SbusSwitchHandler(thing); - } else if (thingTypeUID.equals(THING_TYPE_TEMPERATURE)) { - logger.debug("Creating Sbus temperature handler for thing {}", thing.getUID()); + } else if (thingTypeUID.equals(THING_TYPE_TEMPERATURE) || thingTypeUID.equals(THING_TYPE_TEMPERATURE_SENSOR)) { + logger.debug("Creating Sbus temperature sensor handler for thing {}", thing.getUID()); return new SbusTemperatureHandler(thing); } else if (thingTypeUID.equals(THING_TYPE_RGBW)) { logger.debug("Creating Sbus RGBW handler for thing {}", thing.getUID()); diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index 23b9f6e707741..0aacded9f6931 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -9,8 +9,10 @@ thing-type.sbus.rgbw.label = Sbus RGBW Controller thing-type.sbus.rgbw.description = Sbus RGBW lighting controller thing-type.sbus.switch.label = Sbus Switch thing-type.sbus.switch.description = Sbus switch device -thing-type.sbus.temperature.label = Sbus Temperature Sensor -thing-type.sbus.temperature.description = Sbus temperature sensor device +thing-type.sbus.temperature-sensor.label = Temperature Sensor +thing-type.sbus.temperature-sensor.description = Sbus temperature sensor +thing-type.sbus.temperature.label = Sbus Temperature Sensor (Deprecated) +thing-type.sbus.temperature.description = Sbus temperature sensor device (deprecated, use temperature-sensor instead) thing-type.sbus.contact-sensor.label = Sbus Contact Sensor thing-type.sbus.contact-sensor.description = Sbus contact sensor device thing-type.sbus.motion-sensor.label = Sbus Motion Sensor @@ -50,6 +52,14 @@ thing-type.config.sbus.switch.subnetId.label = SubnetId thing-type.config.sbus.switch.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. thing-type.config.sbus.switch.subnetId.option.1 = 1 thing-type.config.sbus.switch.subnetId.option.255 = 255 +thing-type.config.sbus.temperature-sensor.id.label = Device ID +thing-type.config.sbus.temperature-sensor.id.description = The ID of the Sbus device +thing-type.config.sbus.temperature-sensor.refresh.label = Refresh Interval +thing-type.config.sbus.temperature-sensor.refresh.description = Refresh interval in seconds +thing-type.config.sbus.temperature-sensor.subnetId.label = SubnetId +thing-type.config.sbus.temperature-sensor.subnetId.description = Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. +thing-type.config.sbus.temperature-sensor.subnetId.option.1 = 1 +thing-type.config.sbus.temperature-sensor.subnetId.option.255 = 255 thing-type.config.sbus.temperature.id.label = Device ID thing-type.config.sbus.temperature.id.description = The ID of the Sbus device thing-type.config.sbus.temperature.refresh.label = Refresh Interval diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index fafd5bc79540a..c632928370da3 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -36,13 +36,44 @@ - + + + + + + + Sbus temperature sensor + TemperatureSensor + + + + Slave subnet id. Can take any value between 1 and 255. 255 for broadcast. + 1 + + + + + + + + The ID of the Sbus device + + + + Refresh interval in seconds + 30 + s + + + + + - - Sbus temperature sensor device + + Sbus temperature sensor device (deprecated, use temperature-sensor instead) TemperatureSensor From 6894783645c518b301ce3a17f99b2ea37d804afa Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sun, 19 Oct 2025 12:19:44 +0300 Subject: [PATCH 16/18] [sbus] tested lux, motion & contact sensors Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 42 +++++------------ .../internal/config/ContactSensorType.java | 10 ++--- .../internal/config/SbusContactConfig.java | 2 +- .../internal/handler/AbstractSbusHandler.java | 36 +++++++++++---- .../handler/Sbus9in1ContactHandler.java | 10 ++--- .../internal/handler/SbusContactHandler.java | 15 +++---- .../internal/handler/SbusHandlerFactory.java | 6 +-- .../handler/SbusLuxSensorHandler.java | 45 +++++++++++++------ .../handler/SbusMotionSensorHandler.java | 22 ++++++--- .../resources/OH-INF/i18n/sbus.properties | 4 +- .../resources/OH-INF/thing/thing-types.xml | 12 ++--- 11 files changed, 114 insertions(+), 90 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index b139d70cdbd87..aa1cf075190cd 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -10,7 +10,7 @@ The binding supports various thing types including RGB/RGBW controllers, tempera - `rgbw` - RGB/RGBW Controllers for color and brightness control - `temperature-sensor` - Temperature Sensors for monitoring environmental conditions - `switch` - Switch Controllers for basic on/off and dimming control -- `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both 24Z and 9-in-1 sensor types) +- `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both 012C and DB00 sensor types) - `motion-sensor` - Motion Sensors for detecting movement - `lux-sensor` - Light Level Sensors for measuring illuminance - `temperature` - (Deprecated) Use `temperature-sensor` instead @@ -54,7 +54,7 @@ The `contact-sensor` thing type has an additional `type` parameter: | Name | Type | Description | Default | Required | Advanced | |:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| type | text | Sensor type: `24z` (traditional) or `9in1` (9-in-1 sensor) | 24z | no | no | +| type | text | Sensor type: `012c` (dry contact) or `db00` (multi-sensor) | 012c | no | no | **Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where the binding only processes broadcast messages without actively polling. This is useful for sensors that automatically broadcast their status updates. @@ -138,7 +138,7 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000, timeout=5000 ] { Type temperature-channel : temperature [ channelNumber=1 ] } - Thing contact-sensor contact1 [ type="24z", id=80, refresh=30 ] { + Thing contact-sensor contact1 [ type="012c", id=80, refresh=30 ] { Channels: Type contact-channel : contact [ channelNumber=1 ] } @@ -173,43 +173,23 @@ Group gLight "RGBW Light" ["Lighting"] Color rgbwColor "Color" (gLight) ["Control", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:color" } Switch rgbwPower "Power" (gLight) ["Switch", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:power" } -Contact Door_Contact "Door [%s]" { channel="sbus:contact-sensor:mybridge:contact1:contact" } +// Contact Sensor +Contact Contact_Sensor "Contact [%s]" { channel="sbus:contact-sensor:mybridge:contact1:contact" } +// Motion Sensor Switch Motion_Sensor "Motion [%s]" { channel="sbus:motion-sensor:mybridge:sensor_motion:motion" } +// Lux Sensor Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:lux-sensor:mybridge:sensor_lux:lux" } ``` -### Sitemap Configuration - -```perl -sitemap sbus label="Sbus Demo" -{ - Frame label="Sbus Controls" { - Colorpicker item=rgbwColor - Switch item=rgbwPower - Text item=Temp_Sensor - Switch item=Light_Switch - Rollershutter item=Rollershutter_Switch - } - - Frame label="Sensors" { - Text item=Door_Contact - Text item=Sensor_Contact1 - Text item=Sensor_Contact2 - Text item=Motion_Sensor - Text item=Lux_Sensor - } -} -``` - ## Usage Notes ### 9-in-1 Sensor Configuration 9-in-1 sensors are multi-function sensors that combine motion detection, light level measurement, and dry contact monitoring in a single physical unit. To configure a 9-in-1 sensor in openHAB, you need to create **three separate things** that all reference the same physical sensor: -1. **contact-sensor** (type: `9in1`) - For dry contact channels +1. **contact-sensor** (type: `db00`) - For dry contact channels 2. **motion-sensor** - For motion detection 3. **lux-sensor** - For light level sensing @@ -218,7 +198,7 @@ All three things must use the **same subnet ID and unit ID** to represent the sa **Example for a 9-in-1 sensor with ID 85:** ```java -Thing contact-sensor sensor_contact [ type="9in1", id=85, refresh=0 ] { +Thing contact-sensor sensor_contact [ type="db00", id=85, refresh=0 ] { Channels: Type contact-channel : contact1 [ channelNumber=1 ] Type contact-channel : contact2 [ channelNumber=2 ] @@ -245,8 +225,8 @@ Thing lux-sensor sensor_lux [ id=85, refresh=0 ] { The `contact-sensor` thing type supports two different sensor types via the `type` parameter: -- **`24z`** (default): Traditional 24Z contact sensors -- **`9in1`**: 9-in-1 sensor dry contacts +- **`012c`** (default): 012C dry contact sensors +- **`db00`**: DB00 multi-sensor dry contacts Choose the appropriate type based on your hardware. diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java index d390ff3c9fe5c..7d5e52952dad1 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java @@ -22,14 +22,14 @@ @NonNullByDefault public enum ContactSensorType { /** - * Traditional 24Z contact sensor using ReadDryChannelsRequest/Response protocol + * 012C dry contact sensor using ReadDryChannelsRequest/Response protocol */ - SENSOR_24Z("24z"), + SENSOR_012C("012c"), /** - * 9-in-1 sensor with dry contacts using ReadNineInOneStatusRequest/Response protocol + * DB00 multi-sensor with dry contacts using ReadNineInOneStatusRequest/Response protocol */ - NINE_IN_ONE("9in1"); + MULTI_SENSOR_DB00("db00"); private final String configValue; @@ -58,6 +58,6 @@ public static ContactSensorType fromConfigValue(String value) { return type; } } - return SENSOR_24Z; // Default to 24Z for backward compatibility or null values + return SENSOR_012C; // Default to 012C for backward compatibility or null values } } diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java index 96258b2bda3a5..6591cc11c7c23 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/SbusContactConfig.java @@ -25,7 +25,7 @@ public class SbusContactConfig extends SbusDeviceConfig { * Sensor type for contact sensor. * String value is used for configuration binding, converted to enum via ContactSensorType.fromConfigValue() */ - public String type = ContactSensorType.SENSOR_24Z.getConfigValue(); // Default to traditional behavior + public String type = ContactSensorType.SENSOR_012C.getConfigValue(); // Default to traditional behavior /** * Get the sensor type as an enum. diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java index f42311ffa21ee..360d6fe48ac6a 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/AbstractSbusHandler.java @@ -135,8 +135,10 @@ public void updateChannelState(ChannelUID channelUID, org.openhab.core.types.Sta /** * Start polling the device for updates based on the configured refresh interval. + * + * @param initialDelay the initial delay before the first poll (in seconds) */ - protected void startPolling() { + protected void startPolling(long initialDelay) { SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); if (config.refresh > 0) { pollingJob = scheduler.scheduleWithFixedDelay(() -> { @@ -145,9 +147,10 @@ protected void startPolling() { } catch (IllegalStateException e) { logger.warn("Error polling Sbus device: {}", e.getMessage()); } - }, 0, config.refresh, TimeUnit.SECONDS); - } else if (config.refresh == 0) { + }, initialDelay, config.refresh, TimeUnit.SECONDS); + } else if (config.refresh == 0 && initialDelay == 0) { // Run polling once to set initial thing state when refresh is disabled + // Only execute if initialDelay is 0 (startup), not when called from resetPollingTimer pollingJob = scheduler.schedule(() -> { try { pollDevice(); @@ -158,6 +161,14 @@ protected void startPolling() { } } + /** + * Start polling the device for updates based on the configured refresh interval. + * Uses a default initial delay of 0 seconds (immediate execution). + */ + protected void startPolling() { + startPolling(0); + } + /** * Poll the device for updates. This method should be implemented by concrete handlers * to update their specific channel states. @@ -177,14 +188,23 @@ protected void startPolling() { * Reset the polling timer by cancelling the current polling job and starting a new one. * This should be called when an async message is successfully processed to reduce * unnecessary polling when the device is actively sending updates. + * Only resets the timer if periodic polling is enabled (refresh > 0). + * The next poll will occur after the full refresh interval, not immediately. */ protected void resetPollingTimer() { - ScheduledFuture job = pollingJob; - if (job != null) { - job.cancel(false); // Cancel the polling job without interrupting if it is currently running (cancel(false)) + SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); + // Only reset polling timer if periodic polling is enabled + if (config.refresh > 0) { + ScheduledFuture job = pollingJob; + if (job != null) { + job.cancel(false); // Cancel the polling job without interrupting if it is currently running + // (cancel(false)) + } + // Use refresh interval as initial delay to prevent immediate polling + startPolling(config.refresh); + logger.debug("Reset polling timer for handler {} - next poll in {} seconds", getThing().getUID(), + config.refresh); } - startPolling(); - logger.debug("Reset polling timer for handler {}", getThing().getUID()); } // SbusMessageListener implementation diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java index b56a93f7fc3a3..bbcbf860f504c 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java @@ -96,11 +96,11 @@ protected void processAsyncMessage(SbusResponse response) { updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for 9-in-1 contact handler {}", getThing().getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // Process 9-in-1 status response - updateContactChannelsFromResponse(statusResponse); - updateStatus(ThingStatus.ONLINE); - logger.debug("Processed async 9-in-1 status response for contact handler {}", getThing().getUID()); + // } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + // // Process 9-in-1 status response + // updateContactChannelsFromResponse(statusResponse); + // updateStatus(ThingStatus.ONLINE); + // logger.debug("Processed async 9-in-1 status response for contact handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in 9-in-1 contact handler {}: {}", getThing().getUID(), diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java index b1e44e2b3c541..b735042f0dc3d 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusContactHandler.java @@ -27,7 +27,6 @@ import ro.ciprianpascu.sbus.msg.ReadDryChannelsRequest; import ro.ciprianpascu.sbus.msg.ReadDryChannelsResponse; -import ro.ciprianpascu.sbus.msg.ReadStatusChannelsResponse; import ro.ciprianpascu.sbus.msg.SbusResponse; import ro.ciprianpascu.sbus.procimg.InputRegister; @@ -110,11 +109,7 @@ private boolean[] readContactStatusChannels(SbusService adapter, int subnetId, i "Unexpected response type: " + (response != null ? response.getClass().getSimpleName() : "null")); } - InputRegister[] registers = statusResponse.getRegisters(); - boolean[] contactStates = new boolean[registers.length]; - for (int i = 0; i < registers.length; i++) { - contactStates[i] = (registers[i].getValue() & 0xff) > 0; // Convert to boolean - } + boolean[] contactStates = extractContactStatuses(statusResponse); return contactStates; } @@ -123,7 +118,7 @@ private boolean[] readContactStatusChannels(SbusService adapter, int subnetId, i @Override protected void processAsyncMessage(SbusResponse response) { try { - if (response instanceof ReadStatusChannelsResponse statusResponse) { + if (response instanceof ReadDryChannelsResponse statusResponse) { // Process status channel response using existing logic boolean[] statuses = extractContactStatuses(statusResponse); updateChannelStatesFromStatuses(statuses); @@ -138,7 +133,7 @@ protected void processAsyncMessage(SbusResponse response) { @Override protected boolean isMessageRelevant(SbusResponse response) { - if (response instanceof ReadStatusChannelsResponse) { + if (response instanceof ReadDryChannelsResponse) { // Traditional contact sensor messages SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class); return response.getSubnetID() == config.subnetId && response.getUnitID() == config.id; @@ -165,10 +160,10 @@ private void updateChannelStatesFromStatuses(boolean[] contactStates) { } /** - * Extract contact status values from ReadStatusChannelsResponse. + * Extract contact status values from ReadDryChannelsResponse. * Reuses existing logic from readContactStatusChannels method. */ - private boolean[] extractContactStatuses(ReadStatusChannelsResponse response) { + private boolean[] extractContactStatuses(ReadDryChannelsResponse response) { InputRegister[] registers = response.getRegisters(); boolean[] statuses = new boolean[registers.length]; diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java index d2e9b61c5ad4c..90a9aea98358d 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java @@ -73,11 +73,11 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { SbusContactConfig config = thing.getConfiguration().as(SbusContactConfig.class); ContactSensorType sensorType = config.getSensorType(); - if (sensorType == ContactSensorType.NINE_IN_ONE) { - logger.debug("Creating Sbus 9-in-1 contact sensor handler for thing {}", thing.getUID()); + if (sensorType == ContactSensorType.MULTI_SENSOR_DB00) { + logger.debug("Creating Sbus DB00 multi-sensor contact handler for thing {}", thing.getUID()); return new Sbus9in1ContactHandler(thing); } else { - logger.debug("Creating Sbus 24Z contact sensor handler for thing {}", thing.getUID()); + logger.debug("Creating Sbus 012C contact sensor handler for thing {}", thing.getUID()); return new SbusContactHandler(thing); } } else if (thingTypeUID.equals(THING_TYPE_MOTION_SENSOR)) { diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java index 7636611739360..9cf56073034b6 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java @@ -15,8 +15,10 @@ import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.sbus.BindingConstants; import org.openhab.binding.sbus.internal.SbusService; +import org.openhab.binding.sbus.internal.config.SbusChannelConfig; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -48,8 +50,14 @@ public SbusLuxSensorHandler(Thing thing) { @Override protected void initializeChannels() { - // Create lux channel - createChannel(BindingConstants.CHANNEL_LUX, BindingConstants.CHANNEL_TYPE_LUX); + // Get all channel configurations from the thing + for (Channel channel : getThing().getChannels()) { + // Channels are already defined in thing-types.xml, just validate their configuration + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + if (channelConfig.channelNumber <= 0) { + logger.warn("Channel {} has invalid channel number configuration", channel.getUID()); + } + } } @Override @@ -81,12 +89,18 @@ protected void pollDevice() { * @param response the sensor response containing lux data */ private void updateChannelStatesFromResponse(ReadNineInOneStatusResponse response) { - // Update lux channel (byte 2: LUX value) + // Update lux channel (byte 2: LUX value - one byte unsigned, 0-255 range) + // Shift 8-bit value to 16-bit range, then normalize to 0-100% ChannelUID luxChannelUID = new ChannelUID(getThing().getUID(), BindingConstants.CHANNEL_LUX); - DecimalType luxValue = new DecimalType(response.getLuxValue()); + int luxValueSigned = response.getLuxValue(); + int luxValue8bit = luxValueSigned & 0xFF; // Convert to unsigned 8-bit value (0-255) + int luxValue16bit = luxValue8bit << 8; // Shift to 16-bit range (0-65280) + int luxPercentage = (luxValue16bit * 100) / 65535; // Normalize to 0-100% + DecimalType luxValue = new DecimalType(luxPercentage); updateState(luxChannelUID, luxValue); - logger.debug("Updated lux sensor state - LUX: {}", luxValue); + logger.debug("Updated lux sensor state - LUX: {}% (8-bit: {}, 16-bit: {}, raw: {})", luxPercentage, + luxValue8bit, luxValue16bit, luxValueSigned); } /** @@ -95,12 +109,17 @@ private void updateChannelStatesFromResponse(ReadNineInOneStatusResponse respons * @param report the motion sensor status report containing lux data */ private void updateChannelStatesFromReport(MotionSensorStatusReport report) { - // Update lux channel (bytes 6-7: LUX value as 2-byte value) + // Update lux channel (bytes 6-7: LUX value - two bytes unsigned, 0-65535 range) + // Normalize to 0-100% ChannelUID luxChannelUID = new ChannelUID(getThing().getUID(), BindingConstants.CHANNEL_LUX); - DecimalType luxValue = new DecimalType(report.getLuxValue()); + int luxValueSigned = report.getLuxValue(); + int luxValue16bit = luxValueSigned & 0xFFFF; // Convert to unsigned 16-bit value (0-65535) + int luxPercentage = (luxValue16bit * 100) / 65535; // Normalize to 0-100% + DecimalType luxValue = new DecimalType(luxPercentage); updateState(luxChannelUID, luxValue); - logger.debug("Updated lux sensor state from report - LUX: {}", luxValue); + logger.debug("Updated lux sensor state from report - LUX: {}% (16-bit: {}, raw: {})", luxPercentage, + luxValue16bit, luxValueSigned); } @Override @@ -143,11 +162,11 @@ protected void processAsyncMessage(SbusResponse response) { updateChannelStatesFromReport(report); updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for lux handler {}", getThing().getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // Process 9-in-1 status response (0xDB01) - updateChannelStatesFromResponse(statusResponse); - updateStatus(ThingStatus.ONLINE); - logger.debug("Processed async 9-in-1 status response for lux handler {}", getThing().getUID()); + // } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + // // Process 9-in-1 status response (0xDB01) + // updateChannelStatesFromResponse(statusResponse); + // updateStatus(ThingStatus.ONLINE); + // logger.debug("Processed async 9-in-1 status response for lux handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in lux sensor handler {}: {}", getThing().getUID(), diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java index 6380688bc0b87..b41f0e5d39f1a 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java @@ -15,8 +15,10 @@ import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.sbus.BindingConstants; import org.openhab.binding.sbus.internal.SbusService; +import org.openhab.binding.sbus.internal.config.SbusChannelConfig; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -48,8 +50,14 @@ public SbusMotionSensorHandler(Thing thing) { @Override protected void initializeChannels() { - // Create motion channel - this handler focuses only on motion detection - createChannel(BindingConstants.CHANNEL_MOTION, BindingConstants.CHANNEL_TYPE_MOTION); + // Get all channel configurations from the thing + for (Channel channel : getThing().getChannels()) { + // Channels are already defined in thing-types.xml, just validate their configuration + SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class); + if (channelConfig.channelNumber <= 0) { + logger.warn("Channel {} has invalid channel number configuration", channel.getUID()); + } + } } @Override @@ -143,11 +151,11 @@ protected void processAsyncMessage(SbusResponse response) { updateChannelStatesFromReport(report); updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for handler {}", getThing().getUID()); - } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // Process 9-in-1 status response (0xDB01) - updateChannelStatesFromResponse(statusResponse); - updateStatus(ThingStatus.ONLINE); - logger.debug("Processed async 9-in-1 status response for handler {}", getThing().getUID()); + // } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { + // // Process 9-in-1 status response (0xDB01) + // updateChannelStatesFromResponse(statusResponse); + // updateStatus(ThingStatus.ONLINE); + // logger.debug("Processed async 9-in-1 status response for handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in motion sensor handler {}: {}", getThing().getUID(), diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index 0aacded9f6931..a638f82de92f4 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -34,8 +34,8 @@ thing-type.config.sbus.rgbw.subnetId.option.1 = 1 thing-type.config.sbus.rgbw.subnetId.option.255 = 255 thing-type.config.sbus.contact-sensor.type.label = Device Type thing-type.config.sbus.contact-sensor.type.description = Type of contact sensor device -thing-type.config.sbus.contact-sensor.type.option.24z = 24Z (Traditional) -thing-type.config.sbus.contact-sensor.type.option.9in1 = 9-in-1 Sensor +thing-type.config.sbus.contact-sensor.type.option.012c = 012C (Dry Contact Sensor) +thing-type.config.sbus.contact-sensor.type.option.db00 = DB00 (Multi-Sensor) thing-type.config.sbus.contact-sensor.id.label = Device ID thing-type.config.sbus.contact-sensor.id.description = The ID of the Sbus device thing-type.config.sbus.contact-sensor.refresh.label = Refresh Interval diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index c632928370da3..4110c0d435730 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -142,10 +142,10 @@ Type of contact sensor device - - + + - 24z + 012c @@ -373,9 +373,10 @@ - + The physical channel number on the Sbus device + 1 @@ -391,9 +392,10 @@ - + The physical channel number on the Sbus device + 1 From 3bdbfa80bcce8af976102f5f931dc04c24ffaba9 Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Sun, 19 Oct 2025 14:41:59 +0300 Subject: [PATCH 17/18] [sbus] tested lux, motion & contact sensors Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.sbus/README.md | 10 +++++----- .../sbus/internal/config/ContactSensorType.java | 5 +++-- .../sbus/internal/handler/SbusHandlerFactory.java | 4 ++-- .../src/main/resources/OH-INF/i18n/sbus.properties | 2 +- .../src/main/resources/OH-INF/thing/thing-types.xml | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/README.md b/bundles/org.openhab.binding.sbus/README.md index aa1cf075190cd..fd494bff30f4c 100644 --- a/bundles/org.openhab.binding.sbus/README.md +++ b/bundles/org.openhab.binding.sbus/README.md @@ -10,7 +10,7 @@ The binding supports various thing types including RGB/RGBW controllers, tempera - `rgbw` - RGB/RGBW Controllers for color and brightness control - `temperature-sensor` - Temperature Sensors for monitoring environmental conditions - `switch` - Switch Controllers for basic on/off and dimming control -- `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both 012C and DB00 sensor types) +- `contact-sensor` - Contact Sensors for monitoring open/closed states (supports both 012C and 02CA sensor types) - `motion-sensor` - Motion Sensors for detecting movement - `lux-sensor` - Light Level Sensors for measuring illuminance - `temperature` - (Deprecated) Use `temperature-sensor` instead @@ -54,7 +54,7 @@ The `contact-sensor` thing type has an additional `type` parameter: | Name | Type | Description | Default | Required | Advanced | |:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:| -| type | text | Sensor type: `012c` (dry contact) or `db00` (multi-sensor) | 012c | no | no | +| type | text | Sensor type: `012c` (dry contact) or `02ca` (multi-sensor) | 012c | no | no | **Listen-Only Mode:** Setting `refresh=0` enables listen-only mode where the binding only processes broadcast messages without actively polling. This is useful for sensors that automatically broadcast their status updates. @@ -189,7 +189,7 @@ Number Lux_Sensor "Light Level [%.0f lux]" { channel="sbus:lux-sensor:mybr 9-in-1 sensors are multi-function sensors that combine motion detection, light level measurement, and dry contact monitoring in a single physical unit. To configure a 9-in-1 sensor in openHAB, you need to create **three separate things** that all reference the same physical sensor: -1. **contact-sensor** (type: `db00`) - For dry contact channels +1. **contact-sensor** (type: `02ca`) - For dry contact channels 2. **motion-sensor** - For motion detection 3. **lux-sensor** - For light level sensing @@ -198,7 +198,7 @@ All three things must use the **same subnet ID and unit ID** to represent the sa **Example for a 9-in-1 sensor with ID 85:** ```java -Thing contact-sensor sensor_contact [ type="db00", id=85, refresh=0 ] { +Thing contact-sensor sensor_contact [ type="02ca", id=85, refresh=0 ] { Channels: Type contact-channel : contact1 [ channelNumber=1 ] Type contact-channel : contact2 [ channelNumber=2 ] @@ -226,7 +226,7 @@ Thing lux-sensor sensor_lux [ id=85, refresh=0 ] { The `contact-sensor` thing type supports two different sensor types via the `type` parameter: - **`012c`** (default): 012C dry contact sensors -- **`db00`**: DB00 multi-sensor dry contacts +- **`02ca`**: 02CA multi-sensor dry contacts Choose the appropriate type based on your hardware. diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java index 7d5e52952dad1..44507697a313a 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/config/ContactSensorType.java @@ -27,9 +27,10 @@ public enum ContactSensorType { SENSOR_012C("012c"), /** - * DB00 multi-sensor with dry contacts using ReadNineInOneStatusRequest/Response protocol + * 02CA multi-sensor with dry contacts using ReadNineInOneStatusRequest/Response protocol + * and MotionSensorStatusReport (0x02CA) broadcasts */ - MULTI_SENSOR_DB00("db00"); + MULTI_SENSOR_02CA("02ca"); private final String configValue; diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java index 90a9aea98358d..d18518422e51b 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusHandlerFactory.java @@ -73,8 +73,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { SbusContactConfig config = thing.getConfiguration().as(SbusContactConfig.class); ContactSensorType sensorType = config.getSensorType(); - if (sensorType == ContactSensorType.MULTI_SENSOR_DB00) { - logger.debug("Creating Sbus DB00 multi-sensor contact handler for thing {}", thing.getUID()); + if (sensorType == ContactSensorType.MULTI_SENSOR_02CA) { + logger.debug("Creating Sbus 02CA multi-sensor contact handler for thing {}", thing.getUID()); return new Sbus9in1ContactHandler(thing); } else { logger.debug("Creating Sbus 012C contact sensor handler for thing {}", thing.getUID()); diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties index a638f82de92f4..7bda40eb86c0d 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/i18n/sbus.properties @@ -35,7 +35,7 @@ thing-type.config.sbus.rgbw.subnetId.option.255 = 255 thing-type.config.sbus.contact-sensor.type.label = Device Type thing-type.config.sbus.contact-sensor.type.description = Type of contact sensor device thing-type.config.sbus.contact-sensor.type.option.012c = 012C (Dry Contact Sensor) -thing-type.config.sbus.contact-sensor.type.option.db00 = DB00 (Multi-Sensor) +thing-type.config.sbus.contact-sensor.type.option.02ca = 02CA (Multi-Sensor) thing-type.config.sbus.contact-sensor.id.label = Device ID thing-type.config.sbus.contact-sensor.id.description = The ID of the Sbus device thing-type.config.sbus.contact-sensor.refresh.label = Refresh Interval diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index 4110c0d435730..dca60d426388f 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -143,7 +143,7 @@ Type of contact sensor device - + 012c From f099a1a8c588c2f19311c171e2a806ddce11703f Mon Sep 17 00:00:00 2001 From: Ciprian Pascu Date: Tue, 21 Oct 2025 20:32:06 +0300 Subject: [PATCH 18/18] [sbus] tested lux, motion & contact sensors Signed-off-by: Ciprian Pascu --- .../handler/Sbus9in1ContactHandler.java | 5 ---- .../handler/SbusLuxSensorHandler.java | 24 +++++++------------ .../handler/SbusMotionSensorHandler.java | 5 ---- .../resources/OH-INF/thing/thing-types.xml | 4 ++-- 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java index bbcbf860f504c..39ae576da2423 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/Sbus9in1ContactHandler.java @@ -96,11 +96,6 @@ protected void processAsyncMessage(SbusResponse response) { updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for 9-in-1 contact handler {}", getThing().getUID()); - // } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // // Process 9-in-1 status response - // updateContactChannelsFromResponse(statusResponse); - // updateStatus(ThingStatus.ONLINE); - // logger.debug("Processed async 9-in-1 status response for contact handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in 9-in-1 contact handler {}: {}", getThing().getUID(), diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java index 9cf56073034b6..e436ebe3d2c3c 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusLuxSensorHandler.java @@ -17,7 +17,8 @@ import org.openhab.binding.sbus.internal.SbusService; import org.openhab.binding.sbus.internal.config.SbusChannelConfig; import org.openhab.binding.sbus.internal.config.SbusDeviceConfig; -import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -90,17 +91,16 @@ protected void pollDevice() { */ private void updateChannelStatesFromResponse(ReadNineInOneStatusResponse response) { // Update lux channel (byte 2: LUX value - one byte unsigned, 0-255 range) - // Shift 8-bit value to 16-bit range, then normalize to 0-100% + // Shift 8-bit value to 16-bit range, then convert to lux with unit ChannelUID luxChannelUID = new ChannelUID(getThing().getUID(), BindingConstants.CHANNEL_LUX); int luxValueSigned = response.getLuxValue(); int luxValue8bit = luxValueSigned & 0xFF; // Convert to unsigned 8-bit value (0-255) int luxValue16bit = luxValue8bit << 8; // Shift to 16-bit range (0-65280) - int luxPercentage = (luxValue16bit * 100) / 65535; // Normalize to 0-100% - DecimalType luxValue = new DecimalType(luxPercentage); + QuantityType luxValue = new QuantityType<>(luxValue16bit, Units.LUX); updateState(luxChannelUID, luxValue); - logger.debug("Updated lux sensor state - LUX: {}% (8-bit: {}, 16-bit: {}, raw: {})", luxPercentage, - luxValue8bit, luxValue16bit, luxValueSigned); + logger.debug("Updated lux sensor state - LUX: {} (8-bit: {}, 16-bit: {}, raw: {})", luxValue16bit, luxValue8bit, + luxValue16bit, luxValueSigned); } /** @@ -110,15 +110,14 @@ private void updateChannelStatesFromResponse(ReadNineInOneStatusResponse respons */ private void updateChannelStatesFromReport(MotionSensorStatusReport report) { // Update lux channel (bytes 6-7: LUX value - two bytes unsigned, 0-65535 range) - // Normalize to 0-100% + // Convert to lux with unit ChannelUID luxChannelUID = new ChannelUID(getThing().getUID(), BindingConstants.CHANNEL_LUX); int luxValueSigned = report.getLuxValue(); int luxValue16bit = luxValueSigned & 0xFFFF; // Convert to unsigned 16-bit value (0-65535) - int luxPercentage = (luxValue16bit * 100) / 65535; // Normalize to 0-100% - DecimalType luxValue = new DecimalType(luxPercentage); + QuantityType luxValue = new QuantityType<>(luxValue16bit, Units.LUX); updateState(luxChannelUID, luxValue); - logger.debug("Updated lux sensor state from report - LUX: {}% (16-bit: {}, raw: {})", luxPercentage, + logger.debug("Updated lux sensor state from report - LUX: {} (16-bit: {}, raw: {})", luxValue16bit, luxValue16bit, luxValueSigned); } @@ -162,11 +161,6 @@ protected void processAsyncMessage(SbusResponse response) { updateChannelStatesFromReport(report); updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for lux handler {}", getThing().getUID()); - // } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // // Process 9-in-1 status response (0xDB01) - // updateChannelStatesFromResponse(statusResponse); - // updateStatus(ThingStatus.ONLINE); - // logger.debug("Processed async 9-in-1 status response for lux handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in lux sensor handler {}: {}", getThing().getUID(), diff --git a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java index b41f0e5d39f1a..cacc1791c9da2 100644 --- a/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java +++ b/bundles/org.openhab.binding.sbus/src/main/java/org/openhab/binding/sbus/internal/handler/SbusMotionSensorHandler.java @@ -151,11 +151,6 @@ protected void processAsyncMessage(SbusResponse response) { updateChannelStatesFromReport(report); updateStatus(ThingStatus.ONLINE); logger.debug("Processed async motion sensor status report for handler {}", getThing().getUID()); - // } else if (response instanceof ReadNineInOneStatusResponse statusResponse) { - // // Process 9-in-1 status response (0xDB01) - // updateChannelStatesFromResponse(statusResponse); - // updateStatus(ThingStatus.ONLINE); - // logger.debug("Processed async 9-in-1 status response for handler {}", getThing().getUID()); } } catch (IllegalStateException | IllegalArgumentException e) { logger.warn("Error processing async message in motion sensor handler {}: {}", getThing().getUID(), diff --git a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml index dca60d426388f..0febe48c06b23 100644 --- a/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -382,7 +382,7 @@ - Number + Number:Illuminance Light level in LUX units Sun @@ -390,7 +390,7 @@ Measurement Light - +