[sbus] Add support for Contact Sensor and (white) in RGBW channels (#18747)

Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
pull/18686/head
Ciprian Pascu 2025-06-03 22:44:15 +03:00 committed by GitHub
parent 19a755cfe8
commit 28aec48cb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 327 additions and 56 deletions

View File

@ -10,6 +10,7 @@ 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` - Contact Sensors for monitoring open/closed states
## Discovery
@ -29,57 +30,54 @@ The binding itself does not require any special configuration.
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 |
| port | integer | UDP port number | 6000 | no | no |
### RGBW Controller Configuration
### RGBW Controller, Contact, Switch, Temperature Configuration
| Name | Type | Description | Default | Required | Advanced |
|---------|---------|------------------------------------------------------|---------|----------|-----------|
|:--------|:--------|:-----------------------------------------------------|:-------:|:--------:|:---------:|
| subnetId| integer | Subnet ID the RGBW controller is part of | N/A | yes | no |
| id | integer | Device ID of the RGBW controller | N/A | yes | no |
| refresh | integer | Refresh interval in seconds | 30 | no | yes |
### Temperature Sensor Configuration
| Name | Type | Description | Default | Required | Advanced |
|---------|---------|------------------------------------------------------|---------|----------|-----------|
| subnetId| integer | Subnet ID the temperature sensor is part of | N/A | yes | no |
| id | integer | Device ID of the temperature sensor | N/A | yes | no |
| refresh | integer | Refresh interval in seconds | 30 | no | yes |
### Switch Controller Configuration
| Name | Type | Description | Default | Required | Advanced |
|---------|---------|------------------------------------------------------|---------|----------|-----------|
| subnetId| integer | Subnet ID the switch controller is part of | N/A | yes | no |
| id | integer | Device ID of the switch controller | N/A | yes | no |
| refresh | integer | Refresh interval in seconds | 30 | no | yes |
## Channels
### RGBW Controller Channels
| Channel | Type | Read/Write | Description |
|---------|--------|------------|------------------------------------------------------------|
| color | Color | RW | HSB color picker that controls RGBW components (0-100%) |
|:--------|:-------|:----------:|:-----------------------------------------------------------|
| color | Color | RW | HSB color picker that controls RGBW components (0-100%). Can be configured to disable the white channel. |
| switch | Switch | RW | On/Off control for the RGBW output with optional timer |
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 |
| enableWhite | boolean | Controls the white component support for RGB palette | true | no | yes |
### Temperature Sensor Channels
| Channel | Type | Read/Write | Description |
|-------------|---------------------|------------|--------------------------------|
|:------------|:--------------------|:----------:|:-------------------------------|
| temperature | Number:Temperature | R | Current temperature reading. Can be configured to use Celsius (default) or Fahrenheit units |
### Switch Controller Channels
| Channel | Type | Read/Write | Description |
|---------|----------------|------------|-----------------------------------------------------------|
|:--------|:---------------|:----------:|:----------------------------------------------------------|
| switch | Switch | RW | Basic ON/OFF state control |
| 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
| Channel | Type | Read/Write | Description |
|:--------|:--------|:----------:|:----------------------------------------------------------|
| contact | Contact | R | Contact state (OPEN/CLOSED) |
## Full Example
### Thing Configuration
@ -88,8 +86,14 @@ The Sbus Bridge has the following configuration parameters:
Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000 ] {
Thing rgbw colorctrl [ id=72, refresh=30 ] {
Channels:
Type color-channel : color [ channelNumber=1 ] // HSB color picker, RGBW values stored at channel 1
Type switch-channel : power [ channelNumber=1 ] // On/Off control for the RGBW output For complex scenes, one Sbus color controller can keep up to 40 color states. The switch channelNumber has to fall into this range.
Type color-channel : color [ channelNumber=1, enableWhite=true ] // Full RGBW control with white component
Type switch-channel : power [ channelNumber=1 ] // On/Off control for the RGBW output
}
Thing rgbw rgbonly [ id=73, refresh=30 ] {
Channels:
Type color-channel : color [ channelNumber=1, enableWhite=false ] // RGB only, no white component
Type switch-channel : power [ channelNumber=1 ]
}
Thing temperature temp1 [ id=62, refresh=30 ] {
@ -103,6 +107,11 @@ Bridge sbus:udp:mybridge [ host="192.168.1.255", port=5000 ] {
Type dimmer-channel : second_switch [ channelNumber=2 ]
Type paired-channel : third_switch [ channelNumber=3, pairedChannelNumber=4 ]
}
Thing contact contact1 [ id=80, refresh=30 ] {
Channels:
Type contact-channel : contact [ channelNumber=1 ]
}
}
```
@ -122,6 +131,9 @@ Rollershutter Rollershutter_Switch "Rollershutter [%s]" { channel="sbus:switch:m
Group gLight "RGBW Light" <light> ["Lighting"]
Color rgbwColor "Color" <colorwheel> (gLight) ["Control", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:color" }
Switch rgbwPower "Power" <switch> (gLight) ["Switch", "Light"] { channel="sbus:rgbw:mybridge:colorctrl:power" }
// Contact Sensor
Contact Door_Contact "Door [%s]" <door> { channel="sbus:contact:mybridge:contact1:contact" }
```
### Sitemap Configuration
@ -134,5 +146,30 @@ sitemap sbus label="Sbus Demo"
Text item=Temp_Sensor
Switch item=Light_Switch
Rollershutter item=Rollershutter_Switch
Text item=Door_Contact
}
}
## Usage Notes
### RGB vs. RGBW Mode
The `enableWhite` parameter for color channels controls whether the white component is used:
- Set to `true` (default): Full RGBW control with white component
- Set to `false`: RGB-only control with no white component
This is useful for:
- Pure RGB fixtures without a white channel
- Creating saturated colors without white dilution
- Specialized color effects where white would wash out the intended color
### Color Control and On/Off Functionality
The Color item type in openHAB inherently supports both color selection and on/off functionality:
- The color picker controls hue and saturation
- The brightness component (0-100%) functions as the on/off control
- When brightness is 0%, the light is OFF
- When brightness is >0%, the light is ON
This is why a Color item shows both a color picker and an on/off button in the UI without requiring a separate Switch item.

View File

@ -18,8 +18,7 @@
<dependency>
<groupId>ro.ciprianpascu</groupId>
<artifactId>j2sbus</artifactId>
<version>1.5.8</version>
<scope>compile</scope>
<version>1.5.9</version>
</dependency>
</dependencies>

View File

@ -36,6 +36,7 @@ public class BindingConstants {
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_RGBW = new ThingTypeUID(BINDING_ID, "rgbw");
public static final ThingTypeUID THING_TYPE_CONTACT = new ThingTypeUID(BINDING_ID, "contact");
// Channel IDs for Switch Device
public static final String CHANNEL_SWITCH_STATE = "state";
@ -55,4 +56,5 @@ public class BindingConstants {
public static final String CHANNEL_TYPE_SWITCH = "switch-channel";
public static final String CHANNEL_TYPE_DIMMER = "dimmer-channel";
public static final String CHANNEL_TYPE_PAIRED = "paired-channel";
public static final String CHANNEL_TYPE_CONTACT = "contact-channel";
}

View File

@ -0,0 +1,101 @@
/*
* 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.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sbus.handler.config.SbusChannelConfig;
import org.openhab.binding.sbus.handler.config.SbusDeviceConfig;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
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.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SbusContactHandler} is responsible for handling commands for Sbus contact devices.
* It supports reading the current contact state (open/closed).
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusContactHandler extends AbstractSbusHandler {
private final Logger logger = LoggerFactory.getLogger(SbusContactHandler.class);
public SbusContactHandler(Thing thing, TranslationProvider translationProvider, LocaleProvider localeProvider) {
super(thing, translationProvider, localeProvider);
}
@Override
protected void initializeChannels() {
// 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) {
Bundle bundle = FrameworkUtil.getBundle(getClass());
logger.warn("{}", translationProvider.getText(bundle, "error.channel.invalid-number",
channel.getUID().toString(), localeProvider.getLocale()));
}
}
}
@Override
protected void pollDevice() {
final SbusService adapter = super.sbusAdapter;
if (adapter == null) {
Bundle bundle = FrameworkUtil.getBundle(getClass());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, translationProvider.getText(bundle,
"error.device.adapter-not-initialized", null, localeProvider.getLocale()));
return;
}
try {
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
boolean[] contactStates = adapter.readContactStatusChannels(config.subnetId, config.id);
// Iterate over all channels and update their states
for (Channel channel : getThing().getChannels()) {
if (!isLinked(channel.getUID())) {
continue;
}
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
if (channelConfig.channelNumber > 0 && channelConfig.channelNumber <= contactStates.length) {
boolean isOpen = contactStates[channelConfig.channelNumber - 1];
updateState(channel.getUID(), isOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
}
}
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
Bundle bundle = FrameworkUtil.getBundle(getClass());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
translationProvider.getText(bundle, "error.device.read-state", null, localeProvider.getLocale()));
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Contact sensors are read-only
Bundle bundle = FrameworkUtil.getBundle(getClass());
logger.debug("{}",
translationProvider.getText(bundle, "info.contact.readonly", null, localeProvider.getLocale()));
}
}

View File

@ -61,7 +61,7 @@ public class SbusHandlerFactory extends BaseThingHandlerFactory {
}
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_UDP_BRIDGE, THING_TYPE_SWITCH,
THING_TYPE_TEMPERATURE, THING_TYPE_RGBW);
THING_TYPE_TEMPERATURE, THING_TYPE_RGBW, THING_TYPE_CONTACT);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@ -93,6 +93,9 @@ public class SbusHandlerFactory extends BaseThingHandlerFactory {
} else if (thingTypeUID.equals(THING_TYPE_RGBW)) {
logger.debug("Creating Sbus RGBW handler for thing {}", thing.getUID());
return new SbusRgbwHandler(thing, tp, lp);
} else if (thingTypeUID.equals(THING_TYPE_CONTACT)) {
logger.debug("Creating Sbus contact handler for thing {}", thing.getUID());
return new SbusContactHandler(thing, tp, lp);
}
logger.debug("Unknown thing type: {}", thingTypeUID);

View File

@ -20,6 +20,7 @@ import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
@ -47,24 +48,6 @@ public class SbusRgbwHandler extends AbstractSbusHandler {
super(thing, translationProvider, localeProvider);
}
/**
* Checks if any RGBW value is greater than 0.
*
* @param rgbw an int array [R, G, B, W] each in [0..255]
* @return true if any value is greater than 0, false otherwise
*/
private boolean isAnyRgbwValueActive(int[] rgbw) {
if (rgbw.length < 4) {
return false;
}
for (int value : rgbw) {
if (value > 0) {
return true;
}
}
return false;
}
@Override
protected void initializeChannels() {
int switchChannelCount = 0;
@ -129,13 +112,26 @@ public class SbusRgbwHandler extends AbstractSbusHandler {
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
if (BindingConstants.CHANNEL_TYPE_COLOR.equals(channelTypeId)) {
// Read status channels for switch states
int[] statuses = adapter.readStatusChannels(config.subnetId, config.id);
boolean isActive = isAnyRgbwValueActive(statuses);
// Read RGBW values for this channel
int[] rgbwValues = adapter.readRgbw(config.subnetId, config.id, channelConfig.channelNumber);
if (rgbwValues.length >= 4) {
// Convert RGBW to HSB using our custom conversion
HSBType hsbType = ColorUtil.rgbToHsb(rgbwValues);
updateState(channel.getUID(), hsbType);
int[] rgbwValues = isActive ? statuses
: adapter.readRgbw(config.subnetId, config.id, channelConfig.channelNumber);
fixRgbwValues(rgbwValues);
// Check if white channel should be disabled
boolean enableWhite = true; // Default to true if not specified
if (channel.getConfiguration().containsKey("enableWhite")) {
enableWhite = (boolean) channel.getConfiguration().get("enableWhite");
}
if (!enableWhite) {
rgbwValues = new int[] { rgbwValues[0], rgbwValues[1], rgbwValues[2] };
}
// Convert RGBW to HSB using our custom conversion
HSBType color = ColorUtil.rgbToHsb(rgbwValues);
color = new HSBType(color.getHue(), color.getSaturation(),
isActive ? PercentType.HUNDRED : PercentType.ZERO);
updateState(channel.getUID(), color);
} else if (BindingConstants.CHANNEL_TYPE_SWITCH.equals(channelTypeId)) {
// Read status channels for switch states
int[] statuses = adapter.readStatusChannels(config.subnetId, config.id);
@ -180,9 +176,28 @@ public class SbusRgbwHandler extends AbstractSbusHandler {
if (BindingConstants.CHANNEL_TYPE_COLOR.equals(channelTypeId)
&& command instanceof HSBType hsbCommand) {
// Handle color command
int[] rgbw = ColorUtil.hsbToRgbw(hsbCommand);
adapter.writeRgbw(config.subnetId, config.id, channelConfig.channelNumber, rgbw);
if (hsbCommand.getBrightness().intValue() == 0) {
adapter.writeSingleChannel(config.subnetId, config.id, channelConfig.channelNumber, 0, -1);
hsbCommand = new HSBType(hsbCommand.getHue(), hsbCommand.getSaturation(), PercentType.ZERO);
} else {
// Check if white channel should be disabled
boolean enableWhite = true; // Default to true if not specified
if (channel.getConfiguration().containsKey("enableWhite")) {
enableWhite = (boolean) channel.getConfiguration().get("enableWhite");
}
// Handle color command
// If white is disabled, set the white component (index 3) to 0
int[] rgbw = null;
if (enableWhite) {
rgbw = ColorUtil.hsbToRgbw(hsbCommand);
} else {
var converted = ColorUtil.hsbToRgb(hsbCommand);
rgbw = new int[] { converted[0], converted[1], converted[2], 0 };
}
adapter.writeRgbw(config.subnetId, config.id, channelConfig.channelNumber, rgbw);
adapter.writeSingleChannel(config.subnetId, config.id, channelConfig.channelNumber, 100, -1);
hsbCommand = new HSBType(hsbCommand.getHue(), hsbCommand.getSaturation(), PercentType.HUNDRED);
}
updateState(channelUID, hsbCommand);
} else if (BindingConstants.CHANNEL_TYPE_SWITCH.equals(channelTypeId)
&& command instanceof OnOffType onOffCommand) {
@ -199,4 +214,32 @@ public class SbusRgbwHandler extends AbstractSbusHandler {
translationProvider.getText(bundle, "error.device.send-command", null, localeProvider.getLocale()));
}
}
/**
* Checks if any RGBW value is greater than 0.
*
* @param rgbw an int array [R, G, B, W] each in [0..255]
* @return true if any value is greater than 0, false otherwise
*/
private boolean isAnyRgbwValueActive(int[] rgbw) {
for (int value : rgbw) {
if (value > 0) {
return true;
}
}
return false;
}
/**
* Checks if any RGBW value is smaller than 0.
*
* @param rgbw an int array [R, G, B, W] each in [0..255]
*/
private void fixRgbwValues(int[] rgbw) {
for (int i = 0; i < rgbw.length; i++) {
if (rgbw[i] < 0) {
rgbw[i] = 0;
}
}
}
}

View File

@ -54,6 +54,16 @@ public interface SbusService {
*/
int[] readStatusChannels(int subnetId, int id) throws Exception;
/**
* Reads contact status values from device channels.
*
* @param subnetId the subnet ID of the device
* @param id the device ID
* @return array of contact status values (true for open, false for closed)
* @throws Exception if reading fails
*/
boolean[] readContactStatusChannels(int subnetId, int id) throws Exception;
/**
* Writes RGBW values to a device channel.
*

View File

@ -78,7 +78,16 @@ public class SbusServiceImpl implements SbusService {
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
return adapter.readStatusChannels(subnetId, id);
return adapter.readExecutionStatusChannels(subnetId, id);
}
@Override
public boolean[] readContactStatusChannels(int subnetId, int id) throws Exception {
final SbusAdapter adapter = this.adapter;
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
return adapter.readContactStatusChannels(subnetId, id);
}
@Override
@ -96,7 +105,7 @@ public class SbusServiceImpl implements SbusService {
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
adapter.writeSingleChannel(subnetId, id, channelNumber, value, timer);
adapter.writeSingleExecutionChannel(subnetId, id, channelNumber, value, timer);
}
@Override

View File

@ -11,6 +11,8 @@ 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.udp.label = Sbus UDP Bridge
thing-type.sbus.udp.description = Endpoint for Sbus UDP slaves
@ -24,6 +26,14 @@ 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.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
@ -57,6 +67,8 @@ channel-type.sbus.switch-channel.label = Switch State
channel-type.sbus.switch-channel.description = Switch state (ON/OFF)
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 types config
@ -78,6 +90,8 @@ channel-type.config.sbus.temperature-channel.unit.label = Temperature Unit
channel-type.config.sbus.temperature-channel.unit.description = The unit to use for temperature readings (°C or °F)
channel-type.config.sbus.temperature-channel.unit.option.CELSIUS = Celsius
channel-type.config.sbus.temperature-channel.unit.option.FAHRENHEIT = Fahrenheit
channel-type.config.sbus.contact-channel.channelNumber.label = Channel Number
channel-type.config.sbus.contact-channel.channelNumber.description = The physical channel number on the Sbus device
# thing types config
@ -122,3 +136,4 @@ error.device.send-command = Error sending command to device
# info messages
info.temperature.readonly = Temperature device is read-only, ignoring command
info.contact.readonly = Contact device is read-only, ignoring command

View File

@ -98,6 +98,37 @@
</config-description>
</thing-type>
<!-- Contact Device -->
<thing-type id="contact">
<supported-bridge-type-refs>
<bridge-type-ref id="udp"/>
</supported-bridge-type-refs>
<label>Contact Sensor</label>
<description>Sbus contact sensor device</description>
<semantic-equipment-tag>ContactSensor</semantic-equipment-tag>
<config-description>
<parameter name="subnetId" type="integer">
<label>SubnetId</label>
<description>Slave subnet id. Can take any value between 1 and 255. 255 for broadcast.</description>
<default>1</default>
<options>
<option value="1">1</option>
<option value="255">255</option>
</options>
</parameter>
<parameter name="id" type="integer" required="true">
<label>Device ID</label>
<description>The ID of the Sbus device</description>
</parameter>
<parameter name="refresh" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval in seconds</description>
<default>30</default>
<unitLabel>s</unitLabel>
</parameter>
</config-description>
</thing-type>
<!-- Channel Types -->
<channel-type id="switch-channel">
<item-type>Switch</item-type>
@ -182,7 +213,28 @@
<label>Channel Number</label>
<description>The physical channel number on the Sbus device</description>
</parameter>
<parameter name="enableWhite" type="boolean">
<label>Enable White Channel</label>
<description>Whether to use the white channel component in addition to RGB</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
</config-description>
</channel-type>
<channel-type id="contact-channel">
<item-type>Contact</item-type>
<label>Contact State</label>
<description>Contact state (OPEN/CLOSED)</description>
<category>Contact</category>
<state readOnly="true"/>
<config-description>
<parameter name="channelNumber" type="integer" required="true">
<label>Channel Number</label>
<description>The physical channel number on the Sbus device</description>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>