This commit is contained in:
Ciprian Pascu 2025-01-08 19:37:32 +00:00 committed by GitHub
commit fe8bc9b233
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 1848 additions and 1 deletions

View File

@ -331,6 +331,7 @@
/bundles/org.openhab.binding.salus/ @magx2
/bundles/org.openhab.binding.samsungtv/ @NickWaterton
/bundles/org.openhab.binding.satel/ @druciak
/bundles/org.openhab.binding.sbus/ @cipianpascu
/bundles/org.openhab.binding.semsportal/ @itb3
/bundles/org.openhab.binding.senechome/ @vctender @KorbinianP @eguib
/bundles/org.openhab.binding.seneye/ @nikotanghe

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,132 @@
# 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.
## Supported Things
* `udp` - Sbus Bridge for UDP communication
* `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
## Discovery
Sbus devices communicate via UDP broadcast, but manual configuration is required to set up the devices in openHAB.
Auto-discovery is not supported at this moment.
## Binding Configuration
The binding itself does not require any special configuration.
## Thing Configuration
### Bridge Configuration
The Sbus Bridge requires 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
| 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%) |
| switch | Switch | RW | On/Off control for the RGBW output with optional timer |
### 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 | Paired | RW | ON/OFF state for two paired channels (e.g., curtains) |
## Full Example
### Thing Configuration
```
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.
}
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 ]
Type dimmer-channel : second_switch [ channelNumber=2 ]
Type paired-channel : third_switch [ channelNumber=3 ]
}
}
```
### Item Configuration
```
// Temperature Sensor
Number:Temperature Temp_Sensor "Temperature [%.1f °C]" { channel="sbus:temperature:mybridge:temp1:temperature" }
// Basic Switch
Switch Light_Switch "Switch" { channel="sbus:switch:mybridge:switch1:switch" }
// RGBW Controller with Power Control
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" }
```
### Sitemap Configuration
```
sitemap sbus label="Sbus Demo"
{
Frame label="Sbus Controls" {
Colorpicker item=Light_RGB
Text item=Temp_Sensor
Switch item=Light_Switch
}
}

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.sbus</artifactId>
<name>openHAB Add-ons :: Bundles :: Sbus Binding</name>
<dependencies>
<dependency>
<groupId>ro.ciprianpascu</groupId>
<artifactId>j2sbus</artifactId>
<version>1.5.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>6.0.0</version>
<configuration>
<instructions>
<Import-Package>
ro.ciprianpascu.sbus.facade,
*
</Import-Package>
<Embed-Dependency>j2sbus;scope=compile</Embed-Dependency>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.sbus-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-sbus" description="Sbus Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.sbus/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,52 @@
/**
* 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link BindingConstants} class defines common constants used across the Sbus binding.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class BindingConstants {
private BindingConstants() {
// Prevent instantiation
}
public static final String BINDING_ID = "sbus";
// Bridge Type
public static final ThingTypeUID THING_TYPE_UDP_BRIDGE = new ThingTypeUID(BINDING_ID, "udp");
// 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_RGBW = new ThingTypeUID(BINDING_ID, "rgbw");
// Channel IDs for Switch Device
public static final String CHANNEL_SWITCH_STATE = "state";
// Channel IDs for Temperature Device
public static final String CHANNEL_TEMPERATURE = "temperature";
// Channel IDs for RGBW Device
public static final String CHANNEL_RED = "red";
public static final String CHANNEL_GREEN = "green";
public static final String CHANNEL_BLUE = "blue";
public static final String CHANNEL_WHITE = "white";
public static final String CHANNEL_COLOR = "color";
}

View File

@ -0,0 +1,151 @@
/**
* 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 static org.openhab.binding.sbus.BindingConstants.BINDING_ID;
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.handler.config.SbusDeviceConfig;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
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.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AbstractSbusHandler} is the base class for all Sbus device handlers.
* It provides common functionality for device initialization, channel management, and polling.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractSbusHandler extends BaseThingHandler {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected @Nullable SbusService sbusAdapter;
protected @Nullable ScheduledFuture<?> pollingJob;
public AbstractSbusHandler(Thing thing) {
super(thing);
}
@Override
public final void initialize() {
logger.debug("Initializing Sbus handler for thing {}", getThing().getUID());
initializeChannels();
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
return;
}
SbusBridgeHandler bridgeHandler = (SbusBridgeHandler) bridge.getHandler();
if (bridgeHandler == null || bridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge is not online");
return;
}
sbusAdapter = bridgeHandler.getSbusConnection();
if (sbusAdapter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Bridge connection not initialized");
return;
}
startPolling();
}
/**
* Initialize channels for this device based on its configuration.
* This method should be implemented by concrete handlers to set up their specific channels.
*/
protected abstract void initializeChannels();
/**
* Create or update a channel with the specified ID and type.
*
* @param channelId The ID of the channel to create/update
* @param channelTypeId The type ID of the channel
*/
protected void createChannel(String channelId, String channelTypeId) {
ThingBuilder thingBuilder = ThingBuilder.create(getThing().getThingTypeUID(), getThing().getUID())
.withConfiguration(getThing().getConfiguration()).withBridge(getThing().getBridgeUID());
// Add all existing channels except the one we're creating/updating
ChannelUID newChannelUID = new ChannelUID(getThing().getUID(), channelId);
for (Channel existingChannel : getThing().getChannels()) {
if (!existingChannel.getUID().equals(newChannelUID)) {
thingBuilder.withChannel(existingChannel);
}
}
// Add the new channel
Channel channel = ChannelBuilder.create(newChannelUID).withType(new ChannelTypeUID(BINDING_ID, channelTypeId))
.withConfiguration(new Configuration()).build();
thingBuilder.withChannel(channel);
// Update the thing with the new channel configuration
updateThing(thingBuilder.build());
}
/**
* Start polling the device for updates based on the configured refresh interval.
*/
protected void startPolling() {
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
if (config.refresh > 0) {
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
try {
pollDevice();
} catch (Exception e) {
logger.error("Error polling Sbus device", e);
}
}, 0, config.refresh, TimeUnit.SECONDS);
}
}
/**
* Poll the device for updates. This method should be implemented by concrete handlers
* to update their specific channel states.
*/
protected abstract void pollDevice();
@Override
public void dispose() {
ScheduledFuture<?> job = pollingJob;
if (job != null) {
job.cancel(true);
}
final SbusService adapter = sbusAdapter;
if (adapter != null) {
adapter.close();
}
pollingJob = null;
sbusAdapter = null;
super.dispose();
}
}

View File

@ -0,0 +1,104 @@
/**
* 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.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sbus.handler.config.SbusBridgeConfig;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SbusBridgeHandler} is responsible for handling communication with the Sbus bridge.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(SbusBridgeHandler.class);
@Reference
private @Nullable SbusService sbusService;
/**
* Constructs a new SbusBridgeHandler.
*
* @param bridge the bridge
*/
public SbusBridgeHandler(Bridge bridge) {
super(bridge);
}
/**
* Initializes the Sbus bridge handler by establishing a connection to the Sbus network.
*/
@Override
public void initialize() {
logger.debug("Initializing Sbus bridge handler for bridge {}", getThing().getUID());
// Get configuration using the config class
SbusBridgeConfig config = getConfigAs(SbusBridgeConfig.class);
if (config.host.isBlank()) {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR, "Host address not configured");
return;
}
try {
// Initialize Sbus service with the configuration parameters
final SbusService service = sbusService;
if (service == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"Sbus service not available");
return;
}
service.initialize(config.host, config.port);
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
/**
* Gets the Sbus service.
*
* @return the Sbus service
*/
public @Nullable SbusService getSbusConnection() {
return sbusService;
}
/**
* Disposes the handler by closing the Sbus connection.
*/
@Override
public void dispose() {
logger.debug("Disposing Sbus bridge handler");
final SbusService service = sbusService;
if (service != null) {
service.close();
}
super.dispose();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Bridge doesn't handle commands directly
}
}

View File

@ -0,0 +1,73 @@
/**
* 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 static org.openhab.binding.sbus.BindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SbusHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.sbus")
public class SbusHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(SbusHandlerFactory.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_UDP_BRIDGE, THING_TYPE_SWITCH,
THING_TYPE_TEMPERATURE, THING_TYPE_RGBW);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_UDP_BRIDGE)) {
logger.debug("Creating Sbus UDP bridge handler for thing {}", thing.getUID());
return new SbusBridgeHandler((Bridge) thing);
}
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());
return new SbusTemperatureHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_RGBW)) {
logger.debug("Creating Sbus RGBW handler for thing {}", thing.getUID());
return new SbusRgbwHandler(thing);
}
logger.debug("Unknown thing type: {}", thingTypeUID);
return null;
}
}

View File

@ -0,0 +1,246 @@
/**
* 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.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;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.util.ColorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SbusRgbwHandler} is responsible for handling commands for Sbus RGBW devices.
* It supports reading and controlling red, green, blue, and white color channels.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusRgbwHandler extends AbstractSbusHandler {
private final Logger logger = LoggerFactory.getLogger(SbusRgbwHandler.class);
public SbusRgbwHandler(Thing thing) {
super(thing);
}
/**
* Converts an openHAB HSBType into an RGBW array ([R, G, B, W]),
* with each channel in [0..255].
*
* We extract 'white' by taking the minimum of R, G, B and
* subtracting it from each color channel.
*
* @param hsbType the openHAB HSBType (hue [0..360], sat [0..100], bri [0..100])
* @return an int array [R, G, B, W] each in [0..255]
*/
private int[] hsbToRgbw(HSBType hsbType) {
// Convert HSBType to standard RGB [0..255]
PercentType[] rgb = ColorUtil.hsbToRgbPercent(hsbType);
// Convert each channel from 0..100 to 0..255
int r = (int) Math.round(rgb[0].floatValue() * 2.55);
int g = (int) Math.round(rgb[1].floatValue() * 2.55);
int b = (int) Math.round(rgb[2].floatValue() * 2.55);
// Determine the white component as the min of R, G, B
int w = Math.min(r, Math.min(g, b));
// Subtract W from each
r -= w;
g -= w;
b -= w;
return new int[] { r, g, b, w };
}
/**
* Converts an RGBW array ([R, G, B, W]) back to an openHAB HSBType.
*
* We add the W channel back into R, G, and B, then clamp to [0..255].
* Finally, we create an HSBType via fromRGB().
*
* @param rgbw an int array [R, G, B, W] each in [0..255]
* @return an HSBType (hue [0..360], saturation/brightness [0..100])
*/
private HSBType rgbwToHsb(int[] rgbw) {
if (rgbw.length < 4) {
throw new IllegalArgumentException("rgbw must have 4 elements: [R, G, B, W].");
}
int r = rgbw[0];
int g = rgbw[1];
int b = rgbw[2];
int w = rgbw[3];
// Restore the combined R, G, B
int rTotal = r + w;
int gTotal = g + w;
int bTotal = b + w;
// Clamp to [0..255]
rTotal = Math.min(255, Math.max(0, rTotal));
gTotal = Math.min(255, Math.max(0, gTotal));
bTotal = Math.min(255, Math.max(0, bTotal));
// Convert back to an HSBType via fromRGB
HSBType hsbType = HSBType.fromRGB(rTotal, gTotal, bTotal);
return hsbType;
}
/**
* 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;
// Validate all channel configurations
for (Channel channel : getThing().getChannels()) {
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
var channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID == null) {
logger.warn("Channel {} has no channel type", channel.getUID());
continue;
}
String channelTypeId = channelTypeUID.getId();
if ("color-channel".equals(channelTypeId)) {
if (channelConfig.channelNumber <= 0) {
logger.warn("Channel {} has invalid channel number configuration", channel.getUID());
}
}
if ("switch-channel".equals(channelTypeId)) {
switchChannelCount++;
if (channelConfig.channelNumber <= 0) {
logger.warn("Channel {} has invalid channel number configuration", channel.getUID());
}
}
}
if (switchChannelCount > 1) {
logger.error("Only one switch channel is allowed for RGBW thing {}", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Only one switch channel is allowed");
return;
}
}
@Override
protected void pollDevice() {
final SbusService adapter = super.sbusAdapter;
if (adapter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Sbus adapter not initialized");
return;
}
try {
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
// Update all channels
for (Channel channel : getThing().getChannels()) {
var channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID == null) {
logger.warn("Channel {} has no channel type", channel.getUID());
continue;
}
String channelTypeId = channelTypeUID.getId();
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
if ("color-channel".equals(channelTypeId)) {
// 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 = rgbwToHsb(rgbwValues);
updateState(channel.getUID(), hsbType);
}
} else if ("switch-channel".equals(channelTypeId)) {
// Read status channels for switch states
int[] statuses = adapter.readStatusChannels(config.subnetId, config.id);
// Update switch state
boolean isActive = isAnyRgbwValueActive(statuses);
updateState(channel.getUID(), isActive ? OnOffType.ON : OnOffType.OFF);
}
}
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error reading device state");
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
final SbusService adapter = super.sbusAdapter;
if (adapter == null) {
logger.warn("Sbus adapter not initialized");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Sbus adapter not initialized");
return;
}
try {
Channel channel = getThing().getChannel(channelUID.getId());
if (channel != null) {
var channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID == null) {
logger.warn("Channel {} has no channel type", channel.getUID());
return;
}
String channelTypeId = channelTypeUID.getId();
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
if ("color-channel".equals(channelTypeId) && command instanceof HSBType hsbCommand) {
// Handle color command
int[] rgbw = hsbToRgbw(hsbCommand);
adapter.writeRgbw(config.subnetId, config.id, channelConfig.channelNumber, rgbw[0], rgbw[1],
rgbw[2], rgbw[3]);
updateState(channelUID, hsbCommand);
} else if ("switch-channel".equals(channelTypeId) && command instanceof OnOffType onOffCommand) {
// Handle switch command
boolean isOn = onOffCommand == OnOffType.ON;
adapter.writeSingleChannel(config.subnetId, config.id, channelConfig.channelNumber, isOn ? 100 : 0,
-1);
updateState(channelUID, isOn ? OnOffType.ON : OnOffType.OFF);
}
}
} catch (Exception e) {
logger.error("Error handling command", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error sending command to device");
}
}
}

View File

@ -0,0 +1,88 @@
/**
* 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;
/**
* The {@link SbusService} defines the interface for handling Sbus communication.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public interface SbusService {
/**
* Reads temperature values from a device.
*
* @param subnetId the subnet ID of the device
* @param id the device ID
* @return array of temperature values in Celsius
* @throws Exception if reading fails
*/
float[] readTemperatures(int subnetId, int id) throws Exception;
/**
* Reads RGBW values from a device channel.
*
* @param subnetId the subnet ID of the device
* @param id the device ID
* @param channelNumber the channel number to read
* @return array of RGBW values [R, G, B, W]
* @throws Exception if reading fails
*/
int[] readRgbw(int subnetId, int id, int channelNumber) throws Exception;
/**
* Reads status values from device channels.
*
* @param subnetId the subnet ID of the device
* @param id the device ID
* @return array of channel status values
* @throws Exception if reading fails
*/
int[] readStatusChannels(int subnetId, int id) throws Exception;
/**
* Writes RGBW values to a device channel.
*
* @param subnetId the subnet ID of the device
* @param id the device ID
* @param channelNumber the channel number to write to
* @param r red value (0-255)
* @param g green value (0-255)
* @param b blue value (0-255)
* @param w white value (0-255)
* @throws Exception if writing fails
*/
void writeRgbw(int subnetId, int id, int channelNumber, int r, int g, int b, int w) throws Exception;
/**
* Writes a value to a single channel.
*
* @param subnetId the subnet ID of the device
* @param id the device ID
* @param channelNumber the channel number to write to
* @param value the value to write
* @param timer timer value (-1 for no timer)
* @throws Exception if writing fails
*/
void writeSingleChannel(int subnetId, int id, int channelNumber, int value, int timer) throws Exception;
/**
* Closes the service and releases resources.
*/
void close();
void initialize(String host, int port) throws Exception;
}

View File

@ -0,0 +1,167 @@
/**
* 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.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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;
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;
/**
* The {@link SbusSwitchHandler} is responsible for handling commands for Sbus switch devices.
* It supports reading the current state and switching the device on/off.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusSwitchHandler extends AbstractSbusHandler {
private final Logger logger = LoggerFactory.getLogger(SbusSwitchHandler.class);
public SbusSwitchHandler(Thing thing) {
super(thing);
}
@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) {
logger.warn("Channel {} has invalid channel number configuration", channel.getUID());
}
}
}
@Override
protected void pollDevice() {
final SbusService adapter = super.sbusAdapter;
if (adapter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Sbus adapter not initialized");
return;
}
try {
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
int[] statuses = adapter.readStatusChannels(config.subnetId, config.id);
// Iterate over all channels and update their states
for (Channel channel : getThing().getChannels()) {
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
if (channelConfig.channelNumber > 0 && channelConfig.channelNumber <= statuses.length) {
var channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID == null) {
logger.warn("Channel {} has no channel type", channel.getUID());
continue;
}
String channelTypeId = channelTypeUID.getId();
// 100 when on, something else when off
boolean isActive = statuses[channelConfig.channelNumber - 1] == 0x64;
if ("switch-channel".equals(channelTypeId)) {
updateState(channel.getUID(), isActive ? OnOffType.ON : OnOffType.OFF);
} else if ("dimmer-channel".equals(channelTypeId)) {
updateState(channel.getUID(), new PercentType(statuses[channelConfig.channelNumber - 1]));
} else if ("paired-channel".equals(channelTypeId)) {
updateState(channel.getUID(), isActive ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
}
}
}
} catch (Exception e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error reading device state");
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
final SbusService adapter = super.sbusAdapter;
if (adapter == null) {
logger.warn("Sbus adapter not initialized");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Sbus adapter not initialized");
return;
}
try {
Channel channel = getThing().getChannel(channelUID);
if (channel != null) {
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
if (channelConfig.channelNumber <= 0) {
logger.warn("Invalid channel number for {}", channelUID);
return;
}
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
if (command instanceof OnOffType) {
handleOnOffCommand((OnOffType) command, config, channelConfig, channelUID, adapter);
} else if (command instanceof PercentType) {
handlePercentCommand((PercentType) command, config, channelConfig, channelUID, adapter);
} else if (command instanceof OpenClosedType) {
handleOpenClosedCommand((OpenClosedType) command, config, channelConfig, channelUID, adapter);
}
}
} catch (Exception e) {
logger.error("Error handling command", e);
}
}
private void handleOnOffCommand(OnOffType command, SbusDeviceConfig config, SbusChannelConfig channelConfig,
ChannelUID channelUID, SbusService adapter) throws Exception {
boolean isOn = command == OnOffType.ON;
adapter.writeSingleChannel(config.subnetId, config.id, channelConfig.channelNumber, isOn ? 100 : 0,
channelConfig.timer);
updateState(channelUID, isOn ? OnOffType.ON : OnOffType.OFF);
}
private void handlePercentCommand(PercentType command, SbusDeviceConfig config, SbusChannelConfig channelConfig,
ChannelUID channelUID, SbusService adapter) throws Exception {
adapter.writeSingleChannel(config.subnetId, config.id, channelConfig.channelNumber, command.intValue(),
channelConfig.timer);
updateState(channelUID, command);
}
private void handleOpenClosedCommand(OpenClosedType command, SbusDeviceConfig config,
SbusChannelConfig channelConfig, ChannelUID channelUID, SbusService adapter) throws Exception {
boolean isOpen = command == OpenClosedType.OPEN;
// Set main channel
if (getChannelToClose(channelConfig, isOpen) > 0) {
adapter.writeSingleChannel(config.subnetId, config.id, getChannelToClose(channelConfig, isOpen), 0,
channelConfig.timer);
}
// Set paired channel to opposite state if configured
if (getChannelToOpen(channelConfig, isOpen) > 0) {
adapter.writeSingleChannel(config.subnetId, config.id, getChannelToOpen(channelConfig, isOpen), 0x64,
channelConfig.timer);
}
updateState(channelUID, isOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
}
private int getChannelToOpen(SbusChannelConfig channelConfig, boolean state) {
return state ? channelConfig.channelNumber : channelConfig.pairedChannelNumber;
}
private int getChannelToClose(SbusChannelConfig channelConfig, boolean state) {
return state ? channelConfig.pairedChannelNumber : channelConfig.channelNumber;
}
}

View File

@ -0,0 +1,100 @@
/**
* 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.SbusDeviceConfig;
import org.openhab.binding.sbus.handler.config.TemperatureChannelConfig;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
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;
/**
* The {@link SbusTemperatureHandler} is responsible for handling commands for Sbus temperature sensors.
* It supports reading temperature values in Celsius.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusTemperatureHandler extends AbstractSbusHandler {
private final Logger logger = LoggerFactory.getLogger(SbusTemperatureHandler.class);
public SbusTemperatureHandler(Thing thing) {
super(thing);
}
@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
TemperatureChannelConfig channelConfig = channel.getConfiguration().as(TemperatureChannelConfig.class);
if (!channelConfig.isValid()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Invalid channel configuration: " + channel.getUID());
return;
}
}
}
@Override
protected void pollDevice() {
final SbusService adapter = super.sbusAdapter;
if (adapter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Sbus adapter not initialized");
return;
}
try {
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
// Read temperatures in Celsius from device
float[] temperatures = adapter.readTemperatures(config.subnetId, config.id);
// Iterate over all channels and update their states with corresponding temperatures
for (Channel channel : getThing().getChannels()) {
TemperatureChannelConfig channelConfig = channel.getConfiguration().as(TemperatureChannelConfig.class);
if (channelConfig.channelNumber > 0 && channelConfig.channelNumber <= temperatures.length) {
float temperatureCelsius = temperatures[channelConfig.channelNumber - 1];
if (channelConfig.isFahrenheit()) {
// Convert Celsius to Fahrenheit
float temperatureFahrenheit = (temperatureCelsius * 9 / 5) + 32;
updateState(channel.getUID(),
new QuantityType<>(temperatureFahrenheit, ImperialUnits.FAHRENHEIT));
} else {
updateState(channel.getUID(), new QuantityType<>(temperatureCelsius, SIUnits.CELSIUS));
}
}
}
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error reading device state");
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Temperature sensors are read-only
logger.debug("Temperature device is read-only, ignoring command");
}
}

View File

@ -0,0 +1,35 @@
/**
* 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import ro.ciprianpascu.sbus.Sbus;
/**
* The {@link SbusBridgeConfig} class contains fields mapping bridge configuration parameters.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusBridgeConfig {
/**
* The host address of the Sbus bridge
*/
public String host = "localhost";
/**
* The port number for Sbus communication
*/
public int port = Sbus.DEFAULT_PORT;
}

View File

@ -0,0 +1,39 @@
/**
* 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SbusChannelConfig} class contains fields mapping channel configuration parameters.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusChannelConfig {
/**
* The physical channel number on the Sbus device.
*/
public int channelNumber;
/**
* The paired channel number for OpenClosedType channels.
* When the main channel is opened, this channel will be closed and vice versa.
*/
public int pairedChannelNumber;
/**
* Timer in seconds to automatically turn off the switch (-1 = disabled).
*/
public int timer = 0;
}

View File

@ -0,0 +1,40 @@
/**
* 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import ro.ciprianpascu.sbus.Sbus;
/**
* The {@link SbusDeviceConfig} class contains fields mapping thing configuration parameters.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class SbusDeviceConfig {
/**
* The ID of the Sbus device
*/
public int id = Sbus.DEFAULT_UNIT_ID;
/**
* The subnet ID for Sbus communication
*/
public int subnetId = Sbus.DEFAULT_SUBNET_ID;
/**
* Refresh interval in seconds
*/
public int refresh = 30; // Default value from thing-types.xml
}

View File

@ -0,0 +1,52 @@
/**
* 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration class for the temperature channel.
*
* @author Ciprian Pascu - Initial contribution
*/
@NonNullByDefault
public class TemperatureChannelConfig {
/**
* The physical channel number on the Sbus device
*/
public int channelNumber = 1;
/**
* The unit to use for temperature readings (CELSIUS or FAHRENHEIT)
*/
public String unit = "CELSIUS";
/**
* Validates the configuration parameters.
*
* @return true if the configuration is valid
*/
public boolean isValid() {
return channelNumber > 0 && ("CELSIUS".equals(unit) || "FAHRENHEIT".equals(unit));
}
/**
* Checks if the configured unit is Fahrenheit
*
* @return true if Fahrenheit is configured, false otherwise
*/
public boolean isFahrenheit() {
return "FAHRENHEIT".equals(unit);
}
}

View File

@ -0,0 +1,108 @@
/**
* 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sbus.handler.SbusService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import ro.ciprianpascu.sbus.facade.SbusAdapter;
/**
* The {@link SbusServiceImpl} implements the SbusService interface by delegating to SbusAdapter.
*
* @author Ciprian Pascu - Initial contribution
*/
@Component(service = SbusService.class)
@NonNullByDefault
public class SbusServiceImpl implements SbusService {
private @Nullable SbusAdapter adapter;
@Activate
public SbusServiceImpl() {
// Service is activated but adapter is initialized later with connection parameters
}
/**
* Initializes the underlying SbusAdapter with connection parameters.
*
* @param host the host address of the Sbus device
* @param port the port number to use
* @throws Exception if initialization fails
*/
public void initialize(String host, int port) throws Exception {
this.adapter = new SbusAdapter(host, port);
}
@Deactivate
public void deactivate() {
close();
}
@Override
public float[] readTemperatures(int subnetId, int id) throws Exception {
final SbusAdapter adapter = this.adapter;
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
return adapter.readTemperatures(subnetId, id);
}
@Override
public int[] readRgbw(int subnetId, int id, int channelNumber) throws Exception {
final SbusAdapter adapter = this.adapter;
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
return adapter.readRgbw(subnetId, id, channelNumber);
}
@Override
public int[] readStatusChannels(int subnetId, int id) throws Exception {
final SbusAdapter adapter = this.adapter;
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
return adapter.readStatusChannels(subnetId, id);
}
@Override
public void writeRgbw(int subnetId, int id, int channelNumber, int r, int g, int b, int w) throws Exception {
final SbusAdapter adapter = this.adapter;
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
adapter.writeRgbw(subnetId, id, channelNumber, r, g, b, w);
}
@Override
public void writeSingleChannel(int subnetId, int id, int channelNumber, int value, int timer) throws Exception {
final SbusAdapter adapter = this.adapter;
if (adapter == null) {
throw new IllegalStateException("SbusAdapter not initialized");
}
adapter.writeSingleChannel(subnetId, id, channelNumber, value, timer);
}
@Override
public void close() {
final SbusAdapter adapter = this.adapter;
if (adapter != null) {
adapter.close();
this.adapter = null;
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="sbus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Sbus Binding</name>
<description>Binding for Sbus</description>
<connection>local</connection>
</addon:addon>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="profile:sbus:gainOffset">
<parameter name="pre-gain-offset" type="decimal">
<label>Pre-gain Offset</label>
<description>Offset to add to raw value towards the item (before the gain). The negative
offset will be applied in the
reverse direction (before inverting the gain). If omitted, zero offset is used.</description>
</parameter>
<parameter name="gain" type="text">
<label>Gain</label>
<description>Gain to apply to the state towards the item. One can also specify the unit to declare resulting unit.
This is used as divisor for values in the reverse direction. If omitted, gain of 1 is used.</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,95 @@
# add-on
addon.sbus.name = Sbus Binding
addon.sbus.description = Binding for Sbus
# thing types
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.udp.label = Sbus UDP Slave
thing-type.sbus.udp.description = Endpoint for Sbus UDP slaves
# thing types config
thing-type.config.sbus.rgbw.id.label = Device ID
thing-type.config.sbus.rgbw.id.description = The ID of the Sbus device
thing-type.config.sbus.rgbw.refresh.label = Refresh Interval
thing-type.config.sbus.rgbw.refresh.description = Refresh interval in seconds
thing-type.config.sbus.rgbw.subnetId.label = SubnetId
thing-type.config.sbus.rgbw.subnetId.description = Slave subnet id. Can take any value between 0 and 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
thing-type.config.sbus.switch.refresh.description = Refresh interval in seconds
thing-type.config.sbus.switch.subnetId.label = SubnetId
thing-type.config.sbus.switch.subnetId.description = Slave subnet id. Can take any value between 0 and 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
thing-type.config.sbus.temperature.refresh.description = Refresh interval in seconds
thing-type.config.sbus.temperature.subnetId.label = SubnetId
thing-type.config.sbus.temperature.subnetId.description = Slave subnet id. Can take any value between 0 and 255.
thing-type.config.sbus.udp.afterConnectionDelayMillis.label = Connection warm-up time
thing-type.config.sbus.udp.afterConnectionDelayMillis.description = Connection warm-up time. Additional time which is spent on preparing connection which should be spent waiting while end device is getting ready to answer first sbus call. In milliseconds.
thing-type.config.sbus.udp.connectMaxTries.label = Maximum Connection Tries
thing-type.config.sbus.udp.connectMaxTries.description = How many times we try to establish the connection. Should be at least 1.
thing-type.config.sbus.udp.connectTimeoutMillis.label = Timeout for Establishing the Connection
thing-type.config.sbus.udp.connectTimeoutMillis.description = The maximum time that is waited when establishing the connection. Value of zero means that system/OS default is respected. In milliseconds.
thing-type.config.sbus.udp.enableDiscovery.label = Discovery Enabled
thing-type.config.sbus.udp.enableDiscovery.description = When enabled we try to find a device specific handler. Turn this on if you're using one of the supported devices.
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
thing-type.config.sbus.udp.port.description = Port of the slave
thing-type.config.sbus.udp.reconnectAfterMillis.label = Reconnect Again After
thing-type.config.sbus.udp.reconnectAfterMillis.description = The connection is kept open at least the time specified here. Value of zero means that connection is disconnected after every Sbus transaction. In milliseconds.
thing-type.config.sbus.udp.rtuEncoded.label = RTU Encoding
thing-type.config.sbus.udp.rtuEncoded.description = Use RTU Encoding over IP
thing-type.config.sbus.udp.timeBetweenReconnectMillis.label = Time Between Reconnections
thing-type.config.sbus.udp.timeBetweenReconnectMillis.description = How long to wait to before trying to establish a new connection after the previous one has been disconnected. In milliseconds.
thing-type.config.sbus.udp.timeBetweenTransactionsMillis.label = Time Between Transactions
thing-type.config.sbus.udp.timeBetweenTransactionsMillis.description = How long to delay we must have at minimum between two consecutive Sbus transactions. In milliseconds.
# channel group types
channel-group-type.sbus.colors.label = Color Channels
channel-group-type.sbus.colors.description = Group of RGBW color channels
channel-group-type.sbus.sensors.label = Temperature Sensors
channel-group-type.sbus.sensors.description = Group of temperature sensors
channel-group-type.sbus.switches.label = Switch Channels
channel-group-type.sbus.switches.description = Group of switch channels
# channel types
channel-type.sbus.color-channel.label = Color
channel-type.sbus.color-channel.description = Color control
channel-type.sbus.dimmer-channel.label = Dimmer State
channel-type.sbus.dimmer-channel.description = Dimmer state (0-100%)
channel-type.sbus.paired-channel.label = Paired Channel State
channel-type.sbus.paired-channel.description = Paired channel state (OPEN/CLOSED) - controls two opposite channels
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 types config
channel-type.config.sbus.color-channel.channelNumber.label = Channel Number
channel-type.config.sbus.color-channel.channelNumber.description = The physical channel number on the Sbus device
channel-type.config.sbus.dimmer-channel.channelNumber.label = Channel Number
channel-type.config.sbus.dimmer-channel.channelNumber.description = The physical channel number on the Sbus device
channel-type.config.sbus.dimmer-channel.timer.label = Timer
channel-type.config.sbus.dimmer-channel.timer.description = Timer in seconds to automatically turn off the switch (0 = disabled)
channel-type.config.sbus.paired-channel.channelNumber.label = Channel Number
channel-type.config.sbus.paired-channel.channelNumber.description = The physical channel number on the Sbus device
channel-type.config.sbus.paired-channel.pairedChannelNumber.label = Paired Channel Number
channel-type.config.sbus.paired-channel.pairedChannelNumber.description = The physical channel number of the paired channel (will be set to opposite state)
channel-type.config.sbus.switch-channel.channelNumber.label = Channel Number
channel-type.config.sbus.switch-channel.channelNumber.description = The physical channel number on the Sbus device
channel-type.config.sbus.temperature-channel.channelNumber.label = Channel Number
channel-type.config.sbus.temperature-channel.channelNumber.description = The physical channel number on the Sbus device

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="sbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="udp">
<label>Sbus UDP Bridge</label>
<description>Endpoint for Sbus UDP slaves</description>
<config-description>
<parameter name="host" type="text" required="true">
<label>IP Address or Hostname</label>
<description>Network address of the device</description>
<default>localhost</default>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer">
<label>Port</label>
<description>Port of the slave</description>
<default>6000</default>
</parameter>
<parameter name="enableDiscovery" type="boolean">
<label>Discovery Enabled</label>
<description>When enabled we try to find a device specific handler. Turn this on if you're using one of the
supported devices.</description>
<default>false</default>
</parameter>
<parameter name="rtuEncoded" type="boolean">
<label>RTU Encoding</label>
<description>Use RTU Encoding over IP</description>
<default>false</default>
</parameter>
<!-- connection handling -->
<parameter name="timeBetweenTransactionsMillis" type="integer" min="0" unit="ms">
<label>Time Between Transactions</label>
<description>How long to delay we must have at minimum between two consecutive Sbus transactions. In milliseconds.
</description>
<default>60</default>
</parameter>
<parameter name="timeBetweenReconnectMillis" type="integer" min="0" unit="ms">
<label>Time Between Reconnections</label>
<description>How long to wait to before trying to establish a new connection after the previous one has been
disconnected. In milliseconds.</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
<parameter name="connectMaxTries" type="integer" min="1">
<label>Maximum Connection Tries</label>
<description>How many times we try to establish the connection. Should be at least 1.</description>
<default>1</default>
<advanced>true</advanced>
</parameter>
<parameter name="afterConnectionDelayMillis" type="integer" min="0" unit="ms">
<label>Connection warm-up time</label>
<description>Connection warm-up time. Additional time which is spent on preparing connection which should be spent
waiting while end device is getting ready to answer first sbus call. In milliseconds.</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
<parameter name="reconnectAfterMillis" type="integer" min="0" unit="ms">
<label>Reconnect Again After</label>
<description>The connection is kept open at least the time specified here. Value of zero means that connection is
disconnected after every Sbus transaction. In milliseconds.</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
<parameter name="connectTimeoutMillis" type="integer" min="0" unit="ms">
<label>Timeout for Establishing the Connection</label>
<description>The maximum time that is waited when establishing the connection. Value of zero means that system/OS
default is respected. In milliseconds.</description>
<default>10000</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="sbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0
https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Switch Device -->
<thing-type id="switch">
<supported-bridge-type-refs>
<bridge-type-ref id="udp"/>
</supported-bridge-type-refs>
<label>Sbus Switch</label>
<description>Sbus switch device</description>
<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>
<!-- Temperature Device -->
<thing-type id="temperature">
<supported-bridge-type-refs>
<bridge-type-ref id="udp"/>
</supported-bridge-type-refs>
<label>Sbus Temperature Sensor</label>
<description>Sbus temperature sensor device</description>
<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>
<!-- RGBW Device -->
<thing-type id="rgbw">
<supported-bridge-type-refs>
<bridge-type-ref id="udp"/>
</supported-bridge-type-refs>
<label>Sbus RGBW Controller</label>
<description>Sbus RGBW lighting controller</description>
<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>
<label>Switch State</label>
<description>Switch state (ON/OFF)</description>
<category>Switch</category>
<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>
<channel-type id="dimmer-channel">
<item-type>Dimmer</item-type>
<label>Dimmer State</label>
<description>Dimmer state (0-100%)</description>
<category>DimmableLight</category>
<config-description>
<parameter name="channelNumber" type="integer" required="true">
<label>Channel Number</label>
<description>The physical channel number on the Sbus device</description>
</parameter>
<parameter name="timer" type="integer">
<label>Timer</label>
<description>Timer in seconds to automatically turn off the switch (0 = disabled)</description>
<default>0</default>
<advanced>true</advanced>
<unitLabel>s</unitLabel>
</parameter>
</config-description>
</channel-type>
<channel-type id="paired-channel">
<item-type>Contact</item-type>
<label>Paired Channel State</label>
<description>Paired channel state (OPEN/CLOSED) - controls two opposite channels</description>
<category>Contact</category>
<config-description>
<parameter name="channelNumber" type="integer" required="true">
<label>Channel Number</label>
<description>The physical channel number on the Sbus device</description>
</parameter>
<parameter name="pairedChannelNumber" type="integer" required="true">
<label>Paired Channel Number</label>
<description>The physical channel number of the paired channel (will be set to opposite state)</description>
</parameter>
</config-description>
</channel-type>
<channel-type id="temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Temperature reading from the device</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
<config-description>
<parameter name="channelNumber" type="integer" required="true">
<label>Channel Number</label>
<description>The physical channel number on the Sbus device</description>
</parameter>
<parameter name="unit" type="text">
<label>Temperature Unit</label>
<description>The unit to use for temperature readings (°C or °F)</description>
<options>
<option value="CELSIUS">Celsius</option>
<option value="FAHRENHEIT">Fahrenheit</option>
</options>
<default>CELSIUS</default>
</parameter>
</config-description>
</channel-type>
<channel-type id="color-channel">
<item-type>Color</item-type>
<label>Color</label>
<description>Color control</description>
<category>ColorLight</category>
<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>

View File

@ -364,6 +364,7 @@
<module>org.openhab.binding.salus</module>
<module>org.openhab.binding.samsungtv</module>
<module>org.openhab.binding.satel</module>
<module>org.openhab.binding.sbus</module>
<module>org.openhab.binding.semsportal</module>
<module>org.openhab.binding.senechome</module>
<module>org.openhab.binding.seneye</module>

View File

@ -48,6 +48,7 @@
<include name="*/src/main/feature/feature.xml"/>
<exclude name="**/org.openhab.binding.bluetooth*/**/feature.xml"/>
<exclude name="**/org.openhab.binding.modbus*/**/feature.xml"/>
<exclude name="**/org.openhab.binding.sbus*/**/feature.xml"/>
<exclude name="**/org.openhab.binding.mqtt*/**/feature.xml"/>
</fileset>
<filterchain>
@ -73,6 +74,7 @@
<features>
<feature>openhab-binding-bluetooth</feature>
<feature>openhab-binding-modbus</feature>
<feature>openhab-binding-sbus</feature>
<feature>openhab-binding-mqtt</feature>
</features>
</configuration>

View File

@ -48,5 +48,4 @@
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.sungrow/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.sunspec/${project.version}</bundle>
</feature>
</features>