mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[sbus] first rewritten version
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
2c07129ee9
commit
1b3524272e
@ -1,189 +1,98 @@
|
||||
# For Developers
|
||||
# SBUS Binding Development
|
||||
|
||||
## Debugging an addon
|
||||
This document provides information for developers who want to contribute to the OpenHAB SBUS binding.
|
||||
|
||||
Please follow IDE setup guide at <https://www.openhab.org/docs/developer/ide/eclipse.html>.
|
||||
## Development Setup
|
||||
|
||||
When configuring dependencies in `openhab-distro/launch/app/pom.xml`, add all dependencies, including the transitive dependencies:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.sbus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.sbus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.wimpi</groupId>
|
||||
<artifactId>jamod</artifactId>
|
||||
<version>1.2.4.OH</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
1. Clone the OpenHAB addons repository:
|
||||
```bash
|
||||
git clone https://github.com/openhab/openhab-addons.git
|
||||
cd openhab-addons
|
||||
```
|
||||
|
||||
## Testing Serial Implementation
|
||||
|
||||
You can use test serial slaves without any hardware on Linux using these steps:
|
||||
|
||||
1. Set-up virtual null modem emulator using [tty0tty](https://github.com/freemed/tty0tty)
|
||||
1. Download [diagslave](https://www.modbusdriver.com/diagslave.html) and start modbus serial slave up using this command:
|
||||
|
||||
```shell
|
||||
./diagslave -m rtu -a 1 -b 38400 -d 8 -s 1 -p none -4 10 /dev/pts/7
|
||||
2. Build the binding:
|
||||
```bash
|
||||
cd bundles/org.openhab.binding.sbus
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
1. Configure openHAB's modbus slave to connect to `/dev/pts/8`.
|
||||
## Project Structure
|
||||
|
||||
1. Modify `start.sh` or `start_debug.sh` to include the unconventional port name by adding the following argument to `java`:
|
||||
|
||||
```text
|
||||
-Dgnu.io.rxtx.SerialPorts=/dev/pts/8
|
||||
```
|
||||
org.openhab.binding.sbus/
|
||||
├── src/main/java/org/openhab/binding/sbus/
|
||||
│ ├── handler/ # Thing handlers
|
||||
│ │ ├── SbusRgbwHandler.java
|
||||
│ │ ├── SbusSwitchHandler.java
|
||||
│ │ └── SbusTemperatureHandler.java
|
||||
│ └── internal/ # Internal implementation
|
||||
│ └── SbusBridgeHandler.java
|
||||
└── src/main/resources/
|
||||
└── OH-INF/ # OpenHAB configuration files
|
||||
├── binding/ # Binding definitions
|
||||
├── thing/ # Thing type definitions
|
||||
└── i18n/ # Internationalization
|
||||
```
|
||||
|
||||
Naturally this is not the same thing as the real thing but helps to identify simple issues.
|
||||
## Key Components
|
||||
|
||||
## Testing UDP Implementation
|
||||
* `SbusBridgeHandler`: Manages the UDP connection to SBUS devices
|
||||
* `SbusRgbwHandler`: Handles RGBW light control
|
||||
* `SbusSwitchHandler`: Handles switch control
|
||||
* `SbusTemperatureHandler`: Handles temperature sensor readings
|
||||
|
||||
1. Download [diagslave](https://www.modbusdriver.com/diagslave.html) and start modbus udp server (slave) using this command:
|
||||
## Testing
|
||||
|
||||
```shell
|
||||
./diagslave -m udp -a 1 -p 6000
|
||||
```
|
||||
1. Unit Tests
|
||||
* Run unit tests with: `mvn test`
|
||||
* Add new tests in `src/test/java/`
|
||||
|
||||
1. Configure openHAB's modbus slave to connect to `127.0.0.1:6000`.
|
||||
2. Integration Testing
|
||||
* Test with real SBUS devices
|
||||
* Verify all supported channels work correctly
|
||||
* Test error handling and recovery
|
||||
|
||||
## Writing Data
|
||||
## Debugging
|
||||
|
||||
See this [community post](https://community.openhab.org/t/something-is-rounding-my-float-values-in-sitemap/13704/32?u=ssalonen) explaining how `pollmb` and `diagslave` can be used to debug modbus communication.
|
||||
|
||||
You can also use `modpoll` to write data:
|
||||
|
||||
```shell
|
||||
# write value=5 to holding register 40001 (index=0 in the binding)
|
||||
./modpoll -m udp -a 1 -r 1 -t4 -p 6000 127.0.0.1 5
|
||||
# set coil 00001 (index=0 in the binding) to TRUE
|
||||
./modpoll -m udp -a 1 -r 1 -t0 -p 6000 127.0.0.1 1
|
||||
# write float32
|
||||
./modpoll -m udp -a 1 -r 1 -t4:float -p 6000 127.0.0.1 3.14
|
||||
1. Enable debug logging in OpenHAB:
|
||||
```
|
||||
log:set DEBUG org.openhab.binding.sbus
|
||||
```
|
||||
|
||||
## Extending Modbus Binding
|
||||
|
||||
This Modbus binding can be extended by other OSGi bundles to add more specific support for Modbus enabled devices.
|
||||
To do so to you have to create a new OSGi bundle which has the same binding id as this binding.
|
||||
The best way is to use the `ModbusBindingConstants.BINDING_ID` constant.
|
||||
|
||||
### Thing Handler
|
||||
|
||||
You will have to create one or more handler classes for the devices you want to support.
|
||||
For the modbus connection setup and handling you can use the Modbus UDP Slave or Modbus Serial Slave handlers.
|
||||
Your handler should use these handlers as bridges and you can set up your regular or one shot modbus requests to read from the slave.
|
||||
This is done by extending your `ThingHandler` by `BaseModbusThingHandler`.
|
||||
You can use the inherited methods `submitOneTimePoll()` and `registerRegularPoll()` to poll values and `submitOneTimeWrite()` to send values to a slave.
|
||||
The `BaseModbusThingHandler` takes care that every regular poll task is cancelled, when the Thing is disposed.
|
||||
Despite that, you can cancel the task manually by storing the return value of `registerRegularPoll()` and use it as an argument to `unregisterRegularPoll()`.
|
||||
|
||||
Please keep in mind that these reads are asynchronous and they will call your callback once the read is done.
|
||||
|
||||
Once you have your data read from the modbus device you can parse and transform them then update your channels to publish these data to the openHAB system.
|
||||
|
||||
See the following example:
|
||||
|
||||
```java
|
||||
@NonNullByDefault
|
||||
public class MyHandler extends BaseModbusThingHandler {
|
||||
public MyHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
ModbusReadRequestBlueprint blueprint = new ModbusReadRequestBlueprint(getSlaveId(),
|
||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, 0, 1, 2);
|
||||
|
||||
submitOneTimePoll(blueprint, this::readSuccessful, this::readError);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modbusInitialize() {
|
||||
// do other Thing initialization
|
||||
|
||||
ModbusReadRequestBlueprint blueprint = new ModbusReadRequestBlueprint(getSlaveId(),
|
||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, 0, 1, 2);
|
||||
|
||||
registerRegularPoll(blueprint, 1000, 0, this::readSuccessful, this::readError);
|
||||
}
|
||||
|
||||
private void readSuccessful(AsyncModbusReadResult result) {
|
||||
result.getRegisters().ifPresent(registers -> {
|
||||
Optional<DecimalType> value = ModbusBitUtilities.extractStateFromRegisters(registers, 0, ValueType.INT16);
|
||||
// process value
|
||||
});
|
||||
}
|
||||
|
||||
private void readError(AsyncModbusFailure<ModbusReadRequestBlueprint> error) {
|
||||
// set the Thing offline
|
||||
}
|
||||
}
|
||||
2. Monitor SBUS communication:
|
||||
```
|
||||
openhab> sbus:monitor start
|
||||
```
|
||||
|
||||
### Discovery
|
||||
## Contributing
|
||||
|
||||
If you write a device specific handler then adding discovery for this device is very welcome.
|
||||
You will have to write a discovery participant class which implements the `ModbusDiscoveryParticipant` interface and registers itself as a component. Example:
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
* Follow OpenHAB coding guidelines
|
||||
* Add appropriate unit tests
|
||||
* Update documentation
|
||||
4. Submit a pull request
|
||||
|
||||
```java
|
||||
### Code Style
|
||||
|
||||
@Component
|
||||
@NonNullByDefault
|
||||
public class SunspecDiscoveryParticipant implements ModbusDiscoveryParticipant {
|
||||
...
|
||||
}
|
||||
* Follow OpenHAB's code style guidelines
|
||||
* Use the provided code formatter
|
||||
* Run `mvn spotless:apply` before committing
|
||||
|
||||
### Documentation
|
||||
|
||||
When adding new features:
|
||||
1. Update README.md with user-facing changes
|
||||
2. Update thing-types.xml for new channels/configurations
|
||||
3. Add appropriate JavaDoc comments
|
||||
4. Update this DEVELOPERS.md if needed
|
||||
|
||||
## Building from Source
|
||||
|
||||
```bash
|
||||
cd openhab-addons/bundles/org.openhab.binding.sbus
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
There are two methods you have to implement:
|
||||
|
||||
- `getSupportedThingTypeUIDs` should return a list of the thing type UIDs that are supported by this discovery participant. This is fairly straightforward.
|
||||
|
||||
- `startDiscovery` method will be called when a discovery process has began. This method receives two parameters:
|
||||
|
||||
- `ModbusEndpointThingHandler` is the endpoint's handler that should be tested if it is known by your bundle. You can start your read requests against this handler.
|
||||
|
||||
- `ModbusDiscoveryListener` this listener instance should be used to report any known devices found and to notify the main discovery process when your binding has finished the discovery.
|
||||
|
||||
Please try to avoid write requests to the endpoint because it could be some unknown device that write requests could misconfigure.
|
||||
|
||||
When a known device is found a `DiscoveryResult` object has to be created then the `thingDiscovered` method has to be called.
|
||||
The `DiscoveryResult` supports properties, and you should use this to store any data that will be useful when the actual thing will be created.
|
||||
For example you could store the start Modbus address of the device or vendor/model informations.
|
||||
|
||||
When the discovery process is finished either by detecting a device or by realizing it is not supported you should call the `discoveryFinished` method.
|
||||
This will tear down any resources allocated for the discovery process.
|
||||
|
||||
### Discovery Architecture
|
||||
|
||||
The following diagram shows the concept how discovery is implemented in this binding. (Note that some intermediate classes and interfaces are not shown for clarity.)
|
||||
|
||||
![Discovery architecture](doc/images/ModbusExtensibleDiscovery.png)
|
||||
|
||||
As stated above the discovery process can be extended by OSGi bundles.
|
||||
For this they have to define their own `ModbusDisvoceryParticipant` that gets registered at the `ModbusDiscoveryService`.
|
||||
This object also keeps track of any of the Modbus handlers.
|
||||
Handler level discovery logic is implemented in the `ModbusEndpointDiscoveryService` which gets instantiated for each Modbus `BridgeHandler`.
|
||||
|
||||
The communication flow is detailed in the diagram below:
|
||||
|
||||
![Discovery process](doc/images/DiscoveryProcess.png)
|
||||
|
||||
As can be seen the process is initiated by the `ModbusDiscoveryService` which calls each of the `ModbusEndpointDiscoveryService` instances to start the discovery on the available participants.
|
||||
Then a reference to the `ThingHandler` is passed to each of the participants who can use this to do the actual discovery.
|
||||
|
||||
Any things discovered are reported back in this chain and ultimately sent to openHAB core.
|
||||
The built JAR will be in `target/org.openhab.binding.sbus-[version].jar`
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 41 KiB |
@ -7,11 +7,69 @@
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.sbus</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: S-Bus Binding</name>
|
||||
<name>openHAB Add-ons :: Bundles :: SBUS Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ro.ciprianpascu</groupId>
|
||||
<artifactId>j2sbus</artifactId>
|
||||
<version>1.5.3-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>embed-dependencies</id>
|
||||
<goals>
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeArtifactIds>j2sbus</includeArtifactIds>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>bnd-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<bnd><![CDATA[
|
||||
Bundle-SymbolicName: ${project.artifactId}
|
||||
Bundle-Version: ${project.version}
|
||||
Import-Package: \
|
||||
!ro.ciprianpascu.*, \
|
||||
org.openhab.core.*, \
|
||||
org.eclipse.jdt.annotation;resolution:=optional, \
|
||||
*
|
||||
Private-Package: \
|
||||
ro.ciprianpascu.sbus.*
|
||||
-exportcontents: org.openhab.binding.sbus.*
|
||||
-dsannotations: *
|
||||
-dsannotations-options: norequirements
|
||||
-noimportjava: true
|
||||
]]></bnd>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>bnd-process</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -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>
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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";
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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;
|
||||
|
||||
/**
|
||||
* The {@link ModbusBindingConstants} class defines some constants
|
||||
* public that might be used from other bundles as well.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
* @author Nagy Attila Gabor - Split the original ModbusBindingConstants in two
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "sbus";
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.discovery;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
|
||||
/**
|
||||
* Listener for discovery results
|
||||
*
|
||||
* Each discovered thing should be supplied to the thingDiscovered
|
||||
* method.
|
||||
*
|
||||
* When the discovery process has been finished then the discoveryFinished
|
||||
* method should be called.
|
||||
*
|
||||
* @author Nagy Attila Gabor - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ModbusDiscoveryListener {
|
||||
|
||||
/**
|
||||
* Discovery participant should call this method when a new
|
||||
* thing has been discovered
|
||||
*/
|
||||
void thingDiscovered(DiscoveryResult result);
|
||||
|
||||
/**
|
||||
* This method should be called once the discovery has been finished
|
||||
* or aborted by any error.
|
||||
* It is important to call this even when there were no things discovered.
|
||||
*/
|
||||
void discoveryFinished();
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.discovery;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sbus.handler.ModbusEndpointThingHandler;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* Interface for participants of Modbus discovery
|
||||
* This is an asynchronous process where a participant can discover
|
||||
* multiple things on a Modbus endpoint.
|
||||
*
|
||||
* Results should be submitted using the ModbusDiscvoeryListener
|
||||
* supplied at the begin of the scan.
|
||||
*
|
||||
* @author Nagy Attila Gabor - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ModbusDiscoveryParticipant {
|
||||
|
||||
/**
|
||||
* Defines the list of thing types that this participant can identify
|
||||
*
|
||||
* @return a set of thing type UIDs for which results can be created
|
||||
*/
|
||||
Set<ThingTypeUID> getSupportedThingTypeUIDs();
|
||||
|
||||
/**
|
||||
* Start an asynchronous discovery process of a Modbus endpoint
|
||||
*
|
||||
* @param handler the endpoint that should be discovered
|
||||
*/
|
||||
void startDiscovery(ModbusEndpointThingHandler handler, ModbusDiscoveryListener listener);
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.discovery.internal;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sbus.discovery.ModbusDiscoveryParticipant;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Discovery service for Modbus bridges.
|
||||
*
|
||||
* This service acts as a rendezvous point between the different Modbus endpoints and any
|
||||
* bundles that implement auto discovery through an endpoint.
|
||||
*
|
||||
* New bridges (UDP or Serial Modbus endpoint) should register with this service. This is
|
||||
* handled automatically by the ModbusEndpointDiscoveryService.
|
||||
* Also any bundles that perform auto discovery should register a ModbusDiscoveryParticipant.
|
||||
* This ModbusDiscoveryParticipants will be called by the service when
|
||||
* a discovery scan is requested.
|
||||
*
|
||||
* @author Nagy Attila Gabor - initial contribution
|
||||
*
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.modbus")
|
||||
@NonNullByDefault
|
||||
public class ModbusDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ModbusDiscoveryService.class);
|
||||
|
||||
// Set of services that support Modbus discovery
|
||||
private final Set<ModbusThingHandlerDiscoveryService> services = new CopyOnWriteArraySet<>();
|
||||
|
||||
// Set of the registered participants
|
||||
private final Set<ModbusDiscoveryParticipant> participants = new CopyOnWriteArraySet<>();
|
||||
|
||||
// Set of the supported thing types. This is a union of all the thing types
|
||||
// supported by the registered discovery services.
|
||||
private final Set<ThingTypeUID> supportedThingTypes = new CopyOnWriteArraySet<>();
|
||||
|
||||
private static final int SEARCH_TIME_SECS = 5;
|
||||
|
||||
/**
|
||||
* Constructor for the discovery service.
|
||||
* Set up default parameters
|
||||
*/
|
||||
public ModbusDiscoveryService() {
|
||||
// No supported thing types by default
|
||||
// Search time is for the visual reference
|
||||
// Background discovery disabled by default
|
||||
super(null, SEARCH_TIME_SECS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* ThingHandlerService
|
||||
* Begin a discovery scan over each endpoint
|
||||
*/
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.trace("ModbusDiscoveryService starting scan");
|
||||
|
||||
if (participants.isEmpty()) {
|
||||
// There's no point on continuing if there are no participants at the moment
|
||||
stopScan();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean scanStarted = false;
|
||||
for (ModbusThingHandlerDiscoveryService service : services) {
|
||||
scanStarted |= service.startScan(this);
|
||||
}
|
||||
if (!scanStarted) {
|
||||
stopScan();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to notify us when a handler has finished it's discovery process
|
||||
*/
|
||||
protected void scanFinished() {
|
||||
for (ModbusThingHandlerDiscoveryService service : services) {
|
||||
if (service.scanInProgress()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.trace("All endpoints finished scanning, stopping scan");
|
||||
stopScan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Real discovery is done by the ModbusDiscoveryParticipants
|
||||
* They are executed in series for each Modbus endpoint by ModbusDiscoveryProcess
|
||||
* instances. They call back this method when a thing has been discovered
|
||||
*/
|
||||
@Override
|
||||
protected void thingDiscovered(DiscoveryResult discoveryResult) {
|
||||
super.thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of {@code Thing} types which are supported by the {@link DiscoveryService}.
|
||||
*
|
||||
* @return the list of Thing types which are supported by the discovery service
|
||||
* (not null, could be empty)
|
||||
*/
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return this.supportedThingTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This reference is used to register any new Modbus bridge with the discovery service
|
||||
* Running bridges have a ModbusThingHandlerDiscoveryService connected
|
||||
* which will be responsible for the discovery
|
||||
*
|
||||
* @param service discovery service
|
||||
*/
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addModbusEndpoint(ModbusThingHandlerDiscoveryService service) {
|
||||
logger.trace("Received new handler: {}", service);
|
||||
services.add(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an already registered thing handler discovery component
|
||||
*
|
||||
* @param service discovery service
|
||||
*/
|
||||
protected void removeModbusEndpoint(ModbusThingHandlerDiscoveryService service) {
|
||||
logger.trace("Removed handler: {}", service);
|
||||
services.remove(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a discovery participant. This participant will be called
|
||||
* with any new Modbus bridges that allow discovery
|
||||
*
|
||||
* @param participant
|
||||
*/
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addModbusDiscoveryParticipant(ModbusDiscoveryParticipant participant) {
|
||||
logger.trace("Received new participant: {}", participant);
|
||||
participants.add(participant);
|
||||
supportedThingTypes.addAll(participant.getSupportedThingTypeUIDs());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an already registered discovery participant
|
||||
*
|
||||
* @param participant
|
||||
*/
|
||||
protected void removeModbusDiscoveryParticipant(ModbusDiscoveryParticipant participant) {
|
||||
logger.trace("Removing participant: {}", participant);
|
||||
supportedThingTypes.removeAll(participant.getSupportedThingTypeUIDs());
|
||||
participants.remove(participant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of participants
|
||||
*
|
||||
* @return a set of the participants. Note: this is a copy of the original set
|
||||
*/
|
||||
public Set<ModbusDiscoveryParticipant> getDiscoveryParticipants() {
|
||||
return new CopyOnWriteArraySet<>(participants);
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.discovery.internal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sbus.discovery.ModbusDiscoveryListener;
|
||||
import org.openhab.binding.sbus.discovery.ModbusDiscoveryParticipant;
|
||||
import org.openhab.binding.sbus.handler.ModbusEndpointThingHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A new instance of this class is created for each Modbus endpoint handler
|
||||
* that supports discovery.
|
||||
* This service gets called each time a discovery is requested, and it is
|
||||
* responsible to execute the discovery on the connected thing handler.
|
||||
* Actual discovery is done by the registered ModbusDiscoveryparticipants
|
||||
*
|
||||
* @author Nagy Attila Gabor - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusEndpointDiscoveryService implements ModbusThingHandlerDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ModbusEndpointDiscoveryService.class);
|
||||
|
||||
// This is the handler we will do the discovery on
|
||||
private @Nullable ModbusEndpointThingHandler handler;
|
||||
|
||||
// List of the registered participants
|
||||
// this only contains data when there is scan in progress
|
||||
private final List<ModbusDiscoveryParticipant> participants = new CopyOnWriteArrayList<>();
|
||||
|
||||
// This is set true when we're waiting for a participant to finish discovery
|
||||
private boolean waitingForParticipant = false;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
|
||||
this.handler = thingHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return (ThingHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startScan(ModbusDiscoveryService service) {
|
||||
ModbusEndpointThingHandler handler = this.handler;
|
||||
if (handler == null || !handler.isDiscoveryEnabled()) {
|
||||
return false;
|
||||
}
|
||||
logger.trace("Starting discovery on endpoint {}", handler.getUID().getAsString());
|
||||
|
||||
participants.addAll(service.getDiscoveryParticipants());
|
||||
|
||||
startNextParticipant(handler, service);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scanInProgress() {
|
||||
return !participants.isEmpty() || waitingForParticipant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the next participant's discovery process
|
||||
*
|
||||
* @param service reference to the ModbusDiscoveryService that will collect all the
|
||||
* discovered items
|
||||
*/
|
||||
private void startNextParticipant(final ModbusEndpointThingHandler handler, final ModbusDiscoveryService service) {
|
||||
if (participants.isEmpty()) {
|
||||
logger.trace("All participants has finished");
|
||||
service.scanFinished();
|
||||
return; // We're finished, this will exit the process
|
||||
}
|
||||
|
||||
ModbusDiscoveryParticipant participant = participants.remove(0);
|
||||
|
||||
waitingForParticipant = true;
|
||||
|
||||
// Call startDiscovery on the next participant. The ModbusDiscoveryListener
|
||||
// callback will be notified each time a thing is discovered, and also when
|
||||
// the discovery is finished by this participant
|
||||
participant.startDiscovery(handler, new ModbusDiscoveryListener() {
|
||||
|
||||
/**
|
||||
* Participant has found a thing
|
||||
*/
|
||||
@Override
|
||||
public void thingDiscovered(DiscoveryResult result) {
|
||||
service.thingDiscovered(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Participant finished discovery.
|
||||
* We can continue to the next participant
|
||||
*/
|
||||
@Override
|
||||
public void discoveryFinished() {
|
||||
waitingForParticipant = false;
|
||||
startNextParticipant(handler, service);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.discovery.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
|
||||
/**
|
||||
* Implementation of this interface is responsible for discovery over
|
||||
* a Modbus endpoint. Each time a supporting endpoint handler is created
|
||||
* an instance of this service will be created as well and attached to the
|
||||
* thing handler.
|
||||
*
|
||||
* @author Nagy Attila Gabor - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ModbusThingHandlerDiscoveryService extends ThingHandlerService {
|
||||
|
||||
/**
|
||||
* Implementation should start a discovery when this method gets called
|
||||
*
|
||||
* @param service the discovery service that should be called when the discovery is finished
|
||||
* @return returns true if discovery is enabled, false otherwise
|
||||
*/
|
||||
boolean startScan(ModbusDiscoveryService service);
|
||||
|
||||
/**
|
||||
* This method should return true, if an async scan is in progress
|
||||
*
|
||||
* @return true if a scan is in progress false otherwise
|
||||
*/
|
||||
boolean scanInProgress();
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.internal.SbusBridgeHandler;
|
||||
import org.openhab.binding.sbus.internal.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;
|
||||
|
||||
import ro.ciprianpascu.sbus.facade.SbusAdapter;
|
||||
|
||||
/**
|
||||
* 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 SbusAdapter 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.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge is not online");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sbusAdapter = bridgeHandler.getSbusConnection();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
startPolling();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error initializing SBUS connection", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
}
|
||||
final SbusAdapter adapter = sbusAdapter;
|
||||
if (adapter != null) {
|
||||
adapter.close();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.transport.sbus.ModbusCommunicationInterface;
|
||||
import org.openhab.core.io.transport.sbus.ModbusFailureCallback;
|
||||
import org.openhab.core.io.transport.sbus.ModbusReadCallback;
|
||||
import org.openhab.core.io.transport.sbus.ModbusReadRequestBlueprint;
|
||||
import org.openhab.core.io.transport.sbus.ModbusWriteCallback;
|
||||
import org.openhab.core.io.transport.sbus.ModbusWriteRequestBlueprint;
|
||||
import org.openhab.core.io.transport.sbus.PollTask;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.BridgeHandler;
|
||||
|
||||
/**
|
||||
* This is a convenience class to interact with the Thing's {@link ModbusCommunicationInterface}.
|
||||
*
|
||||
* @author Fabian Wolter - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class BaseModbusThingHandler extends BaseThingHandler {
|
||||
private List<PollTask> periodicPollers = Collections.synchronizedList(new ArrayList<>());
|
||||
private List<Future<?>> oneTimePollers = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public BaseModbusThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the Thing is being initialized, but only if the Modbus Bridge is configured correctly.
|
||||
* The code that normally goes into `BaseThingHandler.initialize()` like configuration reading and validation goes
|
||||
* here.
|
||||
*/
|
||||
public abstract void modbusInitialize();
|
||||
|
||||
@Override
|
||||
public final void initialize() {
|
||||
try {
|
||||
// check if the Bridge is configured correctly (fail-fast)
|
||||
getModbus();
|
||||
getSlaveId();
|
||||
} catch (IllegalStateException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, e.getMessage());
|
||||
}
|
||||
|
||||
modbusInitialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Slave ID, also called as unit id, represented by the thing
|
||||
*
|
||||
* @return slave id represented by this thing handler
|
||||
*/
|
||||
public int getSlaveId() {
|
||||
try {
|
||||
return getBridgeHandler().getSlaveId();
|
||||
} catch (EndpointNotInitializedException e) {
|
||||
throw new IllegalStateException("Bridge not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if auto discovery is enabled for this endpoint
|
||||
*
|
||||
* @return boolean true if the discovery is enabled
|
||||
*/
|
||||
public boolean isDiscoveryEnabled() {
|
||||
return getBridgeHandler().isDiscoveryEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register regularly polled task. The method returns immediately, and the execution of the poll task will happen in
|
||||
* the background.
|
||||
*
|
||||
* One can register only one regular poll task for triplet of (endpoint, request, callback).
|
||||
*
|
||||
* @param request request to send
|
||||
* @param pollPeriodMillis poll interval, in milliseconds
|
||||
* @param initialDelayMillis initial delay before starting polling, in milliseconds
|
||||
* @param resultCallback callback to call with data
|
||||
* @param failureCallback callback to call in case of failure
|
||||
* @return poll task representing the regular poll
|
||||
* @throws IllegalStateException when this communication has been closed already
|
||||
*/
|
||||
public PollTask registerRegularPoll(ModbusReadRequestBlueprint request, long pollPeriodMillis,
|
||||
long initialDelayMillis, ModbusReadCallback resultCallback,
|
||||
ModbusFailureCallback<ModbusReadRequestBlueprint> failureCallback) {
|
||||
PollTask task = getModbus().registerRegularPoll(request, pollPeriodMillis, initialDelayMillis, resultCallback,
|
||||
failureCallback);
|
||||
periodicPollers.add(task);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister regularly polled task
|
||||
*
|
||||
* If this communication interface is closed already, the method returns immediately with false return value
|
||||
*
|
||||
* @param task poll task to unregister
|
||||
* @return whether poll task was unregistered. Poll task is not unregistered in case of unexpected errors or
|
||||
* in the case where the poll task is not registered in the first place
|
||||
* @throws IllegalStateException when this communication has been closed already
|
||||
*/
|
||||
public boolean unregisterRegularPoll(PollTask task) {
|
||||
periodicPollers.remove(task);
|
||||
return getModbus().unregisterRegularPoll(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit one-time poll task. The method returns immediately, and the execution of the poll task will happen in
|
||||
* background.
|
||||
*
|
||||
* @param request request to send
|
||||
* @param resultCallback callback to call with data
|
||||
* @param failureCallback callback to call in case of failure
|
||||
* @return future representing the polled task
|
||||
* @throws IllegalStateException when this communication has been closed already
|
||||
*/
|
||||
public Future<?> submitOneTimePoll(ModbusReadRequestBlueprint request, ModbusReadCallback resultCallback,
|
||||
ModbusFailureCallback<ModbusReadRequestBlueprint> failureCallback) {
|
||||
Future<?> future = getModbus().submitOneTimePoll(request, resultCallback, failureCallback);
|
||||
oneTimePollers.add(future);
|
||||
oneTimePollers.removeIf(Future::isDone);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit one-time write task. The method returns immediately, and the execution of the task will happen in
|
||||
* background.
|
||||
*
|
||||
* @param request request to send
|
||||
* @param resultCallback callback to call with response
|
||||
* @param failureCallback callback to call in case of failure
|
||||
* @return future representing the task
|
||||
* @throws IllegalStateException when this communication has been closed already
|
||||
*/
|
||||
public Future<?> submitOneTimeWrite(ModbusWriteRequestBlueprint request, ModbusWriteCallback resultCallback,
|
||||
ModbusFailureCallback<ModbusWriteRequestBlueprint> failureCallback) {
|
||||
Future<?> future = getModbus().submitOneTimeWrite(request, resultCallback, failureCallback);
|
||||
oneTimePollers.add(future);
|
||||
oneTimePollers.removeIf(Future::isDone);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private ModbusCommunicationInterface getModbus() {
|
||||
ModbusCommunicationInterface communicationInterface = getBridgeHandler().getCommunicationInterface();
|
||||
|
||||
if (communicationInterface == null) {
|
||||
throw new IllegalStateException("Bridge not initialized");
|
||||
} else {
|
||||
return communicationInterface;
|
||||
}
|
||||
}
|
||||
|
||||
private ModbusEndpointThingHandler getBridgeHandler() {
|
||||
try {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
throw new IllegalStateException("No Bridge configured");
|
||||
}
|
||||
|
||||
BridgeHandler handler = bridge.getHandler();
|
||||
|
||||
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
|
||||
return thingHandler;
|
||||
} else {
|
||||
throw new IllegalStateException("Not a Modbus Bridge: " + handler);
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
oneTimePollers.forEach(p -> p.cancel(true));
|
||||
oneTimePollers.clear();
|
||||
|
||||
ModbusCommunicationInterface modbus = getModbus();
|
||||
periodicPollers.forEach(p -> modbus.unregisterRegularPoll(p));
|
||||
periodicPollers.clear();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.core.common.registry.Identifiable;
|
||||
import org.openhab.core.io.transport.sbus.ModbusCommunicationInterface;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* Base interface for thing handlers of endpoint things
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ModbusEndpointThingHandler extends Identifiable<ThingUID> {
|
||||
|
||||
/**
|
||||
* Gets the {@link ModbusCommunicationInterface} represented by the thing
|
||||
*
|
||||
* Note that this can be <code>null</code> in case of incomplete initialization
|
||||
*
|
||||
* @return communication interface represented by this thing handler
|
||||
*/
|
||||
@Nullable
|
||||
ModbusCommunicationInterface getCommunicationInterface();
|
||||
|
||||
/**
|
||||
* Get Slave ID, also called as unit id, represented by the thing
|
||||
*
|
||||
* @return slave id represented by this thing handler
|
||||
* @throws EndpointNotInitializedException in case the initialization is not complete
|
||||
*/
|
||||
int getSlaveId() throws EndpointNotInitializedException;
|
||||
|
||||
/**
|
||||
* Get Slave ID, also called as unit id, represented by the thing
|
||||
*
|
||||
* @return slave id represented by this thing handler
|
||||
* @throws EndpointNotInitializedException in case the initialization is not complete
|
||||
*/
|
||||
int getSubnetId() throws EndpointNotInitializedException;
|
||||
|
||||
/**
|
||||
* Return true if auto discovery is enabled for this endpoint
|
||||
*
|
||||
* @return boolean true if the discovery is enabled
|
||||
*/
|
||||
boolean isDiscoveryEnabled();
|
||||
}
|
@ -1,467 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sbus.internal.AtomicStampedValue;
|
||||
import org.openhab.binding.sbus.internal.ModbusBindingConstantsInternal;
|
||||
import org.openhab.binding.sbus.internal.config.ModbusPollerConfiguration;
|
||||
import org.openhab.binding.sbus.internal.handler.ModbusDataThingHandler;
|
||||
import org.openhab.core.io.transport.sbus.AsyncModbusFailure;
|
||||
import org.openhab.core.io.transport.sbus.AsyncModbusReadResult;
|
||||
import org.openhab.core.io.transport.sbus.ModbusCommunicationInterface;
|
||||
import org.openhab.core.io.transport.sbus.ModbusConstants;
|
||||
import org.openhab.core.io.transport.sbus.ModbusFailureCallback;
|
||||
import org.openhab.core.io.transport.sbus.ModbusReadCallback;
|
||||
import org.openhab.core.io.transport.sbus.ModbusReadFunctionCode;
|
||||
import org.openhab.core.io.transport.sbus.ModbusReadRequestBlueprint;
|
||||
import org.openhab.core.io.transport.sbus.ModbusRegisterArray;
|
||||
import org.openhab.core.io.transport.sbus.PollTask;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ModbusPollerThingHandler} is responsible for polling Modbus slaves. Errors and data is delegated to
|
||||
* child thing handlers inheriting from {@link ModbusReadCallback} -- in practice: {@link ModbusDataThingHandler}.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusPollerThingHandler extends BaseBridgeHandler {
|
||||
|
||||
/**
|
||||
* {@link ModbusReadCallback} that delegates all tasks forward.
|
||||
*
|
||||
* All instances of {@linkplain ReadCallbackDelegator} are considered equal, if they are connected to the same
|
||||
* bridge. This makes sense, as the callback delegates
|
||||
* to all child things of this bridge.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
private class ReadCallbackDelegator
|
||||
implements ModbusReadCallback, ModbusFailureCallback<ModbusReadRequestBlueprint> {
|
||||
|
||||
private volatile @Nullable AtomicStampedValue<PollResult> lastResult;
|
||||
|
||||
public synchronized void handleResult(PollResult result) {
|
||||
// Ignore all incoming data and errors if configuration is not correct
|
||||
if (hasConfigurationError() || disposed) {
|
||||
return;
|
||||
}
|
||||
if (config.getCacheMillis() >= 0) {
|
||||
AtomicStampedValue<PollResult> localLastResult = this.lastResult;
|
||||
if (localLastResult == null) {
|
||||
this.lastResult = new AtomicStampedValue<>(System.currentTimeMillis(), result);
|
||||
} else {
|
||||
localLastResult.update(System.currentTimeMillis(), result);
|
||||
this.lastResult = localLastResult;
|
||||
}
|
||||
}
|
||||
logger.debug("Thing {} received response {}", thing.getUID(), result);
|
||||
notifyChildren(result);
|
||||
if (result.failure != null) {
|
||||
Exception error = result.failure.getCause();
|
||||
assert error != null;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
String.format("Error with read: %s: %s", error.getClass().getName(), error.getMessage()));
|
||||
} else {
|
||||
resetCommunicationError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handle(AsyncModbusReadResult result) {
|
||||
// Casting to allow registers.orElse(null) below..
|
||||
Optional<@Nullable ModbusRegisterArray> registers = (Optional<@Nullable ModbusRegisterArray>) result
|
||||
.getRegisters();
|
||||
lastPolledDataCache.set(registers.orElse(null));
|
||||
handleResult(new PollResult(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handle(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
|
||||
handleResult(new PollResult(failure));
|
||||
}
|
||||
|
||||
private void resetCommunicationError() {
|
||||
ThingStatusInfo statusInfo = thing.getStatusInfo();
|
||||
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
|
||||
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update children data if data is fresh enough
|
||||
*
|
||||
* @param oldestStamp oldest data that is still passed to children
|
||||
* @return whether data was updated. Data is not updated when it's too old or there's no data at all.
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public boolean updateChildrenWithOldData(long oldestStamp) {
|
||||
return Optional.ofNullable(this.lastResult).map(result -> result.copyIfStampAfter(oldestStamp))
|
||||
.map(result -> {
|
||||
logger.debug("Thing {} reusing cached data: {}", thing.getUID(), result.getValue());
|
||||
notifyChildren(result.getValue());
|
||||
return true;
|
||||
}).orElse(false);
|
||||
}
|
||||
|
||||
private void notifyChildren(PollResult pollResult) {
|
||||
@Nullable
|
||||
AsyncModbusReadResult result = pollResult.result;
|
||||
@Nullable
|
||||
AsyncModbusFailure<ModbusReadRequestBlueprint> failure = pollResult.failure;
|
||||
childCallbacks.forEach(handler -> {
|
||||
if (result != null) {
|
||||
handler.onReadResult(result);
|
||||
} else if (failure != null) {
|
||||
handler.handleReadError(failure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest data caches
|
||||
*/
|
||||
public void resetCache() {
|
||||
lastResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable data object to cache the results of a poll request
|
||||
*/
|
||||
private class PollResult {
|
||||
|
||||
public final @Nullable AsyncModbusReadResult result;
|
||||
public final @Nullable AsyncModbusFailure<ModbusReadRequestBlueprint> failure;
|
||||
|
||||
PollResult(AsyncModbusReadResult result) {
|
||||
this.result = result;
|
||||
this.failure = null;
|
||||
}
|
||||
|
||||
PollResult(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
|
||||
this.result = null;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return failure == null ? String.format("PollResult(result=%s)", result)
|
||||
: String.format("PollResult(failure=%s)", failure);
|
||||
}
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ModbusPollerThingHandler.class);
|
||||
|
||||
private static final List<String> SORTED_READ_FUNCTION_CODES = ModbusBindingConstantsInternal.READ_FUNCTION_CODES
|
||||
.keySet().stream().sorted().collect(Collectors.toUnmodifiableList());
|
||||
|
||||
private @NonNullByDefault({}) ModbusPollerConfiguration config;
|
||||
private long cacheMillis;
|
||||
private volatile @Nullable PollTask pollTask;
|
||||
private volatile @Nullable ModbusReadRequestBlueprint request;
|
||||
private volatile boolean disposed;
|
||||
private volatile List<ModbusDataThingHandler> childCallbacks = new CopyOnWriteArrayList<>();
|
||||
private volatile AtomicReference<@Nullable ModbusRegisterArray> lastPolledDataCache = new AtomicReference<>();
|
||||
private @NonNullByDefault({}) ModbusCommunicationInterface comms;
|
||||
|
||||
private ReadCallbackDelegator callbackDelegator = new ReadCallbackDelegator();
|
||||
|
||||
private @Nullable ModbusReadFunctionCode functionCode;
|
||||
|
||||
public ModbusPollerThingHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// No channels, no commands
|
||||
}
|
||||
|
||||
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
logger.debug("Bridge is null");
|
||||
return null;
|
||||
}
|
||||
if (bridge.getStatus() != ThingStatus.ONLINE) {
|
||||
logger.debug("Bridge is not online");
|
||||
return null;
|
||||
}
|
||||
|
||||
ThingHandler handler = bridge.getHandler();
|
||||
if (handler == null) {
|
||||
logger.debug("Bridge handler is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
|
||||
return thingHandler;
|
||||
} else {
|
||||
logger.debug("Unexpected bridge handler: {}", handler);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void initialize() {
|
||||
if (this.getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
// If the bridge was online then first change it to offline.
|
||||
// this ensures that children will be notified about the change
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
this.callbackDelegator.resetCache();
|
||||
comms = null;
|
||||
request = null;
|
||||
disposed = false;
|
||||
logger.trace("Initializing {} from status {}", this.getThing().getUID(), this.getThing().getStatus());
|
||||
try {
|
||||
config = getConfigAs(ModbusPollerConfiguration.class);
|
||||
String type = config.getType();
|
||||
if (!ModbusBindingConstantsInternal.READ_FUNCTION_CODES.containsKey(type)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
String.format("No function code found for type='%s'. Was expecting one of: %s", type,
|
||||
String.join(", ", SORTED_READ_FUNCTION_CODES)));
|
||||
return;
|
||||
}
|
||||
functionCode = ModbusBindingConstantsInternal.READ_FUNCTION_CODES.get(type);
|
||||
switch (functionCode) {
|
||||
case READ_INPUT_REGISTERS:
|
||||
case READ_MULTIPLE_REGISTERS:
|
||||
if (config.getLength() > ModbusConstants.MAX_REGISTERS_READ_COUNT) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
|
||||
"Maximum of %d registers can be polled at once due to protocol limitations. Length %d is out of bounds.",
|
||||
ModbusConstants.MAX_REGISTERS_READ_COUNT, config.getLength()));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case READ_COILS:
|
||||
case READ_INPUT_DISCRETES:
|
||||
if (config.getLength() > ModbusConstants.MAX_BITS_READ_COUNT) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
|
||||
"Maximum of %d coils/discrete inputs can be polled at once due to protocol limitations. Length %d is out of bounds.",
|
||||
ModbusConstants.MAX_BITS_READ_COUNT, config.getLength()));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
cacheMillis = this.config.getCacheMillis();
|
||||
registerPollTask();
|
||||
} catch (EndpointNotInitializedException e) {
|
||||
logger.debug("Exception during initialization", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String
|
||||
.format("Exception during initialization: %s (%s)", e.getMessage(), e.getClass().getSimpleName()));
|
||||
} finally {
|
||||
logger.trace("initialize() of thing {} '{}' finished", thing.getUID(), thing.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void dispose() {
|
||||
logger.debug("dispose()");
|
||||
// Mark handler as disposed as soon as possible to halt processing of callbacks
|
||||
disposed = true;
|
||||
unregisterPollTask();
|
||||
this.callbackDelegator.resetCache();
|
||||
comms = null;
|
||||
lastPolledDataCache.set(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister poll task.
|
||||
*
|
||||
* No-op in case no poll task is registered, or if the initialization is incomplete.
|
||||
*/
|
||||
public synchronized void unregisterPollTask() {
|
||||
logger.trace("unregisterPollTask()");
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
PollTask localPollTask = this.pollTask;
|
||||
if (localPollTask != null) {
|
||||
logger.debug("Unregistering polling from ModbusManager");
|
||||
comms.unregisterRegularPoll(localPollTask);
|
||||
}
|
||||
this.pollTask = null;
|
||||
request = null;
|
||||
comms = null;
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register poll task
|
||||
*
|
||||
* @throws EndpointNotInitializedException in case the bridge initialization is not complete. This should only
|
||||
* happen in transient conditions, for example, when bridge is initializing.
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
private synchronized void registerPollTask() throws EndpointNotInitializedException {
|
||||
logger.trace("registerPollTask()");
|
||||
if (pollTask != null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
logger.debug("pollTask should be unregistered before registering a new one!");
|
||||
return;
|
||||
}
|
||||
|
||||
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
|
||||
if (slaveEndpointThingHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, String.format("Bridge '%s' is offline",
|
||||
Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>")));
|
||||
logger.debug("No bridge handler available -- aborting init for {}", this);
|
||||
return;
|
||||
}
|
||||
ModbusCommunicationInterface localComms = slaveEndpointThingHandler.getCommunicationInterface();
|
||||
if (localComms == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, String.format(
|
||||
"Bridge '%s' not completely initialized", Optional.ofNullable(getBridge()).map(b -> b.getLabel())));
|
||||
logger.debug("Bridge not initialized fully (no communication interface) -- aborting init for {}", this);
|
||||
return;
|
||||
}
|
||||
this.comms = localComms;
|
||||
ModbusReadFunctionCode localFunctionCode = functionCode;
|
||||
if (localFunctionCode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModbusReadRequestBlueprint localRequest = new ModbusReadRequestBlueprint(
|
||||
slaveEndpointThingHandler.getSubnetId(), slaveEndpointThingHandler.getSlaveId(), localFunctionCode,
|
||||
config.getStart(), config.getLength(), config.getMaxTries());
|
||||
this.request = localRequest;
|
||||
|
||||
if (config.getRefresh() <= 0L) {
|
||||
logger.debug("Not registering polling with ModbusManager since refresh disabled");
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Not polling");
|
||||
} else {
|
||||
logger.debug("Registering polling with ModbusManager");
|
||||
pollTask = localComms.registerRegularPoll(localRequest, config.getRefresh(), 0, callbackDelegator,
|
||||
callbackDelegator);
|
||||
assert pollTask != null;
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasConfigurationError() {
|
||||
ThingStatusInfo statusInfo = getThing().getStatusInfo();
|
||||
return statusInfo.getStatus() == ThingStatus.OFFLINE
|
||||
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("bridgeStatusChanged for {}. Reseting handler", this.getThing().getUID());
|
||||
this.dispose();
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||
if (childHandler instanceof ModbusDataThingHandler modbusDataThingHandler) {
|
||||
this.childCallbacks.add(modbusDataThingHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
@Override
|
||||
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
|
||||
if (childHandler instanceof ModbusDataThingHandler) {
|
||||
this.childCallbacks.remove(childHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link ModbusReadRequestBlueprint} represented by this thing.
|
||||
*
|
||||
* Note that request might be <code>null</code> in case initialization is not complete.
|
||||
*
|
||||
* @return modbus request represented by this poller
|
||||
*/
|
||||
public @Nullable ModbusReadRequestBlueprint getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get communication interface associated with this poller
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ModbusCommunicationInterface getCommunicationInterface() {
|
||||
return comms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the data
|
||||
*
|
||||
* If data or error was just recently received (i.e. cache is fresh), return the cached response.
|
||||
*/
|
||||
public void refresh() {
|
||||
ModbusReadRequestBlueprint localRequest = this.request;
|
||||
if (localRequest == null) {
|
||||
return;
|
||||
}
|
||||
ModbusRegisterArray possiblyMutatedCache = lastPolledDataCache.get();
|
||||
AtomicStampedValue<PollResult> lastPollResult = callbackDelegator.lastResult;
|
||||
if (lastPollResult != null && possiblyMutatedCache != null) {
|
||||
AsyncModbusReadResult lastSuccessfulPollResult = lastPollResult.getValue().result;
|
||||
if (lastSuccessfulPollResult != null) {
|
||||
ModbusRegisterArray lastRegisters = ((Optional<@Nullable ModbusRegisterArray>) lastSuccessfulPollResult
|
||||
.getRegisters()).orElse(null);
|
||||
if (lastRegisters != null && !possiblyMutatedCache.equals(lastRegisters)) {
|
||||
// Register has been mutated in between by a data thing that writes "individual bits"
|
||||
// Invalidate cache for a fresh poll
|
||||
callbackDelegator.resetCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long oldDataThreshold = System.currentTimeMillis() - cacheMillis;
|
||||
boolean cacheWasRecentEnoughForUpdate = cacheMillis > 0
|
||||
&& this.callbackDelegator.updateChildrenWithOldData(oldDataThreshold);
|
||||
if (cacheWasRecentEnoughForUpdate) {
|
||||
logger.debug(
|
||||
"Poller {} received refresh() and cache was recent enough (age at most {} ms). Reusing old response",
|
||||
getThing().getUID(), cacheMillis);
|
||||
} else {
|
||||
// cache expired, poll new data
|
||||
logger.debug("Poller {} received refresh() but the cache is not applicable. Polling new data",
|
||||
getThing().getUID());
|
||||
ModbusCommunicationInterface localComms = comms;
|
||||
if (localComms != null) {
|
||||
localComms.submitOneTimePoll(localRequest, callbackDelegator, callbackDelegator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AtomicReference<@Nullable ModbusRegisterArray> getLastPolledDataCache() {
|
||||
return lastPolledDataCache;
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sbus.internal.config.SbusChannelConfig;
|
||||
import org.openhab.binding.sbus.internal.config.SbusDeviceConfig;
|
||||
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;
|
||||
|
||||
import ro.ciprianpascu.sbus.facade.SbusAdapter;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@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() {
|
||||
handleReadRgbwValues();
|
||||
}
|
||||
|
||||
private void handleReadRgbwValues() {
|
||||
final SbusAdapter adapter = super.sbusAdapter;
|
||||
if (adapter == null) {
|
||||
logger.warn("SBUS adapter not initialized");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SBUS adapter not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
|
||||
int[] rgbwValues = adapter.readRgbw(config.subnetId, config.id);
|
||||
if (rgbwValues != null && rgbwValues.length >= 4) {
|
||||
// Update each channel based on its ID
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
String channelId = channel.getUID().getId();
|
||||
if (CHANNEL_RED.equals(channelId)) {
|
||||
updateState(channel.getUID(), new PercentType(rgbwValues[0]));
|
||||
} else if (CHANNEL_GREEN.equals(channelId)) {
|
||||
updateState(channel.getUID(), new PercentType(rgbwValues[1]));
|
||||
} else if (CHANNEL_BLUE.equals(channelId)) {
|
||||
updateState(channel.getUID(), new PercentType(rgbwValues[2]));
|
||||
} else if (CHANNEL_WHITE.equals(channelId)) {
|
||||
updateState(channel.getUID(), new PercentType(rgbwValues[3]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid RGBW values received from SBUS device");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error reading RGBW values", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
final SbusAdapter adapter = super.sbusAdapter;
|
||||
if (adapter == null) {
|
||||
logger.warn("SBUS adapter not initialized");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SBUS adapter not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String channelId = channelUID.getId();
|
||||
if (command instanceof PercentType) {
|
||||
int value = ((PercentType) command).intValue();
|
||||
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
|
||||
int[] currentValues = adapter.readRgbw(config.subnetId, config.id);
|
||||
if (currentValues == null || currentValues.length < 4) {
|
||||
logger.warn("Failed to read current RGBW values");
|
||||
return;
|
||||
}
|
||||
|
||||
int[] newValues = currentValues.clone();
|
||||
if (CHANNEL_RED.equals(channelId)) {
|
||||
newValues[0] = value;
|
||||
} else if (CHANNEL_GREEN.equals(channelId)) {
|
||||
newValues[1] = value;
|
||||
} else if (CHANNEL_BLUE.equals(channelId)) {
|
||||
newValues[2] = value;
|
||||
} else if (CHANNEL_WHITE.equals(channelId)) {
|
||||
newValues[3] = value;
|
||||
}
|
||||
|
||||
// Update each channel's state
|
||||
Channel channel = getThing().getChannel(channelId);
|
||||
if (channel != null) {
|
||||
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
|
||||
if (channelConfig.channelNumber > 0) {
|
||||
adapter.writeSingleChannel(config.subnetId, config.id, channelConfig.channelNumber, value > 0);
|
||||
updateState(channelUID, (PercentType) command);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error handling command", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.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;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ro.ciprianpascu.sbus.facade.SbusAdapter;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
handleReadStatusChannels();
|
||||
}
|
||||
|
||||
private void handleReadStatusChannels() {
|
||||
final SbusAdapter adapter = super.sbusAdapter;
|
||||
if (adapter == null) {
|
||||
logger.warn("SBUS adapter not initialized");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SBUS adapter not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
|
||||
boolean[] statuses = adapter.readStatusChannels(config.subnetId, config.id);
|
||||
if (statuses == null) {
|
||||
logger.warn("Received null status channels from SBUS device");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
State state = statuses[channelConfig.channelNumber - 1] ? OnOffType.ON : OnOffType.OFF;
|
||||
updateState(channel.getUID(), state);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error reading status channels", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
final SbusAdapter 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) {
|
||||
boolean isOn = command.equals(OnOffType.ON);
|
||||
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
|
||||
adapter.writeSingleChannel(config.subnetId, config.id, channelConfig.channelNumber, isOn);
|
||||
updateState(channelUID, isOn ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error handling command", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.internal.config.SbusChannelConfig;
|
||||
import org.openhab.binding.sbus.internal.config.SbusDeviceConfig;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
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;
|
||||
|
||||
import ro.ciprianpascu.sbus.facade.SbusAdapter;
|
||||
|
||||
/**
|
||||
* 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
|
||||
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() {
|
||||
handleReadTemperature();
|
||||
}
|
||||
|
||||
private void handleReadTemperature() {
|
||||
final SbusAdapter adapter = super.sbusAdapter;
|
||||
if (adapter == null) {
|
||||
logger.warn("SBUS adapter not initialized");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SBUS adapter not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SbusDeviceConfig config = getConfigAs(SbusDeviceConfig.class);
|
||||
float[] temperatures = adapter.readTemperatures(config.subnetId, config.id);
|
||||
if (temperatures == null) {
|
||||
logger.warn("Received null temperatures from SBUS device");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over all channels and update their states with corresponding temperatures
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
SbusChannelConfig channelConfig = channel.getConfiguration().as(SbusChannelConfig.class);
|
||||
if (channelConfig.channelNumber > 0 && channelConfig.channelNumber <= temperatures.length) {
|
||||
float temperature = temperatures[channelConfig.channelNumber - 1];
|
||||
updateState(channel.getUID(), new QuantityType<>(temperature, SIUnits.CELSIUS));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error reading temperature", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Temperature sensors are read-only
|
||||
logger.debug("Temperature device is read-only, ignoring command");
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Timestamp-value pair that can be updated atomically
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
* @param <V> type of the value
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AtomicStampedValue<V> implements Cloneable {
|
||||
|
||||
private long stamp;
|
||||
private V value;
|
||||
|
||||
private AtomicStampedValue(AtomicStampedValue<V> copy) {
|
||||
this(copy.stamp, copy.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct new stamped key-value pair
|
||||
*
|
||||
* @param stamp stamp for the data
|
||||
* @param value value for the data
|
||||
*
|
||||
* @throws NullPointerException when key or value is null
|
||||
*/
|
||||
public AtomicStampedValue(long stamp, V value) {
|
||||
Objects.requireNonNull(value, "value should not be null!");
|
||||
this.stamp = stamp;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data in this instance atomically
|
||||
*
|
||||
* @param stamp stamp for the data
|
||||
* @param value value for the data
|
||||
*
|
||||
* @throws NullPointerException when value is null
|
||||
*/
|
||||
public synchronized void update(long stamp, V value) {
|
||||
Objects.requireNonNull(value, "value should not be null!");
|
||||
this.stamp = stamp;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy data atomically and return the new (shallow) copy
|
||||
*
|
||||
* @return new copy of the data
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized AtomicStampedValue<V> copy() {
|
||||
return (AtomicStampedValue<V>) this.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronized implementation of clone with exception swallowing
|
||||
*/
|
||||
@Override
|
||||
protected synchronized Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// We should never end up here since this class implements Cloneable
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy data atomically if data is after certain stamp ("fresh" enough)
|
||||
*
|
||||
* @param stampMin
|
||||
* @return null, if the stamp of this instance is before stampMin. Otherwise return the data copied
|
||||
*/
|
||||
public synchronized @Nullable AtomicStampedValue<V> copyIfStampAfter(long stampMin) {
|
||||
if (stampMin <= this.stamp) {
|
||||
return new AtomicStampedValue<>(this);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stamp
|
||||
*/
|
||||
public long getStamp() {
|
||||
return stamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value
|
||||
*/
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two AtomicStampedKeyValue objects based on stamps
|
||||
*
|
||||
* Nulls are ordered first
|
||||
*
|
||||
* @param x first instance
|
||||
* @param y second instance
|
||||
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater
|
||||
* than the second.
|
||||
*/
|
||||
public static int compare(@SuppressWarnings("rawtypes") @Nullable AtomicStampedValue x,
|
||||
@SuppressWarnings("rawtypes") @Nullable AtomicStampedValue y) {
|
||||
if (x == null) {
|
||||
return -1;
|
||||
} else if (y == null) {
|
||||
return 1;
|
||||
} else {
|
||||
return Long.compare(x.stamp, y.stamp);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
/**
|
||||
* The {@link CascadedValueTransformationImpl} implements {@link SingleValueTransformation for a cascaded set of
|
||||
* transformations}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
* @author Ciprian Pascu - Copied from HTTP binding to provide consistent user experience
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CascadedValueTransformationImpl implements ValueTransformation {
|
||||
private final List<SingleValueTransformation> transformations;
|
||||
|
||||
public CascadedValueTransformationImpl(@Nullable String transformationString) {
|
||||
String transformationNonNull = transformationString == null ? "" : transformationString;
|
||||
List<SingleValueTransformation> localTransformations = Arrays.stream(transformationNonNull.split("∩"))
|
||||
.filter(s -> !s.isEmpty()).map(transformation -> new SingleValueTransformation(transformation))
|
||||
.collect(Collectors.toList());
|
||||
if (localTransformations.isEmpty()) {
|
||||
localTransformations = List.of(new SingleValueTransformation(transformationString));
|
||||
}
|
||||
transformations = localTransformations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transform(BundleContext context, String value) {
|
||||
String input = value;
|
||||
// process all transformations
|
||||
for (final ValueTransformation transformation : transformations) {
|
||||
input = transformation.transform(context, input);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdentityTransform() {
|
||||
return transformations.stream().allMatch(SingleValueTransformation::isIdentityTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CascadedValueTransformationImpl("
|
||||
+ transformations.stream().map(SingleValueTransformation::toString).collect(Collectors.joining(" ∩ "))
|
||||
+ ")";
|
||||
}
|
||||
|
||||
List<SingleValueTransformation> getTransformations() {
|
||||
return transformations;
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 static org.openhab.binding.sbus.ModbusBindingConstants.BINDING_ID;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.transport.sbus.ModbusReadFunctionCode;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ModbusBindingConstantsInternal} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusBindingConstantsInternal {
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_MODBUS_UDP = new ThingTypeUID(BINDING_ID, "udp");
|
||||
public static final ThingTypeUID THING_TYPE_MODBUS_SERIAL = new ThingTypeUID(BINDING_ID, "serial");
|
||||
public static final ThingTypeUID THING_TYPE_MODBUS_POLLER = new ThingTypeUID(BINDING_ID, "poller");
|
||||
public static final ThingTypeUID THING_TYPE_MODBUS_DATA = new ThingTypeUID(BINDING_ID, "data");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_SWITCH = "switch";
|
||||
public static final String CHANNEL_CONTACT = "contact";
|
||||
public static final String CHANNEL_DATETIME = "datetime";
|
||||
public static final String CHANNEL_DIMMER = "dimmer";
|
||||
public static final String CHANNEL_NUMBER = "number";
|
||||
public static final String CHANNEL_STRING = "string";
|
||||
public static final String CHANNEL_ROLLERSHUTTER = "rollershutter";
|
||||
public static final String CHANNEL_LAST_READ_SUCCESS = "lastReadSuccess";
|
||||
public static final String CHANNEL_LAST_READ_ERROR = "lastReadError";
|
||||
public static final String CHANNEL_LAST_WRITE_SUCCESS = "lastWriteSuccess";
|
||||
public static final String CHANNEL_LAST_WRITE_ERROR = "lastWriteError";
|
||||
|
||||
public static final String[] DATA_CHANNELS = { CHANNEL_SWITCH, CHANNEL_CONTACT, CHANNEL_DATETIME, CHANNEL_DIMMER,
|
||||
CHANNEL_NUMBER, CHANNEL_STRING, CHANNEL_ROLLERSHUTTER };
|
||||
|
||||
public static final String[] DATA_CHANNELS_TO_COPY_FROM_READ_TO_READWRITE = { CHANNEL_SWITCH, CHANNEL_CONTACT,
|
||||
CHANNEL_DATETIME, CHANNEL_DIMMER, CHANNEL_NUMBER, CHANNEL_STRING, CHANNEL_ROLLERSHUTTER,
|
||||
CHANNEL_LAST_READ_SUCCESS, CHANNEL_LAST_READ_ERROR };
|
||||
|
||||
public static final String[] DATA_CHANNELS_TO_DELEGATE_COMMAND_FROM_READWRITE_TO_WRITE = { CHANNEL_SWITCH,
|
||||
CHANNEL_CONTACT, CHANNEL_DATETIME, CHANNEL_DIMMER, CHANNEL_NUMBER, CHANNEL_STRING, CHANNEL_ROLLERSHUTTER };
|
||||
|
||||
public static final String WRITE_TYPE_COIL = "coil";
|
||||
public static final String WRITE_TYPE_HOLDING = "holding";
|
||||
|
||||
public static final String READ_TYPE_COIL = "coil";
|
||||
public static final String READ_TYPE_HOLDING_REGISTER = "holding";
|
||||
public static final String READ_TYPE_DISCRETE_INPUT = "discrete";
|
||||
public static final String READ_TYPE_INPUT_REGISTER = "input";
|
||||
|
||||
public static final Map<String, ModbusReadFunctionCode> READ_FUNCTION_CODES = new HashMap<>();
|
||||
static {
|
||||
READ_FUNCTION_CODES.put(READ_TYPE_COIL, ModbusReadFunctionCode.READ_COILS);
|
||||
READ_FUNCTION_CODES.put(READ_TYPE_DISCRETE_INPUT, ModbusReadFunctionCode.READ_INPUT_DISCRETES);
|
||||
READ_FUNCTION_CODES.put(READ_TYPE_INPUT_REGISTER, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
|
||||
READ_FUNCTION_CODES.put(READ_TYPE_HOLDING_REGISTER, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 static org.openhab.binding.sbus.internal.ModbusBindingConstantsInternal.THING_TYPE_MODBUS_DATA;
|
||||
import static org.openhab.binding.sbus.internal.ModbusBindingConstantsInternal.THING_TYPE_MODBUS_POLLER;
|
||||
import static org.openhab.binding.sbus.internal.ModbusBindingConstantsInternal.THING_TYPE_MODBUS_SERIAL;
|
||||
import static org.openhab.binding.sbus.internal.ModbusBindingConstantsInternal.THING_TYPE_MODBUS_UDP;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sbus.handler.ModbusPollerThingHandler;
|
||||
import org.openhab.binding.sbus.internal.handler.ModbusDataThingHandler;
|
||||
import org.openhab.binding.sbus.internal.handler.ModbusSerialThingHandler;
|
||||
import org.openhab.binding.sbus.internal.handler.ModbusUdpThingHandler;
|
||||
import org.openhab.core.io.transport.sbus.ModbusManager;
|
||||
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.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ModbusHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.modbus")
|
||||
@NonNullByDefault
|
||||
public class ModbusHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ModbusHandlerFactory.class);
|
||||
|
||||
private @NonNullByDefault({}) ModbusManager manager;
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
|
||||
static {
|
||||
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_MODBUS_UDP);
|
||||
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_MODBUS_SERIAL);
|
||||
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_MODBUS_POLLER);
|
||||
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_MODBUS_DATA);
|
||||
}
|
||||
|
||||
@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_MODBUS_UDP)) {
|
||||
logger.debug("createHandler Modbus udp");
|
||||
return new ModbusUdpThingHandler((Bridge) thing, manager);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_MODBUS_SERIAL)) {
|
||||
logger.debug("createHandler Modbus serial");
|
||||
return new ModbusSerialThingHandler((Bridge) thing, manager);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_MODBUS_POLLER)) {
|
||||
logger.debug("createHandler Modbus poller");
|
||||
return new ModbusPollerThingHandler((Bridge) thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_MODBUS_DATA)) {
|
||||
logger.debug("createHandler data");
|
||||
return new ModbusDataThingHandler(thing);
|
||||
}
|
||||
logger.error("createHandler for unknown thing type uid {}. Thing label was: {}", thing.getThingTypeUID(),
|
||||
thing.getLabel());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
public void setModbusManager(ModbusManager manager) {
|
||||
logger.debug("Setting manager: {}", manager);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void unsetModbusManager(ModbusManager manager) {
|
||||
this.manager = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.internal.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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ro.ciprianpascu.sbus.facade.SbusAdapter;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
private @Nullable SbusAdapter sbusConnection;
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
try {
|
||||
// Get configuration using the config class
|
||||
SbusBridgeConfig config = getConfigAs(SbusBridgeConfig.class);
|
||||
if (config.host.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize SBUS connection with the configuration parameters
|
||||
sbusConnection = new SbusAdapter(config.host, config.port);
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
logger.debug("SBUS bridge handler initialized successfully");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error initializing SBUS bridge", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SBUS adapter connection.
|
||||
*
|
||||
* @return the SBUS adapter
|
||||
*/
|
||||
public @Nullable SbusAdapter getSbusConnection() {
|
||||
return sbusConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the handler by closing the SBUS connection.
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing SBUS bridge handler");
|
||||
final SbusAdapter connection = sbusConnection;
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Bridge doesn't handle commands directly
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 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.binding.sbus.handler.SbusRgbwHandler;
|
||||
import org.openhab.binding.sbus.handler.SbusSwitchHandler;
|
||||
import org.openhab.binding.sbus.handler.SbusTemperatureHandler;
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationHelper;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.TypeParser;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class describing transformation of a command or state.
|
||||
*
|
||||
* Inspired from other openHAB binding "Transformation" classes.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SingleValueTransformation implements ValueTransformation {
|
||||
|
||||
public static final String TRANSFORM_DEFAULT = "default";
|
||||
public static final ValueTransformation IDENTITY_TRANSFORMATION = new SingleValueTransformation(TRANSFORM_DEFAULT,
|
||||
null, null);
|
||||
|
||||
/** RegEx to extract and parse a function String <code>'(.*?)\((.*)\)'</code> */
|
||||
private static final Pattern EXTRACT_FUNCTION_PATTERN_OLD = Pattern.compile("(?<service>.*?)\\((?<arg>.*)\\)");
|
||||
private static final Pattern EXTRACT_FUNCTION_PATTERN_NEW = Pattern.compile("(?<service>.*?):(?<arg>.*)");
|
||||
|
||||
/**
|
||||
* Ordered list of types that are tried out first when trying to parse transformed command
|
||||
*/
|
||||
private static final List<Class<? extends Command>> DEFAULT_TYPES = new ArrayList<>();
|
||||
static {
|
||||
DEFAULT_TYPES.add(DecimalType.class);
|
||||
DEFAULT_TYPES.add(OpenClosedType.class);
|
||||
DEFAULT_TYPES.add(OnOffType.class);
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SingleValueTransformation.class);
|
||||
|
||||
private final @Nullable String transformation;
|
||||
final @Nullable String transformationServiceName;
|
||||
final @Nullable String transformationServiceParam;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transformation either FUN(VAL) (standard transformation syntax), default (identity transformation
|
||||
* (output equals input)) or some other value (output is a constant). Futhermore, empty string is
|
||||
* considered the same way as "default".
|
||||
*/
|
||||
public SingleValueTransformation(@Nullable String transformation) {
|
||||
this.transformation = transformation;
|
||||
//
|
||||
// Parse transformation configuration here on construction, but delay the
|
||||
// construction of TransformationService to call-time
|
||||
if (transformation == null || transformation.isEmpty() || transformation.equalsIgnoreCase(TRANSFORM_DEFAULT)) {
|
||||
// no-op (identity) transformation
|
||||
transformationServiceName = null;
|
||||
transformationServiceParam = null;
|
||||
} else {
|
||||
int colonIndex = transformation.indexOf(":");
|
||||
int parenthesisOpenIndex = transformation.indexOf("(");
|
||||
|
||||
final Matcher matcher;
|
||||
if (parenthesisOpenIndex != -1 && (colonIndex == -1 || parenthesisOpenIndex < colonIndex)) {
|
||||
matcher = EXTRACT_FUNCTION_PATTERN_OLD.matcher(transformation);
|
||||
} else {
|
||||
matcher = EXTRACT_FUNCTION_PATTERN_NEW.matcher(transformation);
|
||||
}
|
||||
if (matcher.matches()) {
|
||||
matcher.reset();
|
||||
matcher.find();
|
||||
transformationServiceName = matcher.group("service");
|
||||
transformationServiceParam = matcher.group("arg");
|
||||
} else {
|
||||
logger.debug(
|
||||
"Given transformation configuration '{}' did not match the FUN(VAL) pattern. Transformation output will be constant '{}'",
|
||||
transformation, transformation);
|
||||
transformationServiceName = null;
|
||||
transformationServiceParam = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing, thus package visibility by design
|
||||
*
|
||||
* @param transformation
|
||||
* @param transformationServiceName
|
||||
* @param transformationServiceParam
|
||||
*/
|
||||
SingleValueTransformation(String transformation, @Nullable String transformationServiceName,
|
||||
@Nullable String transformationServiceParam) {
|
||||
this.transformation = transformation;
|
||||
this.transformationServiceName = transformationServiceName;
|
||||
this.transformationServiceParam = transformationServiceParam;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transform(BundleContext context, String value) {
|
||||
String transformedResponse;
|
||||
String transformationServiceName = this.transformationServiceName;
|
||||
String transformationServiceParam = this.transformationServiceParam;
|
||||
|
||||
if (transformationServiceName != null) {
|
||||
try {
|
||||
if (transformationServiceParam == null) {
|
||||
throw new TransformationException(
|
||||
"transformation service parameter is missing! Invalid transform?");
|
||||
}
|
||||
@Nullable
|
||||
TransformationService transformationService = TransformationHelper.getTransformationService(context,
|
||||
transformationServiceName);
|
||||
if (transformationService != null) {
|
||||
transformedResponse = transformationService.transform(transformationServiceParam, value);
|
||||
} else {
|
||||
transformedResponse = value;
|
||||
logger.warn("couldn't transform response because transformationService of type '{}' is unavailable",
|
||||
transformationServiceName);
|
||||
}
|
||||
} catch (TransformationException te) {
|
||||
logger.error("transformation throws exception [transformation={}, response={}]", transformation, value,
|
||||
te);
|
||||
|
||||
// in case of an error we return the response without any
|
||||
// transformation
|
||||
transformedResponse = value;
|
||||
}
|
||||
} else if (isIdentityTransform()) {
|
||||
// identity transformation
|
||||
transformedResponse = value;
|
||||
} else {
|
||||
// pass value as is
|
||||
transformedResponse = this.transformation;
|
||||
}
|
||||
|
||||
return transformedResponse == null ? "" : transformedResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdentityTransform() {
|
||||
return TRANSFORM_DEFAULT.equalsIgnoreCase(this.transformation);
|
||||
}
|
||||
|
||||
public static Optional<Command> tryConvertToCommand(String transformed) {
|
||||
return Optional.ofNullable(TypeParser.parseCommand(DEFAULT_TYPES, transformed));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SingleValueTransformation [transformation=" + transformation + ", transformationServiceName="
|
||||
+ transformationServiceName + ", transformationServiceParam=" + transformationServiceParam + "]";
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.TypeParser;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
/**
|
||||
* Interface for Transformation
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ValueTransformation {
|
||||
|
||||
String transform(BundleContext context, String value);
|
||||
|
||||
boolean isIdentityTransform();
|
||||
|
||||
/**
|
||||
* Transform state to another state using this transformation
|
||||
*
|
||||
* @param context
|
||||
* @param types types to used to parse the transformation result
|
||||
* @param state
|
||||
* @return Transformed command, or null if no transformation was possible
|
||||
*/
|
||||
default @Nullable State transformState(BundleContext context, List<Class<? extends State>> types, State state) {
|
||||
// Note that even identity transformations go through the State -> String -> State steps. This does add some
|
||||
// overhead but takes care of DecimalType -> PercentType conversions, for example.
|
||||
final String stateAsString = state.toString();
|
||||
final String transformed = transform(context, stateAsString);
|
||||
return TypeParser.parseState(types, transformed);
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Configuration for data thing
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusDataConfiguration {
|
||||
|
||||
private @Nullable String readStart;
|
||||
private @Nullable String readTransform;
|
||||
private @Nullable String readValueType;
|
||||
private @Nullable String writeStart;
|
||||
private @Nullable String writeType;
|
||||
private @Nullable String writeTransform;
|
||||
private @Nullable String writeValueType;
|
||||
private boolean writeMultipleEvenWithSingleRegisterOrCoil;
|
||||
private int writeMaxTries = 3; // backwards compatibility and tests
|
||||
private long updateUnchangedValuesEveryMillis = 1000L;
|
||||
|
||||
public @Nullable String getReadStart() {
|
||||
return readStart;
|
||||
}
|
||||
|
||||
public void setReadStart(String readStart) {
|
||||
this.readStart = readStart;
|
||||
}
|
||||
|
||||
public @Nullable String getReadTransform() {
|
||||
return readTransform;
|
||||
}
|
||||
|
||||
public void setReadTransform(String readTransform) {
|
||||
this.readTransform = readTransform;
|
||||
}
|
||||
|
||||
public @Nullable String getReadValueType() {
|
||||
return readValueType;
|
||||
}
|
||||
|
||||
public void setReadValueType(String readValueType) {
|
||||
this.readValueType = readValueType;
|
||||
}
|
||||
|
||||
public @Nullable String getWriteStart() {
|
||||
return writeStart;
|
||||
}
|
||||
|
||||
public void setWriteStart(String writeStart) {
|
||||
this.writeStart = writeStart;
|
||||
}
|
||||
|
||||
public @Nullable String getWriteType() {
|
||||
return writeType;
|
||||
}
|
||||
|
||||
public void setWriteType(String writeType) {
|
||||
this.writeType = writeType;
|
||||
}
|
||||
|
||||
public @Nullable String getWriteTransform() {
|
||||
return writeTransform;
|
||||
}
|
||||
|
||||
public void setWriteTransform(String writeTransform) {
|
||||
this.writeTransform = writeTransform;
|
||||
}
|
||||
|
||||
public @Nullable String getWriteValueType() {
|
||||
return writeValueType;
|
||||
}
|
||||
|
||||
public void setWriteValueType(String writeValueType) {
|
||||
this.writeValueType = writeValueType;
|
||||
}
|
||||
|
||||
public boolean isWriteMultipleEvenWithSingleRegisterOrCoil() {
|
||||
return writeMultipleEvenWithSingleRegisterOrCoil;
|
||||
}
|
||||
|
||||
public void setWriteMultipleEvenWithSingleRegisterOrCoil(boolean writeMultipleEvenWithSingleRegisterOrCoil) {
|
||||
this.writeMultipleEvenWithSingleRegisterOrCoil = writeMultipleEvenWithSingleRegisterOrCoil;
|
||||
}
|
||||
|
||||
public int getWriteMaxTries() {
|
||||
return writeMaxTries;
|
||||
}
|
||||
|
||||
public void setWriteMaxTries(int writeMaxTries) {
|
||||
this.writeMaxTries = writeMaxTries;
|
||||
}
|
||||
|
||||
public long getUpdateUnchangedValuesEveryMillis() {
|
||||
return updateUnchangedValuesEveryMillis;
|
||||
}
|
||||
|
||||
public void setUpdateUnchangedValuesEveryMillis(long updateUnchangedValuesEveryMillis) {
|
||||
this.updateUnchangedValuesEveryMillis = updateUnchangedValuesEveryMillis;
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Configuration for poller thing
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusPollerConfiguration {
|
||||
private long refresh;
|
||||
private int start;
|
||||
private int length;
|
||||
private @Nullable String type;
|
||||
private int maxTries = 3;// backwards compatibility and tests
|
||||
private long cacheMillis = 50L;
|
||||
|
||||
/**
|
||||
* Gets refresh period in milliseconds
|
||||
*/
|
||||
public long getRefresh() {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets refresh period in milliseconds
|
||||
*/
|
||||
|
||||
public void setRefresh(long refresh) {
|
||||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address of the first register, coil, or discrete input to poll. Input as zero-based index number.
|
||||
*
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets address of the first register, coil, or discrete input to poll. Input as zero-based index number.
|
||||
*
|
||||
*/
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of registers, coils or discrete inputs to read.
|
||||
*/
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets number of registers, coils or discrete inputs to read.
|
||||
*/
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets type of modbus items to poll
|
||||
*
|
||||
*/
|
||||
public @Nullable String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type of modbus items to poll
|
||||
*
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getMaxTries() {
|
||||
return maxTries;
|
||||
}
|
||||
|
||||
public void setMaxTries(int maxTries) {
|
||||
this.maxTries = maxTries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets time to cache data.
|
||||
*
|
||||
* This is used for reusing cached data with explicit refresh calls.
|
||||
*/
|
||||
public long getCacheMillis() {
|
||||
return cacheMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets time to cache data, in milliseconds
|
||||
*
|
||||
*/
|
||||
public void setCacheMillis(long cacheMillis) {
|
||||
this.cacheMillis = cacheMillis;
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Configuration for serial thing
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusSerialConfiguration {
|
||||
private @Nullable String port;
|
||||
private int id = 1;
|
||||
private int subnetId = 1;
|
||||
private int baud;
|
||||
private @Nullable String stopBits;
|
||||
private @Nullable String parity;
|
||||
private int dataBits;
|
||||
private String encoding = "rtu";
|
||||
private boolean echo;
|
||||
private int receiveTimeoutMillis = 1500;
|
||||
private String flowControlIn = "none";
|
||||
private String flowControlOut = "none";
|
||||
private int timeBetweenTransactionsMillis = 35;
|
||||
private int connectMaxTries = 1;
|
||||
private int afterConnectionDelayMillis;
|
||||
private int connectTimeoutMillis = 10_000;
|
||||
private boolean enableDiscovery;
|
||||
|
||||
public @Nullable String getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(String port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getSubnetId() {
|
||||
return subnetId;
|
||||
}
|
||||
|
||||
public void setSubnetId(int subnetId) {
|
||||
this.subnetId = subnetId;
|
||||
}
|
||||
|
||||
public int getBaud() {
|
||||
return baud;
|
||||
}
|
||||
|
||||
public void setBaud(int baud) {
|
||||
this.baud = baud;
|
||||
}
|
||||
|
||||
public @Nullable String getStopBits() {
|
||||
return stopBits;
|
||||
}
|
||||
|
||||
public void setStopBits(String stopBits) {
|
||||
this.stopBits = stopBits;
|
||||
}
|
||||
|
||||
public @Nullable String getParity() {
|
||||
return parity;
|
||||
}
|
||||
|
||||
public void setParity(String parity) {
|
||||
this.parity = parity;
|
||||
}
|
||||
|
||||
public int getDataBits() {
|
||||
return dataBits;
|
||||
}
|
||||
|
||||
public void setDataBits(int dataBits) {
|
||||
this.dataBits = dataBits;
|
||||
}
|
||||
|
||||
public @Nullable String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public void setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public boolean isEcho() {
|
||||
return echo;
|
||||
}
|
||||
|
||||
public void setEcho(boolean echo) {
|
||||
this.echo = echo;
|
||||
}
|
||||
|
||||
public int getReceiveTimeoutMillis() {
|
||||
return receiveTimeoutMillis;
|
||||
}
|
||||
|
||||
public void setReceiveTimeoutMillis(int receiveTimeoutMillis) {
|
||||
this.receiveTimeoutMillis = receiveTimeoutMillis;
|
||||
}
|
||||
|
||||
public @Nullable String getFlowControlIn() {
|
||||
return flowControlIn;
|
||||
}
|
||||
|
||||
public void setFlowControlIn(String flowControlIn) {
|
||||
this.flowControlIn = flowControlIn;
|
||||
}
|
||||
|
||||
public @Nullable String getFlowControlOut() {
|
||||
return flowControlOut;
|
||||
}
|
||||
|
||||
public void setFlowControlOut(String flowControlOut) {
|
||||
this.flowControlOut = flowControlOut;
|
||||
}
|
||||
|
||||
public int getTimeBetweenTransactionsMillis() {
|
||||
return timeBetweenTransactionsMillis;
|
||||
}
|
||||
|
||||
public void setTimeBetweenTransactionsMillis(int timeBetweenTransactionsMillis) {
|
||||
this.timeBetweenTransactionsMillis = timeBetweenTransactionsMillis;
|
||||
}
|
||||
|
||||
public int getConnectMaxTries() {
|
||||
return connectMaxTries;
|
||||
}
|
||||
|
||||
public void setConnectMaxTries(int connectMaxTries) {
|
||||
this.connectMaxTries = connectMaxTries;
|
||||
}
|
||||
|
||||
public int getAfterConnectionDelayMillis() {
|
||||
return afterConnectionDelayMillis;
|
||||
}
|
||||
|
||||
public void setAfterConnectionDelayMillis(int afterConnectionDelayMillis) {
|
||||
this.afterConnectionDelayMillis = afterConnectionDelayMillis;
|
||||
}
|
||||
|
||||
public int getConnectTimeoutMillis() {
|
||||
return connectTimeoutMillis;
|
||||
}
|
||||
|
||||
public void setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||
this.connectTimeoutMillis = connectTimeoutMillis;
|
||||
}
|
||||
|
||||
public boolean isDiscoveryEnabled() {
|
||||
return enableDiscovery;
|
||||
}
|
||||
|
||||
public void setDiscoveryEnabled(boolean enableDiscovery) {
|
||||
this.enableDiscovery = enableDiscovery;
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Configuration for udp thing
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusUdpConfiguration {
|
||||
private @Nullable String host;
|
||||
private int port;
|
||||
private int id = 1;
|
||||
private int subnetId = 1;
|
||||
private int timeBetweenTransactionsMillis = 60;
|
||||
private int timeBetweenReconnectMillis;
|
||||
private int connectMaxTries = 1;
|
||||
private int reconnectAfterMillis;
|
||||
private int afterConnectionDelayMillis;
|
||||
private int connectTimeoutMillis = 10_000;
|
||||
private boolean enableDiscovery;
|
||||
private boolean rtuEncoded;
|
||||
|
||||
public boolean getRtuEncoded() {
|
||||
return rtuEncoded;
|
||||
}
|
||||
|
||||
public @Nullable String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getSubnetId() {
|
||||
return subnetId;
|
||||
}
|
||||
|
||||
public void setSubnetId(int subnetId) {
|
||||
this.subnetId = subnetId;
|
||||
}
|
||||
|
||||
public int getTimeBetweenTransactionsMillis() {
|
||||
return timeBetweenTransactionsMillis;
|
||||
}
|
||||
|
||||
public void setTimeBetweenTransactionsMillis(int timeBetweenTransactionsMillis) {
|
||||
this.timeBetweenTransactionsMillis = timeBetweenTransactionsMillis;
|
||||
}
|
||||
|
||||
public int getTimeBetweenReconnectMillis() {
|
||||
return timeBetweenReconnectMillis;
|
||||
}
|
||||
|
||||
public void setTimeBetweenReconnectMillis(int timeBetweenReconnectMillis) {
|
||||
this.timeBetweenReconnectMillis = timeBetweenReconnectMillis;
|
||||
}
|
||||
|
||||
public int getConnectMaxTries() {
|
||||
return connectMaxTries;
|
||||
}
|
||||
|
||||
public void setConnectMaxTries(int connectMaxTries) {
|
||||
this.connectMaxTries = connectMaxTries;
|
||||
}
|
||||
|
||||
public int getReconnectAfterMillis() {
|
||||
return reconnectAfterMillis;
|
||||
}
|
||||
|
||||
public void setReconnectAfterMillis(int reconnectAfterMillis) {
|
||||
this.reconnectAfterMillis = reconnectAfterMillis;
|
||||
}
|
||||
|
||||
public int getAfterConnectionDelayMillis() {
|
||||
return afterConnectionDelayMillis;
|
||||
}
|
||||
|
||||
public void setAfterConnectionDelayMillis(int afterConnectionDelayMillis) {
|
||||
this.afterConnectionDelayMillis = afterConnectionDelayMillis;
|
||||
}
|
||||
|
||||
public int getConnectTimeoutMillis() {
|
||||
return connectTimeoutMillis;
|
||||
}
|
||||
|
||||
public void setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||
this.connectTimeoutMillis = connectTimeoutMillis;
|
||||
}
|
||||
|
||||
public boolean isDiscoveryEnabled() {
|
||||
return enableDiscovery;
|
||||
}
|
||||
|
||||
public void setDiscoveryEnabled(boolean enableDiscovery) {
|
||||
this.enableDiscovery = enableDiscovery;
|
||||
}
|
||||
}
|
@ -10,22 +10,26 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.sbus.internal;
|
||||
package org.openhab.binding.sbus.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import ro.ciprianpascu.sbus.Sbus;
|
||||
|
||||
/**
|
||||
* Exception for binding configuration exceptions
|
||||
* The {@link SbusBridgeConfig} class contains fields mapping bridge configuration parameters.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusConfigurationException extends Exception {
|
||||
public class SbusBridgeConfig {
|
||||
/**
|
||||
* The host address of the SBUS bridge
|
||||
*/
|
||||
public String host = "";
|
||||
|
||||
public ModbusConfigurationException(String errmsg) {
|
||||
super(errmsg);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -466597103876477780L;
|
||||
/**
|
||||
* The port number for SBUS communication
|
||||
*/
|
||||
public int port = Sbus.DEFAULT_PORT;
|
||||
}
|
@ -10,18 +10,19 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.sbus.handler;
|
||||
package org.openhab.binding.sbus.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Signals that {@link ModbusEndpointThingHandler} is not properly initialized yet, and the requested operation cannot
|
||||
* be completed.
|
||||
* The {@link SbusChannelConfig} class contains fields mapping channel configuration parameters.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EndpointNotInitializedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -6721646244844348903L;
|
||||
public class SbusChannelConfig {
|
||||
/**
|
||||
* The physical channel number on the SBUS device
|
||||
*/
|
||||
public int channelNumber;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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;
|
||||
|
||||
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 milliseconds
|
||||
*/
|
||||
public int refresh = 30000; // Default value from thing-types.xml
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sbus.handler.EndpointNotInitializedException;
|
||||
import org.openhab.binding.sbus.handler.ModbusEndpointThingHandler;
|
||||
import org.openhab.binding.sbus.internal.ModbusConfigurationException;
|
||||
import org.openhab.core.io.transport.sbus.ModbusCommunicationInterface;
|
||||
import org.openhab.core.io.transport.sbus.ModbusManager;
|
||||
import org.openhab.core.io.transport.sbus.endpoint.EndpointPoolConfiguration;
|
||||
import org.openhab.core.io.transport.sbus.endpoint.ModbusSlaveEndpoint;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for Modbus Slave endpoint thing handlers
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*
|
||||
* @param <E> endpoint class
|
||||
* @param <C> config class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractModbusEndpointThingHandler<E extends ModbusSlaveEndpoint, C> extends BaseBridgeHandler
|
||||
implements ModbusEndpointThingHandler {
|
||||
|
||||
protected volatile @Nullable C config;
|
||||
protected volatile @Nullable E endpoint;
|
||||
protected ModbusManager modbusManager;
|
||||
protected volatile @NonNullByDefault({}) EndpointPoolConfiguration poolConfiguration;
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractModbusEndpointThingHandler.class);
|
||||
private @NonNullByDefault({}) ModbusCommunicationInterface comms;
|
||||
|
||||
public AbstractModbusEndpointThingHandler(Bridge bridge, ModbusManager modbusManager) {
|
||||
super(bridge);
|
||||
this.modbusManager = modbusManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
synchronized (this) {
|
||||
logger.trace("Initializing {} from status {}", this.getThing().getUID(), this.getThing().getStatus());
|
||||
if (this.getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
// If the bridge was online then first change it to offline.
|
||||
// this ensures that children will be notified about the change
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
try {
|
||||
configure();
|
||||
@Nullable
|
||||
E endpoint = this.endpoint;
|
||||
if (endpoint == null) {
|
||||
throw new IllegalStateException("endpoint null after configuration!");
|
||||
}
|
||||
try {
|
||||
comms = modbusManager.newModbusCommunicationInterface(endpoint, poolConfiguration);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (IllegalArgumentException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
formatConflictingParameterError());
|
||||
}
|
||||
} catch (ModbusConfigurationException e) {
|
||||
logger.debug("Exception during initialization", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
|
||||
"Exception during initialization: %s (%s)", e.getMessage(), e.getClass().getSimpleName()));
|
||||
} finally {
|
||||
logger.trace("initialize() of thing {} '{}' finished", thing.getUID(), thing.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
try {
|
||||
ModbusCommunicationInterface localComms = comms;
|
||||
if (localComms != null) {
|
||||
localComms.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error closing modbus communication interface", e);
|
||||
} finally {
|
||||
comms = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ModbusCommunicationInterface getCommunicationInterface() {
|
||||
return comms;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public E getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract int getSlaveId() throws EndpointNotInitializedException;
|
||||
|
||||
/**
|
||||
* Must be overriden by subclasses to initialize config, endpoint, and poolConfiguration
|
||||
*/
|
||||
protected abstract void configure() throws ModbusConfigurationException;
|
||||
|
||||
/**
|
||||
* Format error message in case some other endpoint has been configured with different
|
||||
* {@link EndpointPoolConfiguration}
|
||||
*/
|
||||
protected abstract String formatConflictingParameterError();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,125 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sbus.discovery.internal.ModbusEndpointDiscoveryService;
|
||||
import org.openhab.binding.sbus.handler.EndpointNotInitializedException;
|
||||
import org.openhab.binding.sbus.internal.ModbusConfigurationException;
|
||||
import org.openhab.binding.sbus.internal.config.ModbusSerialConfiguration;
|
||||
import org.openhab.core.io.transport.sbus.ModbusManager;
|
||||
import org.openhab.core.io.transport.sbus.endpoint.EndpointPoolConfiguration;
|
||||
import org.openhab.core.io.transport.sbus.endpoint.ModbusSerialSlaveEndpoint;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
|
||||
/**
|
||||
* Endpoint thing handler for serial slaves
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusSerialThingHandler
|
||||
extends AbstractModbusEndpointThingHandler<ModbusSerialSlaveEndpoint, ModbusSerialConfiguration> {
|
||||
|
||||
public ModbusSerialThingHandler(Bridge bridge, ModbusManager manager) {
|
||||
super(bridge, manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() throws ModbusConfigurationException {
|
||||
ModbusSerialConfiguration config = getConfigAs(ModbusSerialConfiguration.class);
|
||||
String port = config.getPort();
|
||||
int baud = config.getBaud();
|
||||
String flowControlIn = config.getFlowControlIn();
|
||||
String flowControlOut = config.getFlowControlOut();
|
||||
String stopBits = config.getStopBits();
|
||||
String parity = config.getParity();
|
||||
String encoding = config.getEncoding();
|
||||
if (port == null || flowControlIn == null || flowControlOut == null || stopBits == null || parity == null
|
||||
|| encoding == null) {
|
||||
throw new ModbusConfigurationException(
|
||||
"port, baud, flowControlIn, flowControlOut, stopBits, parity, encoding all must be non-null!");
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
|
||||
EndpointPoolConfiguration poolConfiguration = new EndpointPoolConfiguration();
|
||||
this.poolConfiguration = poolConfiguration;
|
||||
poolConfiguration.setConnectMaxTries(config.getConnectMaxTries());
|
||||
poolConfiguration.setAfterConnectionDelayMillis(config.getAfterConnectionDelayMillis());
|
||||
poolConfiguration.setConnectTimeoutMillis(config.getConnectTimeoutMillis());
|
||||
poolConfiguration.setInterTransactionDelayMillis(config.getTimeBetweenTransactionsMillis());
|
||||
|
||||
// Never reconnect serial connections "automatically"
|
||||
poolConfiguration.setInterConnectDelayMillis(1000);
|
||||
poolConfiguration.setReconnectAfterMillis(-1);
|
||||
|
||||
endpoint = new ModbusSerialSlaveEndpoint(port, baud, flowControlIn, flowControlOut, config.getDataBits(),
|
||||
stopBits, parity, encoding, config.isEcho(), config.getReceiveTimeoutMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if auto discovery is enabled in the config
|
||||
*/
|
||||
@Override
|
||||
public boolean isDiscoveryEnabled() {
|
||||
if (config != null) {
|
||||
return config.isDiscoveryEnabled();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null") // Since endpoint in Optional.map cannot be null
|
||||
@Override
|
||||
protected String formatConflictingParameterError() {
|
||||
return String.format(
|
||||
"Endpoint '%s' has conflicting parameters: parameters of this thing (%s '%s') are different from some other thing's parameter. Ensure that all endpoints pointing to serial port '%s' have same parameters.",
|
||||
endpoint, thing.getUID(), this.thing.getLabel(),
|
||||
Optional.ofNullable(this.endpoint).map(e -> e.getPortName()).orElse("<null>"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlaveId() throws EndpointNotInitializedException {
|
||||
ModbusSerialConfiguration config = this.config;
|
||||
if (config == null) {
|
||||
throw new EndpointNotInitializedException();
|
||||
}
|
||||
return config.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSubnetId() throws EndpointNotInitializedException {
|
||||
ModbusSerialConfiguration config = this.config;
|
||||
if (config == null) {
|
||||
throw new EndpointNotInitializedException();
|
||||
}
|
||||
return config.getSubnetId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID getUID() {
|
||||
return getThing().getUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(ModbusEndpointDiscoveryService.class);
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sbus.discovery.internal.ModbusEndpointDiscoveryService;
|
||||
import org.openhab.binding.sbus.handler.EndpointNotInitializedException;
|
||||
import org.openhab.binding.sbus.internal.ModbusConfigurationException;
|
||||
import org.openhab.binding.sbus.internal.config.ModbusUdpConfiguration;
|
||||
import org.openhab.core.io.transport.sbus.ModbusManager;
|
||||
import org.openhab.core.io.transport.sbus.endpoint.EndpointPoolConfiguration;
|
||||
import org.openhab.core.io.transport.sbus.endpoint.ModbusUDPSlaveEndpoint;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
|
||||
/**
|
||||
* Endpoint thing handler for UDP slaves
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusUdpThingHandler
|
||||
extends AbstractModbusEndpointThingHandler<ModbusUDPSlaveEndpoint, ModbusUdpConfiguration> {
|
||||
|
||||
public ModbusUdpThingHandler(Bridge bridge, ModbusManager manager) {
|
||||
super(bridge, manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() throws ModbusConfigurationException {
|
||||
ModbusUdpConfiguration config = getConfigAs(ModbusUdpConfiguration.class);
|
||||
|
||||
String host = config.getHost();
|
||||
if (host == null) {
|
||||
throw new ModbusConfigurationException("host must be non-null!");
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
endpoint = new ModbusUDPSlaveEndpoint(host, config.getPort());
|
||||
|
||||
EndpointPoolConfiguration poolConfiguration = new EndpointPoolConfiguration();
|
||||
this.poolConfiguration = poolConfiguration;
|
||||
poolConfiguration.setConnectMaxTries(config.getConnectMaxTries());
|
||||
poolConfiguration.setAfterConnectionDelayMillis(config.getAfterConnectionDelayMillis());
|
||||
poolConfiguration.setConnectTimeoutMillis(config.getConnectTimeoutMillis());
|
||||
poolConfiguration.setInterConnectDelayMillis(config.getTimeBetweenReconnectMillis());
|
||||
poolConfiguration.setInterTransactionDelayMillis(config.getTimeBetweenTransactionsMillis());
|
||||
poolConfiguration.setReconnectAfterMillis(config.getReconnectAfterMillis());
|
||||
}
|
||||
|
||||
@SuppressWarnings("null") // since Optional.map is always called with NonNull argument
|
||||
@Override
|
||||
protected String formatConflictingParameterError() {
|
||||
return String.format(
|
||||
"Endpoint '%s' has conflicting parameters: parameters of this thing (%s '%s') are different from some other thing's parameter. Ensure that all endpoints pointing to udp slave '%s:%s' have same parameters.",
|
||||
endpoint, thing.getUID(), this.thing.getLabel(),
|
||||
Optional.ofNullable(this.endpoint).map(e -> e.getAddress()).orElse("<null>"),
|
||||
Optional.ofNullable(this.endpoint).map(e -> String.valueOf(e.getPort())).orElse("<null>"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlaveId() throws EndpointNotInitializedException {
|
||||
ModbusUdpConfiguration localConfig = config;
|
||||
if (localConfig == null) {
|
||||
throw new EndpointNotInitializedException();
|
||||
}
|
||||
return localConfig.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSubnetId() throws EndpointNotInitializedException {
|
||||
ModbusUdpConfiguration localConfig = config;
|
||||
if (localConfig == null) {
|
||||
throw new EndpointNotInitializedException();
|
||||
}
|
||||
return localConfig.getSubnetId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID getUID() {
|
||||
return getThing().getUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if discovery is enabled
|
||||
*/
|
||||
@Override
|
||||
public boolean isDiscoveryEnabled() {
|
||||
if (config != null) {
|
||||
return config.isDiscoveryEnabled();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(ModbusEndpointDiscoveryService.class);
|
||||
}
|
||||
}
|
@ -1,253 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.profiles;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.measure.Quantity;
|
||||
import javax.measure.UnconvertibleException;
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.profiles.ProfileCallback;
|
||||
import org.openhab.core.thing.profiles.ProfileContext;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeUID;
|
||||
import org.openhab.core.thing.profiles.StateProfile;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Profile for applying gain and offset to values.
|
||||
*
|
||||
* Output of the profile is
|
||||
* - (incoming value + pre-gain-offset) * gain (update towards item)
|
||||
* - (incoming value / gain) - pre-gain-offset (command from item)
|
||||
*
|
||||
* Gain can also specify unit of the result, converting otherwise bare numbers to ones with quantity.
|
||||
*
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusGainOffsetProfile<Q extends Quantity<Q>> implements StateProfile {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ModbusGainOffsetProfile.class);
|
||||
private static final String PREGAIN_OFFSET_PARAM = "pre-gain-offset";
|
||||
private static final String GAIN_PARAM = "gain";
|
||||
|
||||
private final ProfileCallback callback;
|
||||
private final ProfileContext context;
|
||||
|
||||
private Optional<QuantityType<Dimensionless>> pregainOffset;
|
||||
private Optional<QuantityType<Q>> gain;
|
||||
|
||||
public ModbusGainOffsetProfile(ProfileCallback callback, ProfileContext context) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
{
|
||||
Object rawOffsetValue = orDefault("0", this.context.getConfiguration().get(PREGAIN_OFFSET_PARAM));
|
||||
logger.debug("Configuring profile with {} parameter '{}'", PREGAIN_OFFSET_PARAM, rawOffsetValue);
|
||||
pregainOffset = parameterAsQuantityType(PREGAIN_OFFSET_PARAM, rawOffsetValue, Units.ONE);
|
||||
|
||||
}
|
||||
{
|
||||
Object gainValue = orDefault("1", this.context.getConfiguration().get(GAIN_PARAM));
|
||||
logger.debug("Configuring profile with {} parameter '{}'", GAIN_PARAM, gainValue);
|
||||
gain = parameterAsQuantityType(GAIN_PARAM, gainValue);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return pregainOffset.isPresent() && gain.isPresent();
|
||||
}
|
||||
|
||||
public Optional<QuantityType<Dimensionless>> getPregainOffset() {
|
||||
return pregainOffset;
|
||||
}
|
||||
|
||||
public Optional<QuantityType<Q>> getGain() {
|
||||
return gain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileTypeUID getProfileTypeUID() {
|
||||
return ModbusProfiles.GAIN_OFFSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromItem(State state) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromItem(Command command) {
|
||||
Type result = applyGainOffset(command, false);
|
||||
if (result instanceof Command cmd) {
|
||||
logger.trace("Command '{}' from item, sending converted '{}' state towards handler.", command, result);
|
||||
callback.handleCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromHandler(Command command) {
|
||||
Type result = applyGainOffset(command, true);
|
||||
if (result instanceof Command cmd) {
|
||||
logger.trace("Command '{}' from handler, sending converted '{}' command towards item.", command, result);
|
||||
callback.sendCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromHandler(State state) {
|
||||
State result = (State) applyGainOffset(state, true);
|
||||
logger.trace("State update '{}' from handler, sending converted '{}' state towards item.", state, result);
|
||||
callback.sendUpdate(result);
|
||||
}
|
||||
|
||||
private Type applyGainOffset(Type state, boolean towardsItem) {
|
||||
Type result = UnDefType.UNDEF;
|
||||
Optional<QuantityType<Q>> localGain = gain;
|
||||
Optional<QuantityType<Dimensionless>> localPregainOffset = pregainOffset;
|
||||
if (localGain.isEmpty() || localPregainOffset.isEmpty()) {
|
||||
logger.warn("Gain or pre-gain-offset unavailable. Check logs for configuration errors.");
|
||||
return UnDefType.UNDEF;
|
||||
} else if (state instanceof UnDefType) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
QuantityType<Q> gain = localGain.get();
|
||||
QuantityType<Dimensionless> pregainOffsetQt = localPregainOffset.get();
|
||||
String formula = towardsItem ? String.format("( '%s' + '%s') * '%s'", state, pregainOffsetQt, gain)
|
||||
: String.format("'%s'/'%s' - '%s'", state, gain, pregainOffsetQt);
|
||||
if (state instanceof QuantityType quantityState) {
|
||||
try {
|
||||
if (towardsItem) {
|
||||
@SuppressWarnings("unchecked") // xx.toUnit(ONE) returns null or QuantityType<Dimensionless>
|
||||
@Nullable
|
||||
QuantityType<Dimensionless> qtState = (QuantityType<Dimensionless>) (quantityState
|
||||
.toUnit(Units.ONE));
|
||||
if (qtState == null) {
|
||||
logger.warn("Profile can only process plain numbers from handler. Got unit {}. Returning UNDEF",
|
||||
quantityState.getUnit());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
QuantityType<Dimensionless> offsetted = qtState.add(pregainOffsetQt);
|
||||
result = applyGainTowardsItem(offsetted, gain);
|
||||
} else {
|
||||
result = applyGainTowardsHandler(quantityState, gain).subtract(pregainOffsetQt);
|
||||
}
|
||||
} catch (UnconvertibleException | UnsupportedOperationException e) {
|
||||
logger.warn(
|
||||
"Cannot apply gain ('{}') and pre-gain-offset ('{}') to state ('{}') (formula {}) because types do not match (towardsItem={}): {}",
|
||||
gain, pregainOffsetQt, state, formula, towardsItem, e.getMessage());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
} else if (state instanceof DecimalType decState) {
|
||||
return applyGainOffset(new QuantityType<>(decState, Units.ONE), towardsItem);
|
||||
} else if (state instanceof RefreshType) {
|
||||
result = state;
|
||||
} else {
|
||||
logger.warn(
|
||||
"Gain '{}' cannot be applied to the incompatible state '{}' of type {} sent from the binding (towardsItem={}). Returning original state.",
|
||||
gain, state, state.getClass().getSimpleName(), towardsItem);
|
||||
result = state;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Optional<QuantityType<Q>> parameterAsQuantityType(String parameterName, Object parameterValue) {
|
||||
return parameterAsQuantityType(parameterName, parameterValue, null);
|
||||
}
|
||||
|
||||
private <QU extends Quantity<QU>> Optional<QuantityType<QU>> parameterAsQuantityType(String parameterName,
|
||||
Object parameterValue, @Nullable Unit<QU> assertUnit) {
|
||||
Optional<QuantityType<QU>> result = Optional.empty();
|
||||
Unit<QU> sourceUnit = null;
|
||||
if (parameterValue instanceof String str) {
|
||||
try {
|
||||
QuantityType<QU> qt = new QuantityType<>(str);
|
||||
result = Optional.of(qt);
|
||||
sourceUnit = qt.getUnit();
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Cannot convert value '{}' of parameter '{}' into a QuantityType.", parameterValue,
|
||||
parameterName);
|
||||
}
|
||||
} else if (parameterValue instanceof BigDecimal parameterBigDecimal) {
|
||||
result = Optional.of(new QuantityType<QU>(parameterBigDecimal.toString()));
|
||||
} else {
|
||||
logger.error("Parameter '{}' is not of type String or BigDecimal", parameterName);
|
||||
return result;
|
||||
}
|
||||
result = result.map(quantityType -> convertUnit(quantityType, assertUnit));
|
||||
if (result.isEmpty()) {
|
||||
logger.error("Unable to convert parameter '{}' to unit {}. Unit was {}.", parameterName, assertUnit,
|
||||
sourceUnit);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private <QU extends Quantity<QU>> @Nullable QuantityType<QU> convertUnit(QuantityType<QU> quantityType,
|
||||
@Nullable Unit<QU> unit) {
|
||||
if (unit == null) {
|
||||
return quantityType;
|
||||
}
|
||||
QuantityType<QU> normalizedQt = quantityType.toUnit(unit);
|
||||
if (normalizedQt != null) {
|
||||
return normalizedQt;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate qtState * gain or qtState/gain
|
||||
*
|
||||
* When the conversion is towards the handler (towardsItem=false), unit will be ONE
|
||||
*
|
||||
*/
|
||||
private <QU extends Quantity<QU>> QuantityType<QU> applyGainTowardsItem(QuantityType<Dimensionless> qtState,
|
||||
QuantityType<QU> gainDelta) {
|
||||
return new QuantityType<>(qtState.toBigDecimal().multiply(gainDelta.toBigDecimal()), gainDelta.getUnit());
|
||||
}
|
||||
|
||||
private QuantityType<Dimensionless> applyGainTowardsHandler(QuantityType<?> qtState, QuantityType<?> gainDelta) {
|
||||
QuantityType<?> plain = qtState.toUnit(gainDelta.getUnit());
|
||||
if (plain == null) {
|
||||
throw new UnconvertibleException(
|
||||
String.format("Cannot process command '%s', unit should compatible with gain", qtState));
|
||||
}
|
||||
return new QuantityType<>(plain.toBigDecimal().divide(gainDelta.toBigDecimal()), Units.ONE);
|
||||
}
|
||||
|
||||
private static Object orDefault(Object defaultValue, @Nullable Object value) {
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else if (value instanceof String str && str.isBlank()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.profiles;
|
||||
|
||||
import static org.openhab.binding.sbus.internal.profiles.ModbusProfiles.GAIN_OFFSET;
|
||||
import static org.openhab.binding.sbus.internal.profiles.ModbusProfiles.GAIN_OFFSET_TYPE;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.profiles.Profile;
|
||||
import org.openhab.core.thing.profiles.ProfileCallback;
|
||||
import org.openhab.core.thing.profiles.ProfileContext;
|
||||
import org.openhab.core.thing.profiles.ProfileFactory;
|
||||
import org.openhab.core.thing.profiles.ProfileType;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeProvider;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* A factory and advisor for modbus profiles.
|
||||
*
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
|
||||
public class ModbusProfileFactory implements ProfileFactory, ProfileTypeProvider {
|
||||
|
||||
private static final Set<ProfileType> SUPPORTED_PROFILE_TYPES = Set.of(GAIN_OFFSET_TYPE);
|
||||
|
||||
private static final Set<ProfileTypeUID> SUPPORTED_PROFILE_TYPE_UIDS = Set.of(GAIN_OFFSET);
|
||||
|
||||
@Override
|
||||
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
|
||||
ProfileContext context) {
|
||||
if (GAIN_OFFSET.equals(profileTypeUID)) {
|
||||
return new ModbusGainOffsetProfile<>(callback, context);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
|
||||
return SUPPORTED_PROFILE_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
|
||||
return SUPPORTED_PROFILE_TYPE_UIDS;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.profiles;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeBuilder;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeUID;
|
||||
import org.openhab.core.thing.profiles.StateProfileType;
|
||||
|
||||
/**
|
||||
* Modbus profile constants.
|
||||
*
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ModbusProfiles {
|
||||
static final String MODBUS_SCOPE = "modbus";
|
||||
static final ProfileTypeUID GAIN_OFFSET = new ProfileTypeUID(MODBUS_SCOPE, "gainOffset");
|
||||
static final StateProfileType GAIN_OFFSET_TYPE = ProfileTypeBuilder.newState(GAIN_OFFSET, "Gain-Offset Correction")
|
||||
.build();
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="modbus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>Modbus Binding</name>
|
||||
<description>Binding for Modbus</description>
|
||||
<name>Sbus Binding</name>
|
||||
<description>Binding for Sbus</description>
|
||||
<connection>local</connection>
|
||||
|
||||
</addon:addon>
|
||||
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="sbus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>SBUS Binding</name>
|
||||
<description>Binding for SBUS communication protocol</description>
|
||||
<author>Ciprian Pascu</author>
|
||||
|
||||
</binding:binding>
|
@ -5,7 +5,7 @@
|
||||
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:modbus:gainOffset">
|
||||
<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
|
||||
|
@ -3,186 +3,4 @@
|
||||
addon.sbus.name = S-Bus Binding
|
||||
addon.sbus.description = Binding for S-Bus
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.sbus.data.label = S-Bus Data
|
||||
thing-type.sbus.data.description = Data thing extracts values from binary data received from S-Bus slave. Similarly, it is responsible of translating openHAB commands to S-Bus write requests
|
||||
thing-type.sbus.poller.label = Regular Poll
|
||||
thing-type.sbus.poller.description = Regular poll of data from S-Bus slaves
|
||||
thing-type.sbus.serial.label = S-Bus Serial Slave
|
||||
thing-type.sbus.serial.description = Endpoint for S-Bus serial slaves
|
||||
thing-type.sbus.udp.label = S-Bus UDP Slave
|
||||
thing-type.sbus.udp.description = Endpoint for S-Bus UDP slaves
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.sbus.data.readStart.label = Read Address
|
||||
thing-type.config.sbus.data.readStart.description = Start address to start reading the value. Use empty for write-only things. <br /> <br />Input as zero-based index number, e.g. in place of 400001 (first holding register), use the address 0. Must be between (poller start) and (poller start + poller length - 1) (inclusive). <br /> <br />With registers and value type less than 16 bits, you must use X.Y format where Y specifies the sub-element to read from the 16 bit register: <ul> <li>For example, 3.1 would mean pick second bit from register index 3 with bit value type. </li> <li>With int8 valuetype, it would pick the high byte of register index 3.</li> </ul>
|
||||
thing-type.config.sbus.data.readTransform.label = Read Transform
|
||||
thing-type.config.sbus.data.readTransform.description = Transformation to apply to polled data, after it has been converted to number using readValueType <br /><br />Use "default" to communicate that no transformation is done and value should be passed as is. <br />Use SERVICENAME(ARG) or SERVICENAME:ARG to use transformation service. <br />Any other value than the above types will be interpreted as static text, in which case the actual content of the polled value is ignored. <br />You can chain many transformations with ∩, for example SERVICE1:ARG1∩SERVICE2:ARG2
|
||||
thing-type.config.sbus.data.readValueType.label = Read Value Type
|
||||
thing-type.config.sbus.data.readValueType.description = How data is read from modbus. Use empty for write-only things. <br /><br />With registers all value types are applicable.
|
||||
thing-type.config.sbus.data.readValueType.option.int64 = 64bit signed integer (int64)
|
||||
thing-type.config.sbus.data.readValueType.option.uint64 = 64bit unsigned integer (uint64)
|
||||
thing-type.config.sbus.data.readValueType.option.int64_swap = 64bit signed integer, 16bit words in reverse order (dcba) (int64_swap)
|
||||
thing-type.config.sbus.data.readValueType.option.uint64_swap = 64bit unsigned integer, 16bit words in reverse order (dcba) (uint64_swap)
|
||||
thing-type.config.sbus.data.readValueType.option.float32 = 32bit floating point (float32)
|
||||
thing-type.config.sbus.data.readValueType.option.float32_swap = 32bit floating point, 16bit words swapped (float32_swap)
|
||||
thing-type.config.sbus.data.readValueType.option.int32 = 32bit signed integer (int32)
|
||||
thing-type.config.sbus.data.readValueType.option.uint32 = 32bit unsigned integer (uint32)
|
||||
thing-type.config.sbus.data.readValueType.option.int32_swap = 32bit signed integer, 16bit words swapped (int32_swap)
|
||||
thing-type.config.sbus.data.readValueType.option.uint32_swap = 32bit unsigned integer, 16bit words swapped (uint32_swap)
|
||||
thing-type.config.sbus.data.readValueType.option.int16 = 16bit signed integer (int16)
|
||||
thing-type.config.sbus.data.readValueType.option.uint16 = 16bit unsigned integer (uint16)
|
||||
thing-type.config.sbus.data.readValueType.option.int8 = 8bit signed integer (int8)
|
||||
thing-type.config.sbus.data.readValueType.option.uint8 = 8bit unsigned integer (uint8)
|
||||
thing-type.config.sbus.data.readValueType.option.bit = individual bit (bit)
|
||||
thing-type.config.sbus.data.updateUnchangedValuesEveryMillis.label = Interval for Updating Unchanged Values
|
||||
thing-type.config.sbus.data.updateUnchangedValuesEveryMillis.description = Interval to update unchanged values. Normally unchanged values are not updated. In milliseconds.
|
||||
thing-type.config.sbus.data.writeMaxTries.label = Maximum Tries When Writing
|
||||
thing-type.config.sbus.data.writeMaxTries.description = Number of tries when writing data, if some of the writes fail. For single try, enter 1.
|
||||
thing-type.config.sbus.data.writeMultipleEvenWithSingleRegisterOrCoil.label = Write Multiple Even with Single Register or Coil
|
||||
thing-type.config.sbus.data.writeMultipleEvenWithSingleRegisterOrCoil.description = Whether single register / coil of data is written using FC16 ("Write Multiple Holding Registers") / FC15 ("Write Multiple Coils"), respectively. <br /> <br />If false, FC6/FC5 are used with single register and single coil, respectively.
|
||||
thing-type.config.sbus.data.writeStart.label = Write Address
|
||||
thing-type.config.sbus.data.writeStart.description = Start address of the first holding register or coil in the write. Use empty for read-only things. <br />Use zero based address, e.g. in place of 400001 (first holding register), use the address 0. This address is passed to data frame as is. <br />One can write individual bits of a register using X.Y format where X is the register and Y is the bit (0 refers to least significant bit).
|
||||
thing-type.config.sbus.data.writeTransform.label = Write Transform
|
||||
thing-type.config.sbus.data.writeTransform.description = Transformation to apply to received commands. <br /><br />Use "default" to communicate that no transformation is done and value should be passed as is. <br />Use SERVICENAME(ARG) or SERVICENAME:ARG to use transformation service. <br />Any other value than the above types will be interpreted as static text, in which case the actual content of the command <br />You can chain many transformations with ∩, for example SERVICE1:ARG1∩SERVICE2:ARG2 value is ignored.
|
||||
thing-type.config.sbus.data.writeType.label = Write Type
|
||||
thing-type.config.sbus.data.writeType.description = Type of data to write. Leave empty for read-only things. <br /> <br /> Coil uses function code (FC) FC05 or FC15. Holding register uses FC06 or FC16. See writeMultipleEvenWithSingleRegisterOrCoil parameter.
|
||||
thing-type.config.sbus.data.writeType.option.coil = coil, or digital out (DO)
|
||||
thing-type.config.sbus.data.writeType.option.holding = holding register
|
||||
thing-type.config.sbus.data.writeValueType.label = Write Value Type
|
||||
thing-type.config.sbus.data.writeValueType.description = How data is written to modbus. Only applicable to registers, you can leave this undefined for coil. <br /><br />Negative integers are encoded with two's complement, while positive integers are encoded as is.
|
||||
thing-type.config.sbus.data.writeValueType.option.int64 = 64bit positive or negative integer, 4 registers (int64, uint64)
|
||||
thing-type.config.sbus.data.writeValueType.option.int64_swap = 64bit positive or negative integer, 4 registers but with 16bit words/registers in reverse order (dcba) (int64_swap, uint64_swap)
|
||||
thing-type.config.sbus.data.writeValueType.option.float32 = 32bit floating point (float32)
|
||||
thing-type.config.sbus.data.writeValueType.option.float32_swap = 32bit floating point, 16bit words swapped (float32_swap)
|
||||
thing-type.config.sbus.data.writeValueType.option.int32 = 32bit positive or negative integer, 2 registers (int32, uint32)
|
||||
thing-type.config.sbus.data.writeValueType.option.int32_swap = 32bit positive or negative integer, 2 registers but with 16bit words/registers in reverse order (ba) (int32_swap, uint32_swap)
|
||||
thing-type.config.sbus.data.writeValueType.option.int16 = 16bit positive or negative integer, 1 register (int16, uint16)
|
||||
thing-type.config.sbus.data.writeValueType.option.bit = individual bit (bit)
|
||||
thing-type.config.sbus.poller.cacheMillis.label = Cache Duration
|
||||
thing-type.config.sbus.poller.cacheMillis.description = Duration for data cache to be valid, in milliseconds. This cache is used only to serve REFRESH commands. <br /> <br />Use zero to disable the caching.
|
||||
thing-type.config.sbus.poller.length.label = Length
|
||||
thing-type.config.sbus.poller.length.description = Number of registers, coils or discrete inputs to read. <br /> <br />Maximum number of registers is 125 while 2000 is maximum for coils and discrete inputs.
|
||||
thing-type.config.sbus.poller.maxTries.label = Maximum Tries When Reading
|
||||
thing-type.config.sbus.poller.maxTries.description = Number of tries when reading data, if some of the reading fail. For single try, enter 1.
|
||||
thing-type.config.sbus.poller.refresh.label = Poll Interval
|
||||
thing-type.config.sbus.poller.refresh.description = Poll interval in milliseconds. Use zero to disable automatic polling.
|
||||
thing-type.config.sbus.poller.start.label = Start
|
||||
thing-type.config.sbus.poller.start.description = Address of the first register, coil, or discrete input to poll. <br /> <br />Input as zero-based index number, e.g. in place of 400001 (first holding register), use the address 0.
|
||||
thing-type.config.sbus.poller.type.label = Type
|
||||
thing-type.config.sbus.poller.type.description = Type of modbus items to poll
|
||||
thing-type.config.sbus.poller.type.option.coil = coil, or digital out (DO)
|
||||
thing-type.config.sbus.poller.type.option.discrete = discrete input, or digital in (DI)
|
||||
thing-type.config.sbus.poller.type.option.holding = holding register
|
||||
thing-type.config.sbus.poller.type.option.input = input register
|
||||
thing-type.config.sbus.serial.afterConnectionDelayMillis.label = Connection warm-up time
|
||||
thing-type.config.sbus.serial.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 modbus call. In milliseconds.
|
||||
thing-type.config.sbus.serial.baud.label = Baud
|
||||
thing-type.config.sbus.serial.baud.description = Baud of the connection
|
||||
thing-type.config.sbus.serial.baud.option.75 = 75
|
||||
thing-type.config.sbus.serial.baud.option.110 = 110
|
||||
thing-type.config.sbus.serial.baud.option.300 = 300
|
||||
thing-type.config.sbus.serial.baud.option.1200 = 1200
|
||||
thing-type.config.sbus.serial.baud.option.2400 = 2400
|
||||
thing-type.config.sbus.serial.baud.option.4800 = 4800
|
||||
thing-type.config.sbus.serial.baud.option.9600 = 9600
|
||||
thing-type.config.sbus.serial.baud.option.19200 = 19200
|
||||
thing-type.config.sbus.serial.baud.option.38400 = 38400
|
||||
thing-type.config.sbus.serial.baud.option.57600 = 57600
|
||||
thing-type.config.sbus.serial.baud.option.115200 = 115200
|
||||
thing-type.config.sbus.serial.connectMaxTries.label = Maximum Connection Tries
|
||||
thing-type.config.sbus.serial.connectMaxTries.description = How many times we try to establish the connection. Should be at least 1.
|
||||
thing-type.config.sbus.serial.connectTimeoutMillis.label = Timeout for Establishing the Connection
|
||||
thing-type.config.sbus.serial.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.serial.dataBits.label = Data Bits
|
||||
thing-type.config.sbus.serial.dataBits.description = Data bits
|
||||
thing-type.config.sbus.serial.dataBits.option.5 = 5
|
||||
thing-type.config.sbus.serial.dataBits.option.6 = 6
|
||||
thing-type.config.sbus.serial.dataBits.option.7 = 7
|
||||
thing-type.config.sbus.serial.dataBits.option.8 = 8
|
||||
thing-type.config.sbus.serial.echo.label = RS485 Echo Mode
|
||||
thing-type.config.sbus.serial.echo.description = Flag for setting the RS485 echo mode <br/> <br/>This controls whether we should try to read back whatever we send on the line, before reading the response.
|
||||
thing-type.config.sbus.serial.enableDiscovery.label = Discovery Enabled
|
||||
thing-type.config.sbus.serial.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.serial.encoding.label = Encoding
|
||||
thing-type.config.sbus.serial.encoding.description = Encoding
|
||||
thing-type.config.sbus.serial.encoding.option.ascii = ASCII
|
||||
thing-type.config.sbus.serial.encoding.option.rtu = RTU
|
||||
thing-type.config.sbus.serial.encoding.option.bin = BIN
|
||||
thing-type.config.sbus.serial.flowControlIn.label = Flow Control In
|
||||
thing-type.config.sbus.serial.flowControlIn.description = Type of flow control for receiving
|
||||
thing-type.config.sbus.serial.flowControlIn.option.none = None
|
||||
thing-type.config.sbus.serial.flowControlIn.option.xon/xoff in = XON/XOFF
|
||||
thing-type.config.sbus.serial.flowControlIn.option.rts/cts in = RTS/CTS
|
||||
thing-type.config.sbus.serial.flowControlOut.label = Flow Control Out
|
||||
thing-type.config.sbus.serial.flowControlOut.description = Type of flow control for sending
|
||||
thing-type.config.sbus.serial.flowControlOut.option.none = None
|
||||
thing-type.config.sbus.serial.flowControlOut.option.xon/xoff out = XON/XOFF
|
||||
thing-type.config.sbus.serial.flowControlOut.option.rts/cts out = RTS/CTS
|
||||
thing-type.config.sbus.serial.id.label = Id
|
||||
thing-type.config.sbus.serial.id.description = Slave id. Also known as station address or unit identifier.
|
||||
thing-type.config.sbus.serial.parity.label = Parity
|
||||
thing-type.config.sbus.serial.parity.description = Parity
|
||||
thing-type.config.sbus.serial.parity.option.none = None
|
||||
thing-type.config.sbus.serial.parity.option.even = Even
|
||||
thing-type.config.sbus.serial.parity.option.odd = Odd
|
||||
thing-type.config.sbus.serial.port.label = Serial Port
|
||||
thing-type.config.sbus.serial.port.description = Serial port to use, for example /dev/ttyS0 or COM1
|
||||
thing-type.config.sbus.serial.receiveTimeoutMillis.label = Read Operation Timeout
|
||||
thing-type.config.sbus.serial.receiveTimeoutMillis.description = Timeout for read operations. In milliseconds.
|
||||
thing-type.config.sbus.serial.stopBits.label = Stop Bits
|
||||
thing-type.config.sbus.serial.stopBits.description = Stop bits
|
||||
thing-type.config.sbus.serial.stopBits.option.1.0 = 1
|
||||
thing-type.config.sbus.serial.stopBits.option.1.5 = 1.5
|
||||
thing-type.config.sbus.serial.stopBits.option.2.0 = 2
|
||||
thing-type.config.sbus.serial.timeBetweenTransactionsMillis.label = Time Between Transactions
|
||||
thing-type.config.sbus.serial.timeBetweenTransactionsMillis.description = How long to delay we must have at minimum between two consecutive MODBUS transactions. In milliseconds.
|
||||
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 modbus 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.id.label = Id
|
||||
thing-type.config.sbus.udp.id.description = Slave id. Also known as station address or unit identifier.
|
||||
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 MODBUS 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 MODBUS transactions. In milliseconds.
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.sbus.contact-type.label = Value as Contact
|
||||
channel-type.sbus.contact-type.description = Contact item channel
|
||||
channel-type.sbus.datetime-type.label = Value as DateTime
|
||||
channel-type.sbus.datetime-type.description = DateTime item channel
|
||||
channel-type.sbus.dimmer-type.label = Value as Dimmer
|
||||
channel-type.sbus.dimmer-type.description = Dimmer item channel
|
||||
channel-type.sbus.last-erroring-read-type.label = Last Erroring Read
|
||||
channel-type.sbus.last-erroring-read-type.description = Date of last read error
|
||||
channel-type.sbus.last-erroring-write-type.label = Last Erroring Write
|
||||
channel-type.sbus.last-erroring-write-type.description = Date of last write error
|
||||
channel-type.sbus.last-successful-read-type.label = Last Successful Read
|
||||
channel-type.sbus.last-successful-read-type.description = Date of last read
|
||||
channel-type.sbus.last-successful-write-type.label = Last Successful Write
|
||||
channel-type.sbus.last-successful-write-type.description = Date of last write
|
||||
channel-type.sbus.number-type.label = Value as Number
|
||||
channel-type.sbus.number-type.description = Number item channel
|
||||
channel-type.sbus.rollershutter-type.label = Value as Rollershutter
|
||||
channel-type.sbus.rollershutter-type.description = Rollershutter item channel
|
||||
channel-type.sbus.string-type.label = Value as String
|
||||
channel-type.sbus.string-type.description = String item channel
|
||||
channel-type.sbus.switch-type.label = Value as Switch
|
||||
channel-type.sbus.switch-type.description = Switch item channel
|
||||
|
@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="modbus"
|
||||
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="poller">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="udp"/>
|
||||
<bridge-type-ref id="serial"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Regular Poll</label>
|
||||
<description>Regular poll of data from Modbus slaves</description>
|
||||
<config-description>
|
||||
<parameter name="refresh" type="integer" min="0" unit="ms">
|
||||
<label>Poll Interval</label>
|
||||
<description>Poll interval in milliseconds. Use zero to disable automatic polling.</description>
|
||||
<default>500</default>
|
||||
</parameter>
|
||||
<parameter name="start" type="integer">
|
||||
<label>Start</label>
|
||||
<description><![CDATA[Address of the first register, coil, or discrete input to poll.
|
||||
<br />
|
||||
<br />Input as zero-based index number, e.g. in place of 400001 (first holding register), use the address 0.]]></description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="length" type="integer" required="true">
|
||||
<label>Length</label>
|
||||
<description><![CDATA[Number of registers, coils or discrete inputs to read.
|
||||
<br />
|
||||
<br />Maximum number of registers is 125 while 2000 is maximum for coils and discrete inputs.]]></description>
|
||||
</parameter>
|
||||
<parameter name="type" type="text" required="true">
|
||||
<label>Type</label>
|
||||
<description>Type of modbus items to poll</description>
|
||||
<options>
|
||||
<option value="coil">coil, or digital out (DO)</option>
|
||||
<option value="discrete">discrete input, or digital in (DI)</option>
|
||||
<option value="holding">holding register</option>
|
||||
<option value="input">input register</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="maxTries" type="integer" min="1">
|
||||
<label>Maximum Tries When Reading</label>
|
||||
<default>3</default>
|
||||
<description>Number of tries when reading data, if some of the reading fail. For single try, enter 1.</description>
|
||||
</parameter>
|
||||
<parameter name="cacheMillis" type="integer" min="0" unit="ms">
|
||||
<label>Cache Duration</label>
|
||||
<default>50</default>
|
||||
<description><![CDATA[Duration for data cache to be valid, in milliseconds. This cache is used only to serve REFRESH commands.
|
||||
<br />
|
||||
<br />Use zero to disable the caching.]]></description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
@ -1,155 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="modbus"
|
||||
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="serial">
|
||||
<label>Modbus Serial Slave</label>
|
||||
<description>Endpoint for Modbus serial slaves</description>
|
||||
<config-description>
|
||||
<parameter name="port" type="text" required="true">
|
||||
<label>Serial Port</label>
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<description>Serial port to use, for example /dev/ttyS0 or COM1</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="id" type="integer">
|
||||
<label>Id</label>
|
||||
<description>Slave id. Also known as station address or unit identifier.</description>
|
||||
<default>1</default>
|
||||
</parameter>
|
||||
|
||||
<!-- serial parameters -->
|
||||
<parameter name="baud" type="integer" multiple="false">
|
||||
<label>Baud</label>
|
||||
<description>Baud of the connection</description>
|
||||
<default>9600</default>
|
||||
<options>
|
||||
<option value="75">75</option>
|
||||
<option value="110">110</option>
|
||||
<option value="300">300</option>
|
||||
<option value="1200">1200</option>
|
||||
<option value="2400">2400</option>
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600">9600</option>
|
||||
<option value="19200">19200</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="57600">57600</option>
|
||||
<option value="115200">115200</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="stopBits" type="text" multiple="false">
|
||||
<label>Stop Bits</label>
|
||||
<description>Stop bits</description>
|
||||
<default>1.0</default>
|
||||
<options>
|
||||
<option value="1.0">1</option>
|
||||
<option value="1.5">1.5</option>
|
||||
<option value="2.0">2</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="parity" type="text" multiple="false">
|
||||
<label>Parity</label>
|
||||
<description>Parity</description>
|
||||
<default>none</default>
|
||||
<options>
|
||||
<option value="none">None</option>
|
||||
<option value="even">Even</option>
|
||||
<option value="odd">Odd</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="dataBits" type="integer" multiple="false">
|
||||
<label>Data Bits</label>
|
||||
<description>Data bits</description>
|
||||
<default>8</default>
|
||||
<options>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="encoding" type="text" multiple="false">
|
||||
<label>Encoding</label>
|
||||
<description>Encoding</description>
|
||||
<default>rtu</default>
|
||||
<options>
|
||||
<option value="ascii">ASCII</option>
|
||||
<option value="rtu">RTU</option>
|
||||
<option value="bin">BIN</option>
|
||||
</options>
|
||||
</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="echo" type="boolean">
|
||||
<label>RS485 Echo Mode</label>
|
||||
<description><![CDATA[Flag for setting the RS485 echo mode
|
||||
<br/>
|
||||
<br/>This controls whether we should try to read back whatever we send on the line, before reading the response.
|
||||
]]></description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="receiveTimeoutMillis" type="integer" min="0" unit="ms">
|
||||
<label>Read Operation Timeout</label>
|
||||
<description>Timeout for read operations. In milliseconds.</description>
|
||||
<default>1500</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="flowControlIn" type="text" multiple="false">
|
||||
<label>Flow Control In</label>
|
||||
<description>Type of flow control for receiving</description>
|
||||
<default>none</default>
|
||||
<!-- values here match SerialPort.FLOWCONTROL_* constants -->
|
||||
<options>
|
||||
<option value="none">None</option>
|
||||
<option value="xon/xoff in">XON/XOFF</option>
|
||||
<option value="rts/cts in">RTS/CTS</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="flowControlOut" type="text" multiple="false">
|
||||
<label>Flow Control Out</label>
|
||||
<description>Type of flow control for sending</description>
|
||||
<default>none</default>
|
||||
<!-- values here match SerialPort.FLOWCONTROL_* constants -->
|
||||
<options>
|
||||
<option value="none">None</option>
|
||||
<option value="xon/xoff out">XON/XOFF</option>
|
||||
<option value="rts/cts out">RTS/CTS</option>
|
||||
</options>
|
||||
</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 MODBUS transactions. In milliseconds.</description>
|
||||
<default>35</default>
|
||||
</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 modbus call. 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>
|
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="modbus"
|
||||
<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>Modbus UDP Slave</label>
|
||||
<description>Endpoint for Modbus UDP slaves</description>
|
||||
<label>Sbus UDP Slave</label>
|
||||
<description>Endpoint for Sbus UDP slaves</description>
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>IP Address or Hostname</label>
|
||||
@ -19,17 +19,11 @@
|
||||
<default>6000</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="subnetId" type="integer">
|
||||
<label>SubnetId</label>
|
||||
<description>Slave subnet id. Can take any value between 0 and 255.</description>
|
||||
<default>1</default>
|
||||
<parameter name="refresh" type="integer" required="false" min="1">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Refresh interval in seconds</description>
|
||||
<default>30</default>
|
||||
</parameter>
|
||||
<parameter name="id" type="integer">
|
||||
<label>Id</label>
|
||||
<description>Slave id. Also known as station address or unit identifier.</description>
|
||||
<default>1</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
|
||||
@ -46,7 +40,7 @@
|
||||
<!-- 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 MODBUS transactions. In milliseconds.
|
||||
<description>How long to delay we must have at minimum between two consecutive SBUS transactions. In milliseconds.
|
||||
</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
@ -66,14 +60,14 @@
|
||||
<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 modbus call. In milliseconds.</description>
|
||||
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 MODBUS transaction. In milliseconds.</description>
|
||||
disconnected after every SBUS transaction. In milliseconds.</description>
|
||||
<default>0</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
@ -1,150 +0,0 @@
|
||||
<thing:thing-descriptions bindingId="modbus"
|
||||
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">
|
||||
<thing-type id="data">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="poller"/>
|
||||
<bridge-type-ref id="udp"/>
|
||||
<bridge-type-ref id="serial"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Modbus Data</label>
|
||||
<description>Data thing extracts values from binary data received from Modbus slave. Similarly, it is responsible of
|
||||
translating openHAB commands to Modbus write requests</description>
|
||||
<channels>
|
||||
<channel id="number" typeId="number-type"/>
|
||||
<channel id="switch" typeId="switch-type"/>
|
||||
<channel id="contact" typeId="contact-type"/>
|
||||
<channel id="dimmer" typeId="dimmer-type"/>
|
||||
<channel id="datetime" typeId="datetime-type"/>
|
||||
<channel id="string" typeId="string-type"/>
|
||||
<channel id="rollershutter" typeId="rollershutter-type"/>
|
||||
<channel id="lastReadSuccess" typeId="last-successful-read-type"/>
|
||||
<channel id="lastReadError" typeId="last-erroring-read-type"/>
|
||||
<channel id="lastWriteSuccess" typeId="last-successful-write-type"/>
|
||||
<channel id="lastWriteError" typeId="last-erroring-write-type"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
<!-- what to read -->
|
||||
<parameter name="readStart" type="text" pattern="^(0|[0-9][0-9]*(\.[0-9]{1,2})?)?$">
|
||||
<label>Read Address</label>
|
||||
<description><![CDATA[Start address to start reading the value. Use empty for write-only things.
|
||||
<br />
|
||||
<br />Input as zero-based index number, e.g. in place of 400001 (first holding register), use the address 0. Must be between (poller start) and (poller start + poller length - 1) (inclusive).
|
||||
<br />
|
||||
<br />With registers and value type less than 16 bits, you must use X.Y format where Y specifies the sub-element to read from the 16 bit register:
|
||||
<ul>
|
||||
<li>For example, 3.1 would mean pick second bit from register index 3 with bit value type. </li>
|
||||
<li>With int8 valuetype, it would pick the high byte of register index 3.</li>
|
||||
</ul>
|
||||
]]>
|
||||
</description>
|
||||
</parameter>
|
||||
<parameter name="readTransform" type="text">
|
||||
<label>Read Transform</label>
|
||||
<description><![CDATA[Transformation to apply to polled data, after it has been converted to number using readValueType
|
||||
<br /><br />Use "default" to communicate that no transformation is done and value should be passed as is.
|
||||
<br />Use SERVICENAME(ARG) or SERVICENAME:ARG to use transformation service.
|
||||
<br />Any other value than the above types will be interpreted as static text, in which case the actual content of the polled
|
||||
value is ignored.
|
||||
<br />You can chain many transformations with ∩, for example SERVICE1:ARG1∩SERVICE2:ARG2]]></description>
|
||||
<default>default</default>
|
||||
</parameter>
|
||||
<parameter name="readValueType" type="text">
|
||||
<label>Read Value Type</label>
|
||||
<description><![CDATA[How data is read from modbus. Use empty for write-only things.
|
||||
|
||||
<br /><br />With registers all value types are applicable.]]></description>
|
||||
<options>
|
||||
<option value="int64">64bit signed integer (int64)</option>
|
||||
<option value="uint64">64bit unsigned integer (uint64)</option>
|
||||
<option value="int64_swap">64bit signed integer, 16bit words in reverse order (dcba) (int64_swap)</option>
|
||||
<option value="uint64_swap">64bit unsigned integer, 16bit words in reverse order (dcba) (uint64_swap)</option>
|
||||
|
||||
<option value="float32">32bit floating point (float32)</option>
|
||||
<option value="float32_swap">32bit floating point, 16bit words swapped (float32_swap)</option>
|
||||
|
||||
<option value="int32">32bit signed integer (int32)</option>
|
||||
<option value="uint32">32bit unsigned integer (uint32)</option>
|
||||
<option value="int32_swap">32bit signed integer, 16bit words swapped (int32_swap)</option>
|
||||
<option value="uint32_swap">32bit unsigned integer, 16bit words swapped (uint32_swap)</option>
|
||||
|
||||
<option value="int16">16bit signed integer (int16)</option>
|
||||
<option value="uint16">16bit unsigned integer (uint16)</option>
|
||||
|
||||
<option value="int8">8bit signed integer (int8)</option>
|
||||
<option value="uint8">8bit unsigned integer (uint8)</option>
|
||||
|
||||
<option value="bit">individual bit (bit)</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="writeStart" type="text">
|
||||
<label>Write Address</label>
|
||||
<description><![CDATA[Start address of the first holding register or coil in the write. Use empty for read-only things.
|
||||
<br />Use zero based address, e.g. in place of 400001 (first holding register), use the address 0. This address is passed to data frame as is.
|
||||
<br />One can write individual bits of a register using X.Y format where X is the register and Y is the bit (0 refers to least significant bit).
|
||||
]]></description>
|
||||
</parameter>
|
||||
<parameter name="writeType" type="text">
|
||||
<label>Write Type</label>
|
||||
<description><![CDATA[Type of data to write. Leave empty for read-only things.
|
||||
<br />
|
||||
<br />
|
||||
Coil uses function code (FC) FC05 or FC15. Holding register uses FC06 or FC16. See writeMultipleEvenWithSingleRegisterOrCoil parameter.]]></description>
|
||||
<options>
|
||||
<option value="coil">coil, or digital out (DO)</option>
|
||||
<option value="holding">holding register</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="writeTransform" type="text">
|
||||
<label>Write Transform</label>
|
||||
<description><![CDATA[Transformation to apply to received commands.
|
||||
<br /><br />Use "default" to communicate that no transformation is done and value should be passed as is.
|
||||
<br />Use SERVICENAME(ARG) or SERVICENAME:ARG to use transformation service.
|
||||
<br />Any other value than the above types will be interpreted as static text, in which case the actual content of the command
|
||||
<br />You can chain many transformations with ∩, for example SERVICE1:ARG1∩SERVICE2:ARG2
|
||||
value is ignored.]]></description>
|
||||
<default>default</default>
|
||||
</parameter>
|
||||
<parameter name="writeValueType" type="text">
|
||||
<label>Write Value Type</label>
|
||||
<description><![CDATA[How data is written to modbus. Only applicable to registers, you can leave this undefined for coil.
|
||||
<br /><br />Negative integers are encoded with two's complement, while positive integers are encoded as is.
|
||||
]]>
|
||||
</description>
|
||||
<options>
|
||||
<option value="int64">64bit positive or negative integer, 4 registers (int64, uint64)</option>
|
||||
<option value="int64_swap">64bit positive or negative integer, 4 registers but with 16bit words/registers in reverse
|
||||
order (dcba)
|
||||
(int64_swap, uint64_swap)</option>
|
||||
<option value="float32">32bit floating point (float32)</option>
|
||||
<option value="float32_swap">32bit floating point, 16bit words swapped (float32_swap)</option>
|
||||
<option value="int32">32bit positive or negative integer, 2 registers (int32, uint32)</option>
|
||||
<option value="int32_swap">32bit positive or negative integer, 2 registers but with 16bit words/registers in reverse
|
||||
order (ba)
|
||||
(int32_swap, uint32_swap)</option>
|
||||
<option value="int16">16bit positive or negative integer, 1 register (int16, uint16)</option>
|
||||
<option value="bit">individual bit (bit)</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="writeMultipleEvenWithSingleRegisterOrCoil" type="boolean">
|
||||
<label>Write Multiple Even with Single Register or Coil</label>
|
||||
<default>false</default>
|
||||
<description><![CDATA[Whether single register / coil of data is written using FC16 ("Write Multiple Holding Registers") / FC15 ("Write Multiple Coils"), respectively.
|
||||
<br />
|
||||
<br />If false, FC6/FC5 are used with single register and single coil, respectively.]]></description>
|
||||
</parameter>
|
||||
<parameter name="writeMaxTries" type="integer" min="1">
|
||||
<label>Maximum Tries When Writing</label>
|
||||
<default>3</default>
|
||||
<description>Number of tries when writing data, if some of the writes fail. For single try, enter 1.</description>
|
||||
</parameter>
|
||||
<parameter name="updateUnchangedValuesEveryMillis" type="integer" min="0" unit="ms">
|
||||
<label>Interval for Updating Unchanged Values</label>
|
||||
<default>1000</default>
|
||||
<description>Interval to update unchanged values. Normally unchanged values are not updated. In milliseconds.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
@ -1,66 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="modbus"
|
||||
<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">
|
||||
<channel-type id="switch-type">
|
||||
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 0 and 255.</description>
|
||||
<default>1</default>
|
||||
</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 milliseconds</description>
|
||||
<default>30000</default>
|
||||
</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 0 and 255.</description>
|
||||
<default>1</default>
|
||||
</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 milliseconds</description>
|
||||
<default>30000</default>
|
||||
</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 0 and 255.</description>
|
||||
<default>1</default>
|
||||
</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 milliseconds</description>
|
||||
<default>30000</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- Channel Types -->
|
||||
<channel-type id="switch-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Value as Switch</label>
|
||||
<description>Switch item channel</description>
|
||||
<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="contact-type">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Value as Contact</label>
|
||||
<description>Contact item channel</description>
|
||||
|
||||
<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 °C"/>
|
||||
<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="datetime-type">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Value as DateTime</label>
|
||||
<description>DateTime item channel</description>
|
||||
</channel-type>
|
||||
<channel-type id="dimmer-type">
|
||||
|
||||
<channel-type id="color-channel">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Value as Dimmer</label>
|
||||
<description>Dimmer item channel</description>
|
||||
</channel-type>
|
||||
<channel-type id="rollershutter-type">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Value as Rollershutter</label>
|
||||
<description>Rollershutter item channel</description>
|
||||
</channel-type>
|
||||
<channel-type id="string-type">
|
||||
<item-type>String</item-type>
|
||||
<label>Value as String</label>
|
||||
<description>String item channel</description>
|
||||
</channel-type>
|
||||
<channel-type id="number-type">
|
||||
<item-type>Number</item-type>
|
||||
<label>Value as Number</label>
|
||||
<description>Number item channel</description>
|
||||
<config-description></config-description>
|
||||
</channel-type>
|
||||
<channel-type id="last-successful-read-type">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Successful Read</label>
|
||||
<description>Date of last read</description>
|
||||
<config-description></config-description>
|
||||
</channel-type>
|
||||
<channel-type id="last-erroring-read-type">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Erroring Read</label>
|
||||
<description>Date of last read error</description>
|
||||
<config-description></config-description>
|
||||
</channel-type>
|
||||
<channel-type id="last-successful-write-type">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Successful Write</label>
|
||||
<description>Date of last write</description>
|
||||
<config-description></config-description>
|
||||
</channel-type>
|
||||
<channel-type id="last-erroring-write-type">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Erroring Write</label>
|
||||
<description>Date of last write error</description>
|
||||
<config-description></config-description>
|
||||
<label>Color Channel</label>
|
||||
<description>Color intensity (0-100%)</description>
|
||||
<category>ColorLight</category>
|
||||
<state min="0" max="100" step="1" pattern="%d %%"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Channel Group Types (unused, but left in case you need them) -->
|
||||
<channel-group-type id="switches">
|
||||
<label>Switch Channels</label>
|
||||
<description>Group of switch channels</description>
|
||||
<category>Switch</category>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="sensors">
|
||||
<label>Temperature Sensors</label>
|
||||
<description>Group of temperature sensors</description>
|
||||
<category>Temperature</category>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="colors">
|
||||
<label>Color Channels</label>
|
||||
<description>Group of RGBW color channels</description>
|
||||
<category>ColorLight</category>
|
||||
</channel-group-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
@ -1,172 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AtomicStampedKeyValueTest {
|
||||
|
||||
@Test
|
||||
public void testInitWithNullValue() {
|
||||
assertThrows(NullPointerException.class, () -> new AtomicStampedValue<>(0, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetters() {
|
||||
Object val = new Object();
|
||||
AtomicStampedValue<Object> keyValue = new AtomicStampedValue<>(42L, val);
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWithSameStamp() {
|
||||
Object val = new Object();
|
||||
AtomicStampedValue<Object> keyValue = new AtomicStampedValue<>(42L, val);
|
||||
keyValue.update(42L, new Object());
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(not(equalTo(val))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWithDifferentStamp() {
|
||||
Object val = new Object();
|
||||
AtomicStampedValue<Object> keyValue = new AtomicStampedValue<>(42L, val);
|
||||
keyValue.update(-99L, new Object());
|
||||
assertThat(keyValue.getStamp(), is(equalTo(-99L)));
|
||||
assertThat(keyValue.getValue(), is(not(equalTo(val))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopy() {
|
||||
Object val = new Object();
|
||||
AtomicStampedValue<Object> keyValue = new AtomicStampedValue<>(42L, val);
|
||||
AtomicStampedValue<Object> copy = keyValue.copy();
|
||||
|
||||
// unchanged
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
|
||||
// data matches
|
||||
assertThat(keyValue.getStamp(), is(equalTo(copy.getStamp())));
|
||||
assertThat(keyValue.getValue(), is(equalTo(copy.getValue())));
|
||||
|
||||
// after update they live life of their own
|
||||
Object val2 = new Object();
|
||||
copy.update(-99L, val2);
|
||||
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
|
||||
assertThat(copy.getStamp(), is(equalTo(-99L)));
|
||||
assertThat(copy.getValue(), is(equalTo(val2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* instance(stamp=x).copyIfStampAfter(x)
|
||||
*/
|
||||
@Test
|
||||
public void testCopyIfStampAfterEqual() {
|
||||
Object val = new Object();
|
||||
AtomicStampedValue<Object> keyValue = new AtomicStampedValue<>(42L, val);
|
||||
AtomicStampedValue<Object> copy = keyValue.copyIfStampAfter(42L);
|
||||
|
||||
// keyValue unchanged
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
|
||||
// data matches
|
||||
assertThat(keyValue.getStamp(), is(equalTo(copy.getStamp())));
|
||||
assertThat(keyValue.getValue(), is(equalTo(copy.getValue())));
|
||||
|
||||
// after update they live life of their own
|
||||
Object val2 = new Object();
|
||||
copy.update(-99L, val2);
|
||||
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
|
||||
assertThat(copy.getStamp(), is(equalTo(-99L)));
|
||||
assertThat(copy.getValue(), is(equalTo(val2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* instance(stamp=x-1).copyIfStampAfter(x)
|
||||
*/
|
||||
@Test
|
||||
public void testCopyIfStampAfterTooOld() {
|
||||
Object val = new Object();
|
||||
AtomicStampedValue<Object> keyValue = new AtomicStampedValue<>(42L, val);
|
||||
AtomicStampedValue<Object> copy = keyValue.copyIfStampAfter(43L);
|
||||
|
||||
// keyValue unchanged
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
|
||||
// copy is null
|
||||
assertThat(copy, is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* instance(stamp=x).copyIfStampAfter(x-1)
|
||||
*/
|
||||
@Test
|
||||
public void testCopyIfStampAfterFresh() {
|
||||
Object val = new Object();
|
||||
AtomicStampedValue<Object> keyValue = new AtomicStampedValue<>(42L, val);
|
||||
AtomicStampedValue<Object> copy = keyValue.copyIfStampAfter(41L);
|
||||
|
||||
// keyValue unchanged
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
|
||||
// data matches
|
||||
assertThat(keyValue.getStamp(), is(equalTo(copy.getStamp())));
|
||||
assertThat(keyValue.getValue(), is(equalTo(copy.getValue())));
|
||||
|
||||
// after update they live life of their own
|
||||
Object val2 = new Object();
|
||||
copy.update(-99L, val2);
|
||||
|
||||
assertThat(keyValue.getStamp(), is(equalTo(42L)));
|
||||
assertThat(keyValue.getValue(), is(equalTo(val)));
|
||||
|
||||
assertThat(copy.getStamp(), is(equalTo(-99L)));
|
||||
assertThat(copy.getValue(), is(equalTo(val2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompare() {
|
||||
// equal, smaller, larger
|
||||
assertThat(AtomicStampedValue.compare(new AtomicStampedValue<>(42L, ""), new AtomicStampedValue<>(42L, "")),
|
||||
is(equalTo(0)));
|
||||
assertThat(AtomicStampedValue.compare(new AtomicStampedValue<>(41L, ""), new AtomicStampedValue<>(42L, "")),
|
||||
is(equalTo(-1)));
|
||||
assertThat(AtomicStampedValue.compare(new AtomicStampedValue<>(42L, ""), new AtomicStampedValue<>(41L, "")),
|
||||
is(equalTo(1)));
|
||||
|
||||
// Nulls come first
|
||||
assertThat(AtomicStampedValue.compare(null, new AtomicStampedValue<>(42L, "")), is(equalTo(-1)));
|
||||
assertThat(AtomicStampedValue.compare(new AtomicStampedValue<>(42L, ""), null), is(equalTo(1)));
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
/**
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CascadedValueTransformationImplTest {
|
||||
|
||||
@Test
|
||||
public void testTransformation() {
|
||||
CascadedValueTransformationImpl transformation = new CascadedValueTransformationImpl(
|
||||
"REGEX(myregex:foo(.*))∩REG_(EX(myregex:foo(.*))∩JIHAA:test");
|
||||
assertEquals(3, transformation.getTransformations().size());
|
||||
assertEquals("REGEX", transformation.getTransformations().get(0).transformationServiceName);
|
||||
assertEquals("myregex:foo(.*)", transformation.getTransformations().get(0).transformationServiceParam);
|
||||
|
||||
assertEquals("REG_", transformation.getTransformations().get(1).transformationServiceName);
|
||||
assertEquals("EX(myregex:foo(.*)", transformation.getTransformations().get(1).transformationServiceParam);
|
||||
|
||||
assertEquals("JIHAA", transformation.getTransformations().get(2).transformationServiceName);
|
||||
assertEquals("test", transformation.getTransformations().get(2).transformationServiceParam);
|
||||
|
||||
assertEquals(3, transformation.toString().split("∩").length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationEmpty() {
|
||||
CascadedValueTransformationImpl transformation = new CascadedValueTransformationImpl("");
|
||||
assertFalse(transformation.isIdentityTransform());
|
||||
assertEquals("", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationNull() {
|
||||
CascadedValueTransformationImpl transformation = new CascadedValueTransformationImpl(null);
|
||||
assertFalse(transformation.isIdentityTransform());
|
||||
assertEquals("", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationDefault() {
|
||||
CascadedValueTransformationImpl transformation = new CascadedValueTransformationImpl("deFault");
|
||||
assertTrue(transformation.isIdentityTransform());
|
||||
assertEquals("xx", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationDefaultChained() {
|
||||
CascadedValueTransformationImpl transformation = new CascadedValueTransformationImpl("deFault∩DEFAULT∩default");
|
||||
assertTrue(transformation.isIdentityTransform());
|
||||
assertEquals("xx", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationDefaultChainedWithStatic() {
|
||||
CascadedValueTransformationImpl transformation = new CascadedValueTransformationImpl(
|
||||
"deFault∩DEFAULT∩default∩static");
|
||||
assertFalse(transformation.isIdentityTransform());
|
||||
assertEquals("static", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
/**
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SingleValueTransformationTest {
|
||||
|
||||
@Test
|
||||
public void testTransformationOldStyle() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation("REGEX(myregex:foo(.*))");
|
||||
assertEquals("REGEX", transformation.transformationServiceName);
|
||||
assertEquals("myregex:foo(.*)", transformation.transformationServiceParam);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationOldStyle2() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation("REG_(EX(myregex:foo(.*))");
|
||||
assertEquals("REG_", transformation.transformationServiceName);
|
||||
assertEquals("EX(myregex:foo(.*)", transformation.transformationServiceParam);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationNewStyle() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation("REGEX:myregex(.*)");
|
||||
assertEquals("REGEX", transformation.transformationServiceName);
|
||||
assertEquals("myregex(.*)", transformation.transformationServiceParam);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationNewStyle2() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation("REGEX::myregex(.*)");
|
||||
assertEquals("REGEX", transformation.transformationServiceName);
|
||||
assertEquals(":myregex(.*)", transformation.transformationServiceParam);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationEmpty() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation("");
|
||||
assertFalse(transformation.isIdentityTransform());
|
||||
assertEquals("", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationNull() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation(null);
|
||||
assertFalse(transformation.isIdentityTransform());
|
||||
assertEquals("", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationDefault() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation("deFault");
|
||||
assertTrue(transformation.isIdentityTransform());
|
||||
assertEquals("xx", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformationDefaultChainedWithStatic() {
|
||||
SingleValueTransformation transformation = new SingleValueTransformation("static");
|
||||
assertFalse(transformation.isIdentityTransform());
|
||||
assertEquals("static", transformation.transform(Mockito.mock(BundleContext.class), "xx"));
|
||||
}
|
||||
}
|
@ -1,296 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.profiles;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.EmptySource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.NullSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.profiles.ProfileCallback;
|
||||
import org.openhab.core.thing.profiles.ProfileContext;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* @author Ciprian Pascu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusGainOffsetProfileTest {
|
||||
|
||||
static Stream<Arguments> provideArgsForBoth() {
|
||||
return Stream.of(
|
||||
// dimensionless
|
||||
Arguments.of("100", "0.5", "250", "175.0"), Arguments.of("0", "1 %", "250", "250 %"),
|
||||
//
|
||||
// gain with same unit
|
||||
//
|
||||
// e.g. (handler) 3 <---> (item) 106K with pre-gain-offset=50, gain=2K
|
||||
// e.g. (handler) 3 K <---> (item) 106K^2 with pre-gain-offset=50K, gain=2K
|
||||
//
|
||||
Arguments.of("50", "2 K", "3", "106 K"),
|
||||
//
|
||||
// gain with different unit
|
||||
//
|
||||
Arguments.of("50", "2 m/s", "3", "106 m/s"),
|
||||
//
|
||||
// gain without unit
|
||||
//
|
||||
Arguments.of("50", "2", "3", "106"),
|
||||
//
|
||||
// temperature tests
|
||||
//
|
||||
// celsius gain
|
||||
Arguments.of("0", "0.1 °C", "25", "2.5 °C"),
|
||||
// kelvin gain
|
||||
Arguments.of("0", "0.1 K", "25", "2.5 K"),
|
||||
// fahrenheit gain
|
||||
Arguments.of("0", "10 °F", "0.18", "1.80 °F"),
|
||||
//
|
||||
// unsupported types are passed with error
|
||||
Arguments.of("0", "0", OnOffType.ON, OnOffType.ON)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Arguments> provideAdditionalArgsForStateUpdateFromHandler() {
|
||||
return Stream.of(
|
||||
// Dimensionless conversion 2.5/1% = 250%/1% = 250
|
||||
Arguments.of("0", "1 %", "250", "250 %"), Arguments.of("2 %", "1 %", "249.9800", "250.0000 %"),
|
||||
Arguments.of("50", "2 m/s", new DecimalType("3"), "106 m/s"),
|
||||
// UNDEF passes the profile unchanged
|
||||
Arguments.of("0", "0", UnDefType.UNDEF, UnDefType.UNDEF));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Test profile behaviour when handler updates the state
|
||||
*
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource({ "provideArgsForBoth", "provideAdditionalArgsForStateUpdateFromHandler" })
|
||||
public void testOnStateUpdateFromHandler(String preGainOffset, String gain, Object updateFromHandlerObj,
|
||||
Object expectedUpdateTowardsItemObj) {
|
||||
testOnUpdateFromHandlerGeneric(preGainOffset, gain, updateFromHandlerObj, expectedUpdateTowardsItemObj, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Test profile behaviour when handler sends command
|
||||
*
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource({ "provideArgsForBoth", "provideAdditionalArgsForStateUpdateFromHandler" })
|
||||
public void testOnCommandFromHandler(String preGainOffset, String gain, Object updateFromHandlerObj,
|
||||
Object expectedUpdateTowardsItemObj) {
|
||||
// UNDEF is not a command, cannot be sent by handler
|
||||
assumeTrue(updateFromHandlerObj != UnDefType.UNDEF);
|
||||
testOnUpdateFromHandlerGeneric(preGainOffset, gain, updateFromHandlerObj, expectedUpdateTowardsItemObj, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Test profile behaviour when handler updates the state
|
||||
*
|
||||
* @param preGainOffset profile pre-gain-offset offset
|
||||
* @param gain profile gain
|
||||
* @param updateFromHandlerObj state update from handler. String representing QuantityType or State/Command
|
||||
* @param expectedUpdateTowardsItemObj expected state/command update towards item. String representing QuantityType
|
||||
* or
|
||||
* State
|
||||
* @param stateUpdateFromHandler whether there is state update from handler. Otherwise command
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void testOnUpdateFromHandlerGeneric(String preGainOffset, String gain, Object updateFromHandlerObj,
|
||||
Object expectedUpdateTowardsItemObj, boolean stateUpdateFromHandler) {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
ModbusGainOffsetProfile profile = createProfile(callback, gain, preGainOffset);
|
||||
|
||||
final Type actualStateUpdateTowardsItem;
|
||||
if (stateUpdateFromHandler) {
|
||||
final State updateFromHandler;
|
||||
if (updateFromHandlerObj instanceof String str) {
|
||||
updateFromHandler = new QuantityType(str);
|
||||
} else {
|
||||
assertTrue(updateFromHandlerObj instanceof State);
|
||||
updateFromHandler = (State) updateFromHandlerObj;
|
||||
}
|
||||
|
||||
profile.onStateUpdateFromHandler(updateFromHandler);
|
||||
|
||||
ArgumentCaptor<State> capture = ArgumentCaptor.forClass(State.class);
|
||||
verify(callback, times(1)).sendUpdate(capture.capture());
|
||||
actualStateUpdateTowardsItem = capture.getValue();
|
||||
} else {
|
||||
final Command updateFromHandler;
|
||||
if (updateFromHandlerObj instanceof String str) {
|
||||
updateFromHandler = new QuantityType(str);
|
||||
} else {
|
||||
assertTrue(updateFromHandlerObj instanceof State);
|
||||
updateFromHandler = (Command) updateFromHandlerObj;
|
||||
}
|
||||
|
||||
profile.onCommandFromHandler(updateFromHandler);
|
||||
|
||||
ArgumentCaptor<Command> capture = ArgumentCaptor.forClass(Command.class);
|
||||
verify(callback, times(1)).sendCommand(capture.capture());
|
||||
actualStateUpdateTowardsItem = capture.getValue();
|
||||
}
|
||||
|
||||
Type expectedStateUpdateTowardsItem = (expectedUpdateTowardsItemObj instanceof String s) ? new QuantityType(s)
|
||||
: (Type) expectedUpdateTowardsItemObj;
|
||||
assertEquals(expectedStateUpdateTowardsItem, actualStateUpdateTowardsItem);
|
||||
verifyNoMoreInteractions(callback);
|
||||
}
|
||||
|
||||
static Stream<Arguments> provideAdditionalArgsForCommandFromItem() {
|
||||
return Stream.of(
|
||||
// Dimensionless conversion 2.5/1% = 250%/1% = 250
|
||||
// gain in %, command as bare ratio and the other way around
|
||||
Arguments.of("0", "1 %", "250", "2.5"), Arguments.of("2%", "1 %", "249.9800", "2.5"),
|
||||
|
||||
// celsius gain, kelvin command
|
||||
Arguments.of("0", "0.1 °C", "-2706.5", "2.5 K"),
|
||||
|
||||
// incompatible command unit, should be convertible with gain
|
||||
Arguments.of("0", "0.1 °C", null, "2.5 m/s"),
|
||||
//
|
||||
// incompatible offset unit
|
||||
//
|
||||
Arguments.of("50 K", "21", null, "30 m/s"), Arguments.of("50 m/s", "21", null, "30 K"),
|
||||
//
|
||||
// UNDEF command is not processed
|
||||
//
|
||||
Arguments.of("0", "0", null, UnDefType.UNDEF),
|
||||
//
|
||||
// REFRESH command is forwarded
|
||||
//
|
||||
Arguments.of("0", "0", RefreshType.REFRESH, RefreshType.REFRESH)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Test profile behavior when item receives command
|
||||
*
|
||||
* @param preGainOffset profile pre-gain-offset
|
||||
* @param gain profile gain
|
||||
* @param expectedCommandTowardsHandlerObj expected command towards handler. String representing QuantityType or
|
||||
* Command. Use null to verify that no commands are sent to handler.
|
||||
* @param commandFromItemObj command that item receives. String representing QuantityType or Command.
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
@ParameterizedTest
|
||||
@MethodSource({ "provideArgsForBoth", "provideAdditionalArgsForCommandFromItem" })
|
||||
public void testOnCommandFromItem(String preGainOffset, String gain,
|
||||
@Nullable Object expectedCommandTowardsHandlerObj, Object commandFromItemObj) {
|
||||
assumeFalse(commandFromItemObj.equals(UnDefType.UNDEF));
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
ModbusGainOffsetProfile profile = createProfile(callback, gain, preGainOffset);
|
||||
|
||||
Command commandFromItem = (commandFromItemObj instanceof String str) ? new QuantityType(str)
|
||||
: (Command) commandFromItemObj;
|
||||
profile.onCommandFromItem(commandFromItem);
|
||||
|
||||
boolean callsExpected = expectedCommandTowardsHandlerObj != null;
|
||||
if (callsExpected) {
|
||||
ArgumentCaptor<Command> capture = ArgumentCaptor.forClass(Command.class);
|
||||
verify(callback, times(1)).handleCommand(capture.capture());
|
||||
Command actualCommandTowardsHandler = capture.getValue();
|
||||
Command expectedCommandTowardsHandler = (expectedCommandTowardsHandlerObj instanceof String str)
|
||||
? new QuantityType(str)
|
||||
: (Command) expectedCommandTowardsHandlerObj;
|
||||
assertEquals(expectedCommandTowardsHandler, actualCommandTowardsHandler);
|
||||
verifyNoMoreInteractions(callback);
|
||||
} else {
|
||||
verifyNoInteractions(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Test behaviour when item receives state update from item (no-op)
|
||||
*
|
||||
**/
|
||||
@Test
|
||||
public void testOnCommandFromItem() {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
ModbusGainOffsetProfile<?> profile = createProfile(callback, "1.0", "0.0");
|
||||
|
||||
profile.onStateUpdateFromItem(new DecimalType(3.78));
|
||||
// should be no-op
|
||||
verifyNoInteractions(callback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidInit() {
|
||||
// preGainOffset must be dimensionless
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
ModbusGainOffsetProfile<?> profile = createProfile(callback, "1.0", "0.0 K");
|
||||
assertFalse(profile.isValid());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NullSource
|
||||
@EmptySource
|
||||
public void testInitGainDefault(String gain) {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
ModbusGainOffsetProfile<?> p = createProfile(callback, gain, "0.0");
|
||||
assertTrue(p.isValid());
|
||||
assertEquals(p.getGain(), Optional.of(QuantityType.ONE));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NullSource
|
||||
@EmptySource
|
||||
public void testInitOffsetDefault(String preGainOffset) {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
ModbusGainOffsetProfile<?> p = createProfile(callback, "1", preGainOffset);
|
||||
assertTrue(p.isValid());
|
||||
assertEquals(p.getPregainOffset(), Optional.of(QuantityType.ZERO));
|
||||
}
|
||||
|
||||
private ModbusGainOffsetProfile<?> createProfile(ProfileCallback callback, @Nullable String gain,
|
||||
@Nullable String preGainOffset) {
|
||||
ProfileContext context = mock(ProfileContext.class);
|
||||
Configuration config = new Configuration();
|
||||
if (gain != null) {
|
||||
config.put("gain", gain);
|
||||
}
|
||||
if (preGainOffset != null) {
|
||||
config.put("pre-gain-offset", preGainOffset);
|
||||
}
|
||||
when(context.getConfiguration()).thenReturn(config);
|
||||
|
||||
return new ModbusGainOffsetProfile<>(callback, context);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user