mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[knx] Refactoring of KnxCoreTypeMapper and UOM Support (#14534)
* [knx] Refactoring, add basic support for UOM Preparation for refactoring KnxCoreTypeMapper. Carryover from smarthomej/addons#107. Merge UOM implementations. * [knx] Adapt tests DPT strings for QuantityType now strip off a tailing .0 when decimals are converted. * [knx] Refactoring Use pattern matching with instanceof operator (new Java17 feature). * [knx] Refactoring, performance improvements Introduce KNXChannel class. Carryover from smarthomej/addons#114. * [knx] Add warning for incompatible DPT type Configuring incompatible DPT/channel combinations (e.g. DPT 1.005 (alarm) on Contact channels or DPT 1.019 (windows/door) on Switch channels) is not allowed but was silently ignored. This PR adds a warning in case incompatible configurations are detected. Carryover from smarthomej/addons#203. * [knx] Add full support for UoM Replace UoM handling with the implementation from smarthome/j. Carryover from smarthomej/addons#206. * [knx] Refactor KNXCoreTypeMapper, add RGBW and xyY Carryover from smarthomej/addons#208. * [knx] Fix RGB conversion Carryover from smarthomej/addons#219. * [knx] Remove workarounds obsoleted by Calimero 2.5 Carryover from smarthomej/addons#226. * [knx] Add parameter for disabling incoming UoM Carryover from smarthomej/addons#230. * [knx] Fix fallback to DecimalType in number conversion Carryover from smarthomej/addons#279. * [knx] Fix DPT 251.600 decoding Carryover from smarthomej/addons#349. * [knx] Fix UoM handling for special types * [knx] Add test for KNXChannelFactory * [knx] Update CODEOWNERS for knx * [knx] Default conversion for DPT 5.001 and 6.001 * [knx] Fix write blocked forever after read from bus Carryover from smarthomej/addons#299 and smarthomej/addons#330. * [knx] Use new class ColorUtil from core for HSB conversion Also-by: Jan N. Klug <github@klug.nrw> Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
This commit is contained in:
parent
b395d0e227
commit
aae63e9488
@ -159,7 +159,7 @@
|
||||
/bundles/org.openhab.binding.kaleidescape/ @mlobstein
|
||||
/bundles/org.openhab.binding.keba/ @kgoderis
|
||||
/bundles/org.openhab.binding.km200/ @Markinus
|
||||
/bundles/org.openhab.binding.knx/ @kaikreuzer
|
||||
/bundles/org.openhab.binding.knx/ @kaikreuzer @holgerfriedrich
|
||||
/bundles/org.openhab.binding.kodi/ @pail23 @cweitkamp
|
||||
/bundles/org.openhab.binding.konnected/ @volfan6415
|
||||
/bundles/org.openhab.binding.kostalinverter/ @cschneider
|
||||
|
@ -9,6 +9,18 @@ The KNX binding then can communicate directly with this gateway.
|
||||
Alternatively, a PC running [KNXD](https://github.com/knxd/knxd) (free open source component software) can be put in between which then acts as a broker allowing multiple client to connect to the same gateway.
|
||||
Since the protocol is identical, the KNX binding can also communicate with it transparently.
|
||||
|
||||
***Attention:*** With the introduction of Unit of Measurement (UoM) support, some data types have changed (see `number` channel below):
|
||||
|
||||
- Data type for DPT 5.001 (Percent 8bit, 0 -> 100%) has changed from `PercentType` to `QuantityType`for `number` channels (`dimmer`, `color`, `rollershutter` channels stay with `PercentType`).
|
||||
- Data type for DPT 5.004 (Percent 8bit, 0 -> 255%) has changed from `PercentType` to `QuantityType`.
|
||||
- Data type for DPT 6.001 (Percent 8bit -128 -> 127%) has changed from `PercentType` to `QuantityType`.
|
||||
- Data type for DPT 9.007 (Humidity) has changed from `PercentType` to `QuantityType`.
|
||||
|
||||
Rules that check for or compare states and transformations that expect a raw value might need adjustments.
|
||||
If you run into trouble with that and need some time, you can disable UoM support on binding level via the `disableUoM` parameter.
|
||||
UoM are enabled by default and need to be disabled manually.
|
||||
A new setting is activated immediately without restart.
|
||||
|
||||
## Supported Things
|
||||
|
||||
The KNX binding supports two types of bridges, and one type of things to access the KNX bus.
|
||||
@ -16,7 +28,8 @@ There is an _ip_ bridge to connect to KNX IP Gateways, and a _serial_ bridge for
|
||||
|
||||
## Bridges
|
||||
|
||||
The following two bridge types are supported. Bridges don't have channels on their own.
|
||||
The following two bridge types are supported.
|
||||
Bridges don't have channels on their own.
|
||||
|
||||
### IP Gateway
|
||||
|
||||
@ -76,45 +89,30 @@ All channels of a device share one configuration parameter defined on device lev
|
||||
All readable group addresses are queried by openHAB during startup.
|
||||
If readInterval is not specified or set to 0, no further periodic reading will be triggered (default: 0).
|
||||
|
||||
#### Standard Channel Types
|
||||
#### Channel Types
|
||||
|
||||
Standard channels are used most of the time.
|
||||
They are used in the common case where the physical state is owned by a device within the KNX bus, e.g. by a switch actuator who "knows" whether the light is turned on or off, or by a temperature sensor which reports the room temperature regularly.
|
||||
|
||||
Note: After changing the DPT of already existing Channels, openHAB needs to be restarted for the changes to become effective.
|
||||
Control channel types (suffix `-control`) are used for cases where the KNX bus does not own the physical state of a device.
|
||||
This could be the case if e.g. a lamp from another binding should be controlled by a KNX wall switch.
|
||||
When a `GroupValueRead` telegram is sent from the KNX bus to a *-control Channel, the bridge responds with a `GroupValueResponse` telegram to the KNX bus.
|
||||
|
||||
##### Channel Type "switch"
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|-------------------------------------|-------------|
|
||||
| ga | Group address for the binary switch | 1.001 |
|
||||
|
||||
##### Channel Type "dimmer"
|
||||
##### Channel Type `color`, `color-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|------------------|----------------------------------------|-------------|
|
||||
| hsb | Group address for the color | 232.600 |
|
||||
| switch | Group address for the binary switch | 1.001 |
|
||||
| position | Group address of the absolute position | 5.001 |
|
||||
| increaseDecrease | Group address for relative movement | 3.007 |
|
||||
| position | Group address brightness | 5.001 |
|
||||
| increaseDecrease | Group address for relative brightness | 3.007 |
|
||||
|
||||
##### Channel Type "color"
|
||||
The `hsb` address supports DPT 242.600 and 251.600.
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|------------------|----------------------------------------|-------------|
|
||||
| hsb | Group address for color | 232.600 |
|
||||
| switch | Group address for the binary switch | 1.001 |
|
||||
| position | Group address of the absolute position | 5.001 |
|
||||
| increaseDecrease | Group address for relative movement | 3.007 |
|
||||
Some RGB/RGBW products (e.g. MDT) support HSB values for DPT 232.600 instead of RGB.
|
||||
This is supported as "vendor-specific DPT" with a value of 232.60000.
|
||||
|
||||
##### Channel Type "rollershutter"
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|-----------------------------------------|-------------|
|
||||
| upDown | Group address for relative movement | 1.008 |
|
||||
| stopMove | Group address for stopping | 1.010 |
|
||||
| position | Group address for the absolute position | 5.001 |
|
||||
|
||||
##### Channel Type "contact"
|
||||
##### Channel Type `contact`, `contact-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|---------------|-------------|
|
||||
@ -123,32 +121,63 @@ Note: After changing the DPT of already existing Channels, openHAB needs to be r
|
||||
*Attention:* Due to a bug in the original implementation, the states for DPT 1.009 are inverted (i.e. `1` is mapped to `OPEN` instead of `CLOSE`).
|
||||
A change would break all existing installations and is therefore not implemented.
|
||||
|
||||
##### Channel Type "number"
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|---------------|-------------|
|
||||
| ga | Group address | 9.001 |
|
||||
|
||||
Note: Using the Units Of Measurement feature of openHAB (Quantitytype) requires that the DPT value is set correctly.
|
||||
Automatic type conversion will be applied if required.
|
||||
|
||||
##### Channel Type "string"
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|---------------|-------------|
|
||||
| ga | Group address | 16.001 |
|
||||
|
||||
##### Channel Type "datetime"
|
||||
##### Channel Type `datetime`, `datetime-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|---------------|-------------|
|
||||
| ga | Group address | 19.001 |
|
||||
|
||||
##### Channel Type `dimmer`, `dimmer-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|------------------|----------------------------------------|-------------|
|
||||
| switch | Group address for the binary switch | 1.001 |
|
||||
| position | Group address of the absolute position | 5.001 |
|
||||
| increaseDecrease | Group address for relative movement | 3.007 |
|
||||
|
||||
##### Channel Type `number`, `number-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|---------------|-------------|
|
||||
| ga | Group address | 9.001 |
|
||||
|
||||
Note: The `number` channel has full support for Units Of Measurement (UoM).
|
||||
|
||||
Using the UoM feature of openHAB (QuantityType) requires that the DPT value is set correctly.
|
||||
Automatic type conversion will be applied if required.
|
||||
|
||||
Incoming values from the KNX bus are converted to values with units (e.g. `23 °C`).
|
||||
If the channel is linked to the correct item-type (`Number:Temperature` in this case) the display unit can be controlled by item metadata (e.g. `%.1f °F` for 1 digit of precision in Fahrenheit).
|
||||
The unit is stripped if the channel is linked to a plain number item (type `Number`).
|
||||
|
||||
Outgoing values with unit are first converted to the unit associated with the DPT (e.g. a value of `10 °F` is converted to `-8.33 °C` if the channel has DPT 9.001).
|
||||
Values from plain number channels are sent as-is (without any conversion).
|
||||
|
||||
##### Channel Type `rollershutter`, `rollershutter-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|-----------------------------------------|-------------|
|
||||
| upDown | Group address for relative movement | 1.008 |
|
||||
| stopMove | Group address for stopping | 1.010 |
|
||||
| position | Group address for the absolute position | 5.001 |
|
||||
|
||||
##### Channel Type `string`, `string-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|---------------|-------------|
|
||||
| ga | Group address | 16.001 |
|
||||
|
||||
##### Channel Type `switch`, `switch-control`
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|-----------|-------------------------------------|-------------|
|
||||
| ga | Group address for the binary switch | 1.001 |
|
||||
|
||||
#### Control Channel Types
|
||||
|
||||
In contrast to the standard channels above, the control channel types are used for cases where the KNX bus does not own the physical state of a device.
|
||||
This could for example be the case if a lamp from another binding should be controlled by a KNX wall switch.
|
||||
If from the KNX bus a `GroupValueRead` telegram is sent to a *-control Channel, the bridge responds with a `GroupValueResponse` telegram to the KNX bus.
|
||||
When a `GroupValueRead` telegram is sent from the KNX bus to a *-control Channel, the bridge responds with a `GroupValueResponse` telegram to the KNX bus.
|
||||
|
||||
##### Channel Type "switch-control"
|
||||
|
||||
@ -165,14 +194,6 @@ If from the KNX bus a `GroupValueRead` telegram is sent to a *-control Channel,
|
||||
| increaseDecrease | Group address for relative movement | 3.007 |
|
||||
| frequency | Increase/Decrease frequency in milliseconds in case the binding should handle that (0 if the KNX device sends the commands repeatedly itself) | 0 |
|
||||
|
||||
##### Channel Type "color-control"
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
|------------------|----------------------------------------|-------------|
|
||||
| hsb | Group address for color | 232.600 |
|
||||
| switch | Group address for the binary switch | 1.001 |
|
||||
| position | Group address of the absolute position | 5.001 |
|
||||
| increaseDecrease | Group address for relative movement | 3.007 |
|
||||
|
||||
##### Channel Type "rollershutter-control"
|
||||
|
||||
@ -197,6 +218,8 @@ A change would break all existing installations and is therefore not implemented
|
||||
|-----------|---------------|-------------|
|
||||
| ga | Group address | 9.001 |
|
||||
|
||||
For UoM support see the explanations of the `number` channel.
|
||||
|
||||
##### Channel Type "string-control"
|
||||
|
||||
| Parameter | Description | Default DPT |
|
||||
@ -340,14 +363,14 @@ Bridge knx:ip:bridge [
|
||||
knx.items:
|
||||
|
||||
```java
|
||||
Switch demoSwitch "Light [%s]" <light> { channel="knx:device:bridge:generic:demoSwitch" }
|
||||
Color demoColorLight "Color [%s]" <light> { channel="knx:device:bridge:generic:demoColorLight" }
|
||||
Dimmer demoDimmer "Dimmer [%d %%]" <light> { channel="knx:device:bridge:generic:demoDimmer" }
|
||||
Rollershutter demoRollershutter "Shade [%d %%]" <rollershutter> { channel="knx:device:bridge:generic:demoRollershutter" }
|
||||
Contact demoContact "Front Door [%s]" <frontdoor> { channel="knx:device:bridge:generic:demoContact" }
|
||||
Number demoTemperature "Temperature [%.1f °C]" <temperature> { channel="knx:device:bridge:generic:demoTemperature" }
|
||||
String demoString "Message of the day [%s]" { channel="knx:device:bridge:generic:demoString" }
|
||||
DateTime demoDatetime "Alarm [%1$tH:%1$tM]" { channel="knx:device:bridge:generic:demoDatetime" }
|
||||
Switch demoSwitch "Light [%s]" <light> { channel="knx:device:bridge:generic:demoSwitch" }
|
||||
Color demoColorLight "Color [%s]" <light> { channel="knx:device:bridge:generic:demoColorLight" }
|
||||
Dimmer demoDimmer "Dimmer [%d %%]" <light> { channel="knx:device:bridge:generic:demoDimmer" }
|
||||
Rollershutter demoRollershutter "Shade [%d %%]" <rollershutter> { channel="knx:device:bridge:generic:demoRollershutter" }
|
||||
Contact demoContact "Front Door [%s]" <frontdoor> { channel="knx:device:bridge:generic:demoContact" }
|
||||
Number:Temperature demoTemperature "Temperature [%.1f °C]" <temperature> { channel="knx:device:bridge:generic:demoTemperature" }
|
||||
String demoString "Message of the day [%s]" { channel="knx:device:bridge:generic:demoString" }
|
||||
DateTime demoDatetime "Alarm [%1$tH:%1$tM]" { channel="knx:device:bridge:generic:demoDatetime" }
|
||||
```
|
||||
|
||||
knx.sitemap:
|
||||
|
@ -12,11 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
@ -32,6 +28,10 @@ public class KNXBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "knx";
|
||||
|
||||
// Global config
|
||||
public static final String CONFIG_DISABLE_UOM = "disableUoM";
|
||||
public static boolean disableUoM = false;
|
||||
|
||||
// Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_IP_BRIDGE = new ThingTypeUID(BINDING_ID, "ip");
|
||||
public static final ThingTypeUID THING_TYPE_SERIAL_BRIDGE = new ThingTypeUID(BINDING_ID, "serial");
|
||||
@ -84,7 +84,8 @@ public class KNXBindingConstants {
|
||||
public static final String CHANNEL_SWITCH = "switch";
|
||||
public static final String CHANNEL_SWITCH_CONTROL = "switch-control";
|
||||
|
||||
public static final Set<String> CONTROL_CHANNEL_TYPES = Collections.unmodifiableSet(Stream.of(CHANNEL_COLOR_CONTROL, //
|
||||
public static final Set<String> CONTROL_CHANNEL_TYPES = Set.of( //
|
||||
CHANNEL_COLOR_CONTROL, //
|
||||
CHANNEL_CONTACT_CONTROL, //
|
||||
CHANNEL_DATETIME_CONTROL, //
|
||||
CHANNEL_DIMMER_CONTROL, //
|
||||
@ -92,7 +93,7 @@ public class KNXBindingConstants {
|
||||
CHANNEL_ROLLERSHUTTER_CONTROL, //
|
||||
CHANNEL_STRING_CONTROL, //
|
||||
CHANNEL_SWITCH_CONTROL //
|
||||
).collect(toSet()));
|
||||
);
|
||||
|
||||
public static final String CHANNEL_RESET = "reset";
|
||||
|
||||
|
@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
|
||||
/**
|
||||
* Base class for telegram meta-data
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API.
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractSpec {
|
||||
|
||||
private String dpt;
|
||||
|
||||
protected AbstractSpec(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT) {
|
||||
if (channelConfiguration != null) {
|
||||
String configuredDPT = channelConfiguration.getDPT();
|
||||
this.dpt = configuredDPT != null ? configuredDPT : defaultDPT;
|
||||
} else {
|
||||
this.dpt = defaultDPT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert a {@link GroupAddressConfiguration} into a {@link GroupAddress}.
|
||||
*
|
||||
* @param ga the group address configuration
|
||||
* @return a group address object
|
||||
*/
|
||||
protected final GroupAddress toGroupAddress(GroupAddressConfiguration ga) {
|
||||
try {
|
||||
return new GroupAddress(ga.getGA());
|
||||
} catch (KNXFormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data point type.
|
||||
* <p>
|
||||
* See {@link org.openhab.binding.knx.internal.client.InboundSpec#getDPT()} and
|
||||
* {@link org.openhab.binding.knx.internal.client.OutboundSpec#getDPT()}.
|
||||
*
|
||||
* @return the data point type.
|
||||
*/
|
||||
public final String getDPT() {
|
||||
return dpt;
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Data structure representing the content of a channel's group address configuration.
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API.
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChannelConfiguration {
|
||||
|
||||
private final @Nullable String dpt;
|
||||
private final GroupAddressConfiguration mainGA;
|
||||
private final List<GroupAddressConfiguration> listenGAs;
|
||||
|
||||
public ChannelConfiguration(@Nullable String dpt, GroupAddressConfiguration mainGA,
|
||||
List<GroupAddressConfiguration> listenGAs) {
|
||||
this.dpt = dpt;
|
||||
this.mainGA = mainGA;
|
||||
this.listenGAs = listenGAs;
|
||||
}
|
||||
|
||||
public @Nullable String getDPT() {
|
||||
return dpt;
|
||||
}
|
||||
|
||||
public GroupAddressConfiguration getMainGA() {
|
||||
return mainGA;
|
||||
}
|
||||
|
||||
public List<GroupAddressConfiguration> getListenGAs() {
|
||||
return Stream.concat(Stream.of(mainGA), listenGAs.stream()).collect(toList());
|
||||
}
|
||||
|
||||
public List<GroupAddressConfiguration> getReadGAs() {
|
||||
return getListenGAs().stream().filter(ga -> ga.isRead()).collect(toList());
|
||||
}
|
||||
}
|
@ -12,41 +12,106 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
|
||||
/**
|
||||
* Data structure representing a single group address configuration within a channel configuration parameter.
|
||||
* Data structure representing the content of a channel's group address configuration.
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API.
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupAddressConfiguration {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(GroupAddressConfiguration.class);
|
||||
|
||||
private final String ga;
|
||||
private final boolean read;
|
||||
private static final Pattern PATTERN_GA_CONFIGURATION = Pattern.compile(
|
||||
"^((?<dpt>[1-9][0-9]{0,2}\\.[0-9]{3,5}):)?(?<read><)?(?<mainGA>[0-9]{1,5}(/[0-9]{1,4}){0,2})(?<listenGAs>(\\+(<?[0-9]{1,5}(/[0-9]{1,4}){0,2}))*)$");
|
||||
private static final Pattern PATTERN_LISTEN_GA = Pattern
|
||||
.compile("\\+((?<read><)?(?<GA>[0-9]{1,5}(/[0-9]{1,4}){0,2}))");
|
||||
|
||||
public GroupAddressConfiguration(String ga, boolean read) {
|
||||
super();
|
||||
this.ga = ga;
|
||||
this.read = read;
|
||||
private final @Nullable String dpt;
|
||||
private final GroupAddress mainGA;
|
||||
private final Set<GroupAddress> listenGAs;
|
||||
private final Set<GroupAddress> readGAs;
|
||||
|
||||
private GroupAddressConfiguration(@Nullable String dpt, GroupAddress mainGA, Set<GroupAddress> listenGAs,
|
||||
Set<GroupAddress> readGAs) {
|
||||
this.dpt = dpt;
|
||||
this.mainGA = mainGA;
|
||||
this.listenGAs = listenGAs;
|
||||
this.readGAs = readGAs;
|
||||
}
|
||||
|
||||
/**
|
||||
* The group address.
|
||||
*
|
||||
* @return the group address.
|
||||
*/
|
||||
public String getGA() {
|
||||
return ga;
|
||||
public @Nullable String getDPT() {
|
||||
return dpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes whether the group address is marked to be actively read from.
|
||||
*
|
||||
* @return {@code true} if read requests should be issued to this address
|
||||
*/
|
||||
public boolean isRead() {
|
||||
return read;
|
||||
public GroupAddress getMainGA() {
|
||||
return mainGA;
|
||||
}
|
||||
|
||||
public Set<GroupAddress> getListenGAs() {
|
||||
return listenGAs;
|
||||
}
|
||||
|
||||
public Set<GroupAddress> getReadGAs() {
|
||||
return readGAs;
|
||||
}
|
||||
|
||||
public static @Nullable GroupAddressConfiguration parse(@Nullable Object configuration) {
|
||||
if (!(configuration instanceof String)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Matcher matcher = PATTERN_GA_CONFIGURATION.matcher(((String) configuration).replace(" ", ""));
|
||||
if (matcher.matches()) {
|
||||
// Listen GAs
|
||||
String input = matcher.group("listenGAs");
|
||||
Matcher m2 = PATTERN_LISTEN_GA.matcher(input);
|
||||
Set<GroupAddress> listenGAs = new HashSet<>();
|
||||
Set<GroupAddress> readGAs = new HashSet<>();
|
||||
while (m2.find()) {
|
||||
String ga = m2.group("GA");
|
||||
try {
|
||||
GroupAddress groupAddress = new GroupAddress(ga);
|
||||
listenGAs.add(groupAddress);
|
||||
if (m2.group("read") != null) {
|
||||
readGAs.add(groupAddress);
|
||||
}
|
||||
} catch (KNXFormatException e) {
|
||||
LOGGER.warn("Failed to create GroupAddress from {}", ga);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Main GA
|
||||
String mainGA = matcher.group("mainGA");
|
||||
try {
|
||||
GroupAddress groupAddress = new GroupAddress(mainGA);
|
||||
listenGAs.add(groupAddress); // also listening to main GA
|
||||
if (matcher.group("read") != null) {
|
||||
readGAs.add(groupAddress); // also reading main GA
|
||||
}
|
||||
return new GroupAddressConfiguration(matcher.group("dpt"), groupAddress, listenGAs, readGAs);
|
||||
} catch (KNXFormatException e) {
|
||||
LOGGER.warn("Failed to create GroupAddress from {}", mainGA);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
LOGGER.warn("Failed parsing channel configuration '{}'.", configuration);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.CONTROL_CHANNEL_TYPES;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.GA;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.knx.internal.client.InboundSpec;
|
||||
import org.openhab.binding.knx.internal.client.OutboundSpec;
|
||||
import org.openhab.binding.knx.internal.dpt.DPTUtil;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
/**
|
||||
* Meta-data abstraction for the KNX channel configurations.
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API
|
||||
* @author Jan N. Klug - refactored from type definition to channel instance
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class KNXChannel {
|
||||
private final Logger logger = LoggerFactory.getLogger(KNXChannel.class);
|
||||
private final Set<String> gaKeys;
|
||||
|
||||
private final Map<String, GroupAddressConfiguration> groupAddressConfigurations = new HashMap<>();
|
||||
private final Set<GroupAddress> listenAddresses = new HashSet<>();
|
||||
private final Set<GroupAddress> writeAddresses = new HashSet<>();
|
||||
private final String channelType;
|
||||
private final ChannelUID channelUID;
|
||||
private final boolean isControl;
|
||||
private final Class<? extends Type> preferredType;
|
||||
|
||||
KNXChannel(List<Class<? extends Type>> acceptedTypes, Channel channel) {
|
||||
this(Set.of(GA), acceptedTypes, channel);
|
||||
}
|
||||
|
||||
KNXChannel(Set<String> gaKeys, List<Class<? extends Type>> acceptedTypes, Channel channel) {
|
||||
this.gaKeys = gaKeys;
|
||||
this.preferredType = acceptedTypes.get(0);
|
||||
|
||||
// this is safe because we already checked the presence of the ChannelTypeUID before
|
||||
this.channelType = Objects.requireNonNull(channel.getChannelTypeUID()).getId();
|
||||
this.channelUID = channel.getUID();
|
||||
this.isControl = CONTROL_CHANNEL_TYPES.contains(channelType);
|
||||
|
||||
// build map of ChannelConfigurations and GA lists
|
||||
Configuration configuration = channel.getConfiguration();
|
||||
gaKeys.forEach(key -> {
|
||||
GroupAddressConfiguration groupAddressConfiguration = GroupAddressConfiguration
|
||||
.parse(configuration.get(key));
|
||||
if (groupAddressConfiguration != null) {
|
||||
// check DPT configuration (if set) is compatible with item
|
||||
String dpt = groupAddressConfiguration.getDPT();
|
||||
if (dpt != null) {
|
||||
Set<Class<? extends Type>> types = DPTUtil.getAllowedTypes(dpt);
|
||||
if (acceptedTypes.stream().noneMatch(types::contains)) {
|
||||
logger.warn("Configured DPT '{}' is incompatible with accepted types '{}' for channel '{}'",
|
||||
dpt, acceptedTypes, channelUID);
|
||||
}
|
||||
}
|
||||
groupAddressConfigurations.put(key, groupAddressConfiguration);
|
||||
// store address configuration for re-use
|
||||
listenAddresses.addAll(groupAddressConfiguration.getListenGAs());
|
||||
writeAddresses.add(groupAddressConfiguration.getMainGA());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getChannelType() {
|
||||
return channelType;
|
||||
}
|
||||
|
||||
public ChannelUID getChannelUID() {
|
||||
return channelUID;
|
||||
}
|
||||
|
||||
public boolean isControl() {
|
||||
return isControl;
|
||||
}
|
||||
|
||||
public Class<? extends Type> preferredType() {
|
||||
return preferredType;
|
||||
}
|
||||
|
||||
public final Set<GroupAddress> getAllGroupAddresses() {
|
||||
return listenAddresses;
|
||||
}
|
||||
|
||||
public final Set<GroupAddress> getWriteAddresses() {
|
||||
return writeAddresses;
|
||||
}
|
||||
|
||||
public final @Nullable OutboundSpec getCommandSpec(Type command) {
|
||||
logger.trace("getCommandSpec checking keys '{}' for command '{}' ({})", gaKeys, command, command.getClass());
|
||||
for (Map.Entry<String, GroupAddressConfiguration> entry : groupAddressConfigurations.entrySet()) {
|
||||
String dpt = Objects.requireNonNullElse(entry.getValue().getDPT(), getDefaultDPT(entry.getKey()));
|
||||
Set<Class<? extends Type>> expectedTypeClass = DPTUtil.getAllowedTypes(dpt);
|
||||
if (expectedTypeClass.contains(command.getClass())) {
|
||||
logger.trace("getCommandSpec key '{}' has expectedTypeClass '{}', matching command '{}' and dpt '{}'",
|
||||
entry.getKey(), expectedTypeClass, command, dpt);
|
||||
return new WriteSpecImpl(entry.getValue(), dpt, command);
|
||||
}
|
||||
}
|
||||
logger.trace("getCommandSpec no Spec found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
public final List<InboundSpec> getReadSpec() {
|
||||
return groupAddressConfigurations.entrySet().stream()
|
||||
.map(entry -> new ReadRequestSpecImpl(entry.getValue(), getDefaultDPT(entry.getKey())))
|
||||
.filter(spec -> !spec.getGroupAddresses().isEmpty()).collect(toList());
|
||||
}
|
||||
|
||||
public final @Nullable InboundSpec getListenSpec(GroupAddress groupAddress) {
|
||||
return groupAddressConfigurations.entrySet().stream()
|
||||
.map(entry -> new ListenSpecImpl(entry.getValue(), getDefaultDPT(entry.getKey())))
|
||||
.filter(spec -> spec.getGroupAddresses().contains(groupAddress)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public final @Nullable OutboundSpec getResponseSpec(GroupAddress groupAddress, Type value) {
|
||||
return groupAddressConfigurations.entrySet().stream()
|
||||
.map(entry -> new ReadResponseSpecImpl(entry.getValue(), getDefaultDPT(entry.getKey()), value))
|
||||
.filter(spec -> spec.matchesDestination(groupAddress)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
protected abstract String getDefaultDPT(String gaConfigKey);
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* Helper class to find the matching {@link KNXChannel} for any given {@link ChannelTypeUID}.
|
||||
*
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
* @author Jan N. Klug - Refactored to factory class
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KNXChannelFactory {
|
||||
|
||||
private static final Map<Set<String>, Function<Channel, KNXChannel>> TYPES = Map.ofEntries( //
|
||||
Map.entry(TypeColor.SUPPORTED_CHANNEL_TYPES, TypeColor::new), //
|
||||
Map.entry(TypeContact.SUPPORTED_CHANNEL_TYPES, TypeContact::new), //
|
||||
Map.entry(TypeDateTime.SUPPORTED_CHANNEL_TYPES, TypeDateTime::new), //
|
||||
Map.entry(TypeDimmer.SUPPORTED_CHANNEL_TYPES, TypeDimmer::new), //
|
||||
Map.entry(TypeNumber.SUPPORTED_CHANNEL_TYPES, TypeNumber::new), //
|
||||
Map.entry(TypeRollershutter.SUPPORTED_CHANNEL_TYPES, TypeRollershutter::new), //
|
||||
Map.entry(TypeString.SUPPORTED_CHANNEL_TYPES, TypeString::new), //
|
||||
Map.entry(TypeSwitch.SUPPORTED_CHANNEL_TYPES, TypeSwitch::new));
|
||||
|
||||
private KNXChannelFactory() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
public static KNXChannel createKnxChannel(Channel channel) throws IllegalArgumentException {
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
if (channelTypeUID == null) {
|
||||
throw new IllegalArgumentException("Could not determine ChannelTypeUID for channel " + channel.getUID());
|
||||
}
|
||||
|
||||
String channelType = channelTypeUID.getId();
|
||||
|
||||
Function<Channel, KNXChannel> supplier = TYPES.entrySet().stream().filter(e -> e.getKey().contains(channelType))
|
||||
.map(Map.Entry::getValue).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(channelTypeUID + " is not a valid channel type ID"));
|
||||
|
||||
return supplier.apply(channel);
|
||||
}
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
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.binding.knx.internal.KNXTypeMapper;
|
||||
import org.openhab.binding.knx.internal.client.InboundSpec;
|
||||
import org.openhab.binding.knx.internal.client.OutboundSpec;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
|
||||
/**
|
||||
* Meta-data abstraction for the KNX channel configurations.
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API.
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class KNXChannelType {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile(
|
||||
"^((?<dpt>[0-9]{1,3}\\.[0-9]{3,4}):)?(?<read>\\<)?(?<mainGA>[0-9]{1,5}(/[0-9]{1,4}){0,2})(?<listenGAs>(\\+(\\<?[0-9]{1,5}(/[0-9]{1,4}){0,2}))*)$");
|
||||
|
||||
private static final Pattern PATTERN_LISTEN = Pattern
|
||||
.compile("\\+((?<read>\\<)?(?<GA>[0-9]{1,5}(/[0-9]{1,4}){0,2}))");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KNXChannelType.class);
|
||||
private final Set<String> channelTypeIDs;
|
||||
|
||||
KNXChannelType(String... channelTypeIDs) {
|
||||
this.channelTypeIDs = new HashSet<>(Arrays.asList(channelTypeIDs));
|
||||
}
|
||||
|
||||
final Set<String> getChannelIDs() {
|
||||
return channelTypeIDs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected final ChannelConfiguration parse(@Nullable String fancy) {
|
||||
if (fancy == null) {
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = PATTERN.matcher(fancy.replace(" ", ""));
|
||||
|
||||
if (matcher.matches()) {
|
||||
// Listen GAs
|
||||
String input = matcher.group("listenGAs");
|
||||
Matcher m2 = PATTERN_LISTEN.matcher(input);
|
||||
List<GroupAddressConfiguration> listenGAs = new LinkedList<>();
|
||||
while (m2.find()) {
|
||||
listenGAs.add(new GroupAddressConfiguration(m2.group("GA"), m2.group("read") != null));
|
||||
}
|
||||
|
||||
// Main GA
|
||||
GroupAddressConfiguration mainGA = new GroupAddressConfiguration(matcher.group("mainGA"),
|
||||
matcher.group("read") != null);
|
||||
|
||||
return new ChannelConfiguration(matcher.group("dpt"), mainGA, listenGAs);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract Set<String> getAllGAKeys();
|
||||
|
||||
public final Set<GroupAddress> getListenAddresses(Configuration channelConfiguration) {
|
||||
Set<GroupAddress> ret = new HashSet<>();
|
||||
for (String key : getAllGAKeys()) {
|
||||
ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
|
||||
if (conf != null) {
|
||||
ret.addAll(conf.getListenGAs().stream().map(this::toGroupAddress).collect(toSet()));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public final Set<GroupAddress> getReadAddresses(Configuration channelConfiguration) {
|
||||
Set<GroupAddress> ret = new HashSet<>();
|
||||
for (String key : getAllGAKeys()) {
|
||||
ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
|
||||
if (conf != null) {
|
||||
ret.addAll(conf.getReadGAs().stream().map(this::toGroupAddress).collect(toSet()));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public final Set<GroupAddress> getWriteAddresses(Configuration channelConfiguration) {
|
||||
Set<GroupAddress> ret = new HashSet<>();
|
||||
for (String key : getAllGAKeys()) {
|
||||
ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
|
||||
if (conf != null) {
|
||||
GroupAddress ga = toGroupAddress(conf.getMainGA());
|
||||
if (ga != null) {
|
||||
ret.add(ga);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private @Nullable GroupAddress toGroupAddress(GroupAddressConfiguration ga) {
|
||||
try {
|
||||
return new GroupAddress(ga.getGA());
|
||||
} catch (KNXFormatException e) {
|
||||
logger.warn("Could not parse group address '{}'", ga.getGA());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final Set<GroupAddress> getAddresses(@Nullable Configuration configuration, Iterable<String> addresses)
|
||||
throws KNXFormatException {
|
||||
Set<GroupAddress> ret = new HashSet<>();
|
||||
for (String address : addresses) {
|
||||
if (configuration != null && configuration.get(address) != null) {
|
||||
ret.add(new GroupAddress((String) configuration.get(address)));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected final boolean isEquals(@Nullable Configuration configuration, String address, GroupAddress groupAddress)
|
||||
throws KNXFormatException {
|
||||
if (configuration != null && configuration.get(address) != null) {
|
||||
return Objects.equals(new GroupAddress((String) configuration.get(address)), groupAddress);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected final Set<String> asSet(String... values) {
|
||||
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(values)));
|
||||
}
|
||||
|
||||
public final @Nullable OutboundSpec getCommandSpec(Configuration configuration, KNXTypeMapper typeHelper,
|
||||
Type command) throws KNXFormatException {
|
||||
logger.trace("getCommandSpec testing Keys '{}' for command '{}'", getAllGAKeys(), command);
|
||||
for (String key : getAllGAKeys()) {
|
||||
ChannelConfiguration config = parse((String) configuration.get(key));
|
||||
if (config != null) {
|
||||
String dpt = config.getDPT();
|
||||
if (dpt == null) {
|
||||
dpt = getDefaultDPT(key);
|
||||
}
|
||||
Class<? extends Type> expectedTypeClass = typeHelper.toTypeClass(dpt);
|
||||
if (expectedTypeClass != null) {
|
||||
if (expectedTypeClass.isInstance(command)
|
||||
|| ((expectedTypeClass == DecimalType.class) && (command instanceof QuantityType))) {
|
||||
logger.trace(
|
||||
"getCommandSpec key '{}' uses expectedTypeClass '{}' which isInstance for command '{}' and dpt '{}'",
|
||||
key, expectedTypeClass, command, dpt);
|
||||
return new WriteSpecImpl(config, dpt, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.trace("getCommandSpec no Spec found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
public final List<InboundSpec> getReadSpec(Configuration configuration) throws KNXFormatException {
|
||||
return getAllGAKeys().stream()
|
||||
.map(key -> new ReadRequestSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key)))
|
||||
.filter(spec -> !spec.getGroupAddresses().isEmpty()).collect(toList());
|
||||
}
|
||||
|
||||
public final @Nullable InboundSpec getListenSpec(Configuration configuration, GroupAddress groupAddress) {
|
||||
Optional<ListenSpecImpl> result = getAllGAKeys().stream()
|
||||
.map(key -> new ListenSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key)))
|
||||
.filter(spec -> !spec.getGroupAddresses().isEmpty())
|
||||
.filter(spec -> spec.getGroupAddresses().contains(groupAddress)).findFirst();
|
||||
return result.isPresent() ? result.get() : null;
|
||||
}
|
||||
|
||||
protected abstract String getDefaultDPT(String gaConfigKey);
|
||||
|
||||
public final @Nullable OutboundSpec getResponseSpec(Configuration configuration, GroupAddress groupAddress,
|
||||
Type type) throws KNXFormatException {
|
||||
Optional<ReadResponseSpecImpl> result = getAllGAKeys().stream()
|
||||
.map(key -> new ReadResponseSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key), type))
|
||||
.filter(spec -> groupAddress.equals(spec.getGroupAddress())).findFirst();
|
||||
return result.isPresent() ? result.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return channelTypeIDs.toString();
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* Helper class to find the matching {@link KNXChannelType} for any given {@link ChannelTypeUID}.
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API.
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class KNXChannelTypes {
|
||||
|
||||
private static final Set<KNXChannelType> TYPES = Collections.unmodifiableSet(Stream.of(//
|
||||
new TypeColor(), //
|
||||
new TypeContact(), //
|
||||
new TypeDateTime(), //
|
||||
new TypeDimmer(), //
|
||||
new TypeNumber(), //
|
||||
new TypeRollershutter(), //
|
||||
new TypeString(), //
|
||||
new TypeSwitch() //
|
||||
).collect(toSet()));
|
||||
|
||||
private KNXChannelTypes() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
public static KNXChannelType getType(@Nullable ChannelTypeUID channelTypeUID) throws IllegalArgumentException {
|
||||
Objects.requireNonNull(channelTypeUID);
|
||||
for (KNXChannelType c : TYPES) {
|
||||
if (c.getChannelIDs().contains(channelTypeUID.getId())) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(channelTypeUID.getId() + " is not a valid value channel type ID");
|
||||
}
|
||||
}
|
@ -12,13 +12,10 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.knx.internal.client.InboundSpec;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
@ -30,21 +27,22 @@ import tuwien.auto.calimero.GroupAddress;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ListenSpecImpl extends AbstractSpec implements InboundSpec {
|
||||
public class ListenSpecImpl implements InboundSpec {
|
||||
private final String dpt;
|
||||
private final Set<GroupAddress> listenAddresses;
|
||||
|
||||
private final List<GroupAddress> listenAddresses;
|
||||
|
||||
public ListenSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT) {
|
||||
super(channelConfiguration, defaultDPT);
|
||||
if (channelConfiguration != null) {
|
||||
this.listenAddresses = channelConfiguration.getListenGAs().stream().map(this::toGroupAddress)
|
||||
.collect(toList());
|
||||
} else {
|
||||
this.listenAddresses = Collections.emptyList();
|
||||
}
|
||||
public ListenSpecImpl(GroupAddressConfiguration groupAddressConfiguration, String defaultDPT) {
|
||||
this.dpt = Objects.requireNonNullElse(groupAddressConfiguration.getDPT(), defaultDPT);
|
||||
this.listenAddresses = groupAddressConfiguration.getListenGAs();
|
||||
}
|
||||
|
||||
public List<GroupAddress> getGroupAddresses() {
|
||||
@Override
|
||||
public String getDPT() {
|
||||
return dpt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupAddress> getGroupAddresses() {
|
||||
return listenAddresses;
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,10 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.knx.internal.client.InboundSpec;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
@ -30,21 +27,22 @@ import tuwien.auto.calimero.GroupAddress;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ReadRequestSpecImpl extends AbstractSpec implements InboundSpec {
|
||||
public class ReadRequestSpecImpl implements InboundSpec {
|
||||
private final String dpt;
|
||||
private final Set<GroupAddress> readAddresses;
|
||||
|
||||
private final List<GroupAddress> readAddresses;
|
||||
|
||||
public ReadRequestSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT) {
|
||||
super(channelConfiguration, defaultDPT);
|
||||
if (channelConfiguration != null) {
|
||||
this.readAddresses = channelConfiguration.getReadGAs().stream().map(this::toGroupAddress).collect(toList());
|
||||
} else {
|
||||
this.readAddresses = Collections.emptyList();
|
||||
}
|
||||
public ReadRequestSpecImpl(GroupAddressConfiguration groupAddressConfiguration, String defaultDPT) {
|
||||
this.dpt = Objects.requireNonNullElse(groupAddressConfiguration.getDPT(), defaultDPT);
|
||||
this.readAddresses = groupAddressConfiguration.getReadGAs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupAddress> getGroupAddresses() {
|
||||
public String getDPT() {
|
||||
return dpt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupAddress> getGroupAddresses() {
|
||||
return readAddresses;
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.knx.internal.client.OutboundSpec;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
@ -26,28 +27,34 @@ import tuwien.auto.calimero.GroupAddress;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ReadResponseSpecImpl extends AbstractSpec implements OutboundSpec {
|
||||
public class ReadResponseSpecImpl implements OutboundSpec {
|
||||
private final String dpt;
|
||||
private final GroupAddress groupAddress;
|
||||
private final Type value;
|
||||
|
||||
private final @Nullable GroupAddress groupAddress;
|
||||
private final Type type;
|
||||
|
||||
public ReadResponseSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT, Type state) {
|
||||
super(channelConfiguration, defaultDPT);
|
||||
if (channelConfiguration != null) {
|
||||
this.groupAddress = toGroupAddress(channelConfiguration.getMainGA());
|
||||
} else {
|
||||
this.groupAddress = null;
|
||||
}
|
||||
this.type = state;
|
||||
public ReadResponseSpecImpl(GroupAddressConfiguration groupAddressConfiguration, String defaultDPT, Type state) {
|
||||
this.dpt = Objects.requireNonNullElse(groupAddressConfiguration.getDPT(), defaultDPT);
|
||||
this.groupAddress = groupAddressConfiguration.getMainGA();
|
||||
this.value = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GroupAddress getGroupAddress() {
|
||||
public String getDPT() {
|
||||
return dpt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupAddress getGroupAddress() {
|
||||
return groupAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type;
|
||||
public Type getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesDestination(GroupAddress groupAddress) {
|
||||
return groupAddress.equals(this.groupAddress);
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,17 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
|
||||
@ -32,15 +36,12 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorRGB;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeColor extends KNXChannelType {
|
||||
class TypeColor extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_COLOR, CHANNEL_COLOR_CONTROL);
|
||||
|
||||
TypeColor() {
|
||||
super(CHANNEL_COLOR, CHANNEL_COLOR_CONTROL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Stream.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA, HSB_GA).collect(toSet());
|
||||
TypeColor(Channel channel) {
|
||||
super(Set.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA, HSB_GA),
|
||||
List.of(HSBType.class, PercentType.class, OnOffType.class, IncreaseDecreaseType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,10 +14,12 @@ package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
|
||||
@ -28,15 +30,11 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeContact extends KNXChannelType {
|
||||
class TypeContact extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_CONTACT, CHANNEL_CONTACT_CONTROL);
|
||||
|
||||
TypeContact() {
|
||||
super(CHANNEL_CONTACT, CHANNEL_CONTACT_CONTROL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Collections.singleton(GA);
|
||||
TypeContact(Channel channel) {
|
||||
super(List.of(OpenClosedType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,10 +14,12 @@ package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime;
|
||||
|
||||
@ -28,15 +30,11 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeDateTime extends KNXChannelType {
|
||||
class TypeDateTime extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_DATETIME, CHANNEL_DATETIME_CONTROL);
|
||||
|
||||
TypeDateTime() {
|
||||
super(CHANNEL_DATETIME, CHANNEL_DATETIME_CONTROL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Collections.singleton(GA);
|
||||
TypeDateTime(Channel channel) {
|
||||
super(List.of(DateTimeType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,10 +14,15 @@ package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
|
||||
@ -30,15 +35,12 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeDimmer extends KNXChannelType {
|
||||
class TypeDimmer extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_DIMMER, CHANNEL_DIMMER_CONTROL);
|
||||
|
||||
TypeDimmer() {
|
||||
super(CHANNEL_DIMMER, CHANNEL_DIMMER_CONTROL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Set.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA);
|
||||
TypeDimmer(Channel channel) {
|
||||
super(Set.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA),
|
||||
List.of(PercentType.class, OnOffType.class, IncreaseDecreaseType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,10 +14,13 @@ package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
/**
|
||||
* number channel type description
|
||||
@ -26,19 +29,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeNumber extends KNXChannelType {
|
||||
class TypeNumber extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_NUMBER, CHANNEL_NUMBER_CONTROL);
|
||||
|
||||
TypeNumber() {
|
||||
super(CHANNEL_NUMBER, CHANNEL_NUMBER_CONTROL);
|
||||
TypeNumber(Channel channel) {
|
||||
super(List.of(DecimalType.class, QuantityType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultDPT(String gaConfigKey) {
|
||||
return "9.001";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Collections.singleton(GA);
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,15 @@ package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
@ -29,10 +34,13 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeRollershutter extends KNXChannelType {
|
||||
class TypeRollershutter extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_ROLLERSHUTTER,
|
||||
CHANNEL_ROLLERSHUTTER_CONTROL);
|
||||
|
||||
TypeRollershutter() {
|
||||
super(CHANNEL_ROLLERSHUTTER, CHANNEL_ROLLERSHUTTER_CONTROL);
|
||||
TypeRollershutter(Channel channel) {
|
||||
super(Set.of(UP_DOWN_GA, STOP_MOVE_GA, POSITION_GA),
|
||||
List.of(PercentType.class, UpDownType.class, StopMoveType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,9 +56,4 @@ class TypeRollershutter extends KNXChannelType {
|
||||
}
|
||||
throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Set.of(UP_DOWN_GA, STOP_MOVE_GA, POSITION_GA);
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,12 @@ package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorString;
|
||||
|
||||
@ -28,15 +30,11 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorString;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeString extends KNXChannelType {
|
||||
class TypeString extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_STRING, CHANNEL_STRING_CONTROL);
|
||||
|
||||
TypeString() {
|
||||
super(CHANNEL_STRING, CHANNEL_STRING_CONTROL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Collections.singleton(GA);
|
||||
TypeString(Channel channel) {
|
||||
super(List.of(StringType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,10 +14,12 @@ package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
|
||||
@ -28,15 +30,11 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class TypeSwitch extends KNXChannelType {
|
||||
class TypeSwitch extends KNXChannel {
|
||||
public static final Set<String> SUPPORTED_CHANNEL_TYPES = Set.of(CHANNEL_SWITCH, CHANNEL_SWITCH_CONTROL);
|
||||
|
||||
TypeSwitch() {
|
||||
super(CHANNEL_SWITCH, CHANNEL_SWITCH_CONTROL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Collections.singleton(GA);
|
||||
TypeSwitch(Channel channel) {
|
||||
super(List.of(OnOffType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -12,13 +12,13 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.knx.internal.client.OutboundSpec;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
|
||||
/**
|
||||
* Command meta-data
|
||||
@ -27,29 +27,34 @@ import tuwien.auto.calimero.KNXFormatException;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WriteSpecImpl extends AbstractSpec implements OutboundSpec {
|
||||
public class WriteSpecImpl implements OutboundSpec {
|
||||
private final String dpt;
|
||||
private final Type value;
|
||||
private final GroupAddress groupAddress;
|
||||
|
||||
private final Type type;
|
||||
private final @Nullable GroupAddress groupAddress;
|
||||
|
||||
public WriteSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT, Type type)
|
||||
throws KNXFormatException {
|
||||
super(channelConfiguration, defaultDPT);
|
||||
if (channelConfiguration != null) {
|
||||
this.groupAddress = new GroupAddress(channelConfiguration.getMainGA().getGA());
|
||||
} else {
|
||||
this.groupAddress = null;
|
||||
}
|
||||
this.type = type;
|
||||
public WriteSpecImpl(GroupAddressConfiguration groupAddressConfiguration, String defaultDPT, Type value) {
|
||||
this.dpt = Objects.requireNonNullElse(groupAddressConfiguration.getDPT(), defaultDPT);
|
||||
this.groupAddress = groupAddressConfiguration.getMainGA();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type;
|
||||
public String getDPT() {
|
||||
return dpt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GroupAddress getGroupAddress() {
|
||||
public Type getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupAddress getGroupAddress() {
|
||||
return groupAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesDestination(GroupAddress groupAddress) {
|
||||
return groupAddress.equals(this.groupAddress);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.client;
|
||||
|
||||
import static org.openhab.binding.knx.internal.dpt.DPTUtil.NORMALIZED_DPT;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -25,8 +27,7 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.knx.internal.KNXTypeMapper;
|
||||
import org.openhab.binding.knx.internal.dpt.KNXCoreTypeMapper;
|
||||
import org.openhab.binding.knx.internal.dpt.ValueEncoder;
|
||||
import org.openhab.binding.knx.internal.handler.GroupAddressListener;
|
||||
import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
@ -82,7 +83,6 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
private static final int MAX_SEND_ATTEMPTS = 2;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractKNXClient.class);
|
||||
private final KNXTypeMapper typeHelper = new KNXCoreTypeMapper();
|
||||
|
||||
private final ThingUID thingUID;
|
||||
private final int responseTimeout;
|
||||
@ -119,23 +119,20 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
|
||||
@Override
|
||||
public void groupWrite(ProcessEvent e) {
|
||||
processEvent("Group Write", e, (listener, source, destination, asdu) -> {
|
||||
listener.onGroupWrite(AbstractKNXClient.this, source, destination, asdu);
|
||||
});
|
||||
processEvent("Group Write", e, (listener, source, destination, asdu) -> listener
|
||||
.onGroupWrite(AbstractKNXClient.this, source, destination, asdu));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void groupReadRequest(ProcessEvent e) {
|
||||
processEvent("Group Read Request", e, (listener, source, destination, asdu) -> {
|
||||
listener.onGroupRead(AbstractKNXClient.this, source, destination, asdu);
|
||||
});
|
||||
processEvent("Group Read Request", e, (listener, source, destination, asdu) -> listener
|
||||
.onGroupRead(AbstractKNXClient.this, source, destination, asdu));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void groupReadResponse(ProcessEvent e) {
|
||||
processEvent("Group Read Response", e, (listener, source, destination, asdu) -> {
|
||||
listener.onGroupReadResponse(AbstractKNXClient.this, source, destination, asdu);
|
||||
});
|
||||
processEvent("Group Read Response", e, (listener, source, destination, asdu) -> listener
|
||||
.onGroupReadResponse(AbstractKNXClient.this, source, destination, asdu));
|
||||
}
|
||||
};
|
||||
|
||||
@ -151,21 +148,16 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (!scheduleReconnectJob()) {
|
||||
connect();
|
||||
}
|
||||
connect();
|
||||
}
|
||||
|
||||
private boolean scheduleReconnectJob() {
|
||||
private void scheduleReconnectJob() {
|
||||
if (autoReconnectPeriod > 0) {
|
||||
// schedule connect job, for the first connection ignore autoReconnectPeriod and use 1 sec
|
||||
final long reconnectDelayS = (state == ClientState.INIT) ? 1 : autoReconnectPeriod;
|
||||
final String prefix = (state == ClientState.INIT) ? "re" : "";
|
||||
logger.debug("Bridge {} scheduling {}connect in {}s", thingUID, prefix, reconnectDelayS);
|
||||
connectJob = knxScheduler.schedule(this::connect, reconnectDelayS, TimeUnit.SECONDS);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,7 +173,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
|
||||
private synchronized boolean connectIfNotAutomatic() {
|
||||
if (!isConnected()) {
|
||||
return connectJob != null ? false : connect();
|
||||
return connectJob == null && connect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -241,15 +233,14 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
|
||||
// ProcessCommunicationResponder provides responses to requests from KNX bus (Calimero).
|
||||
// Note for KNX Secure: SAL to be provided
|
||||
ProcessCommunicationResponder responseCommunicator = new ProcessCommunicationResponder(link,
|
||||
this.responseCommunicator = new ProcessCommunicationResponder(link,
|
||||
new SecureApplicationLayer(link, Security.defaultInstallation()));
|
||||
this.responseCommunicator = responseCommunicator;
|
||||
|
||||
// register this class, callbacks will be triggered
|
||||
link.addLinkListener(this);
|
||||
|
||||
// create a job carrying out read requests
|
||||
busJob = knxScheduler.scheduleWithFixedDelay(() -> readNextQueuedDatapoint(), 0, readingPause,
|
||||
busJob = knxScheduler.scheduleWithFixedDelay(this::readNextQueuedDatapoint, 0, readingPause,
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
statusUpdateCallback.updateStatus(ThingStatus.ONLINE);
|
||||
@ -314,9 +305,9 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
pc.detach();
|
||||
});
|
||||
deviceInfoClient = null;
|
||||
managementClient = nullify(managementClient, mc -> mc.detach());
|
||||
managementProcedures = nullify(managementProcedures, mp -> mp.detach());
|
||||
link = nullify(link, l -> l.close());
|
||||
managementClient = nullify(managementClient, ManagementClient::detach);
|
||||
managementProcedures = nullify(managementProcedures, ManagementProcedures::detach);
|
||||
link = nullify(link, KNXNetworkLink::close);
|
||||
logger.trace("Bridge {} disconnected from KNX bus", thingUID);
|
||||
}
|
||||
|
||||
@ -339,18 +330,6 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a {@link Type} into a datapoint type value for the KNX bus.
|
||||
*
|
||||
* @param type the {@link Type} to transform
|
||||
* @param dpt the datapoint type to which should be converted
|
||||
* @return the corresponding KNX datapoint type value as a string
|
||||
*/
|
||||
@Nullable
|
||||
private String toDPTValue(Type type, String dpt) {
|
||||
return typeHelper.toDPTValue(type, dpt);
|
||||
}
|
||||
|
||||
// datapoint is null at end of the list, warning is misleading
|
||||
@SuppressWarnings("null")
|
||||
private void readNextQueuedDatapoint() {
|
||||
@ -380,7 +359,6 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
}
|
||||
} catch (InterruptedException | CancellationException e) {
|
||||
logger.debug("Interrupted sending KNX read request");
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// Any other exception: Fail gracefully, i.e. notify user and continue reading next DP.
|
||||
// Not catching this would end the scheduled read for all DPs in case of an error.
|
||||
@ -469,13 +447,13 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean registerGroupAddressListener(GroupAddressListener listener) {
|
||||
return groupAddressListeners.add(listener);
|
||||
public final void registerGroupAddressListener(GroupAddressListener listener) {
|
||||
groupAddressListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean unregisterGroupAddressListener(GroupAddressListener listener) {
|
||||
return groupAddressListeners.remove(listener);
|
||||
public final void unregisterGroupAddressListener(GroupAddressListener listener) {
|
||||
groupAddressListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -499,7 +477,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
ProcessCommunicator processCommunicator = this.processCommunicator;
|
||||
KNXNetworkLink link = this.link;
|
||||
if (processCommunicator == null || link == null) {
|
||||
logger.debug("Cannot write to KNX bus (processCommuicator: {}, link: {})",
|
||||
logger.debug("Cannot write to KNX bus (processCommunicator: {}, link: {})",
|
||||
processCommunicator == null ? "Not OK" : "OK",
|
||||
link == null ? "Not OK" : (link.isOpen() ? "Open" : "Closed"));
|
||||
return;
|
||||
@ -508,9 +486,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
|
||||
logger.trace("writeToKNX groupAddress '{}', commandSpec '{}'", groupAddress, commandSpec);
|
||||
|
||||
if (groupAddress != null) {
|
||||
sendToKNX(processCommunicator, link, groupAddress, commandSpec.getDPT(), commandSpec.getType());
|
||||
}
|
||||
sendToKNX(processCommunicator, groupAddress, commandSpec.getDPT(), commandSpec.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -527,27 +503,26 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
|
||||
logger.trace("respondToKNX groupAddress '{}', responseSpec '{}'", groupAddress, responseSpec);
|
||||
|
||||
if (groupAddress != null) {
|
||||
sendToKNX(responseCommunicator, link, groupAddress, responseSpec.getDPT(), responseSpec.getType());
|
||||
}
|
||||
sendToKNX(responseCommunicator, groupAddress, responseSpec.getDPT(), responseSpec.getValue());
|
||||
}
|
||||
|
||||
private void sendToKNX(ProcessCommunication communicator, KNXNetworkLink link, GroupAddress groupAddress,
|
||||
String dpt, Type type) throws KNXException {
|
||||
private void sendToKNX(ProcessCommunication communicator, GroupAddress groupAddress, String dpt, Type type)
|
||||
throws KNXException {
|
||||
if (!connectIfNotAutomatic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Datapoint datapoint = new CommandDP(groupAddress, thingUID.toString(), 0, dpt);
|
||||
String mappedValue = toDPTValue(type, dpt);
|
||||
|
||||
logger.trace("sendToKNX mappedValue: '{}' groupAddress: '{}'", mappedValue, groupAddress);
|
||||
|
||||
Datapoint datapoint = new CommandDP(groupAddress, thingUID.toString(), 0,
|
||||
NORMALIZED_DPT.getOrDefault(dpt, dpt));
|
||||
String mappedValue = ValueEncoder.encode(type, dpt);
|
||||
if (mappedValue == null) {
|
||||
logger.debug("Value '{}' cannot be mapped to datapoint '{}'", type, datapoint);
|
||||
logger.debug("Value '{}' of type '{}' cannot be mapped to datapoint '{}'", type, type.getClass(),
|
||||
datapoint);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < MAX_SEND_ATTEMPTS; i++) {
|
||||
logger.trace("sendToKNX mappedValue: '{}' groupAddress: '{}'", mappedValue, groupAddress);
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
try {
|
||||
communicator.write(datapoint, mappedValue);
|
||||
logger.debug("Wrote value '{}' to datapoint '{}' ({}. attempt).", type, datapoint, i);
|
||||
|
@ -33,7 +33,7 @@ public interface BusMessageListener {
|
||||
* @param destination
|
||||
* @param asdu
|
||||
*/
|
||||
public void onGroupWrite(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu);
|
||||
void onGroupWrite(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu);
|
||||
|
||||
/**
|
||||
* Called when the KNX bridge receives a group read telegram
|
||||
@ -43,7 +43,7 @@ public interface BusMessageListener {
|
||||
* @param destination
|
||||
* @param asdu
|
||||
*/
|
||||
public void onGroupRead(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu);
|
||||
void onGroupRead(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu);
|
||||
|
||||
/**
|
||||
* Called when the KNX bridge receives a group read response telegram
|
||||
@ -53,6 +53,5 @@ public interface BusMessageListener {
|
||||
* @param destination
|
||||
* @param asdu
|
||||
*/
|
||||
public void onGroupReadResponse(AbstractKNXClient client, IndividualAddress source, GroupAddress destination,
|
||||
byte[] asdu);
|
||||
void onGroupReadResponse(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
@ -39,5 +39,5 @@ public interface InboundSpec {
|
||||
*
|
||||
* @return a list of group addresses.
|
||||
*/
|
||||
List<GroupAddress> getGroupAddresses();
|
||||
Set<GroupAddress> getGroupAddresses();
|
||||
}
|
||||
|
@ -64,17 +64,15 @@ public interface KNXClient {
|
||||
* Register the given listener to be informed on KNX bus traffic.
|
||||
*
|
||||
* @param listener the listener
|
||||
* @return {@code true} if it wasn't registered before
|
||||
*/
|
||||
boolean registerGroupAddressListener(GroupAddressListener listener);
|
||||
void registerGroupAddressListener(GroupAddressListener listener);
|
||||
|
||||
/**
|
||||
* Remove the given listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
* @return {@code true} if it was successfully removed
|
||||
*/
|
||||
boolean unregisterGroupAddressListener(GroupAddressListener listener);
|
||||
void unregisterGroupAddressListener(GroupAddressListener listener);
|
||||
|
||||
/**
|
||||
* Schedule the given data point for asynchronous reading.
|
||||
|
@ -49,13 +49,11 @@ public class NoOpClient implements KNXClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerGroupAddressListener(GroupAddressListener listener) {
|
||||
return false;
|
||||
public void registerGroupAddressListener(GroupAddressListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterGroupAddressListener(GroupAddressListener listener) {
|
||||
return false;
|
||||
public void unregisterGroupAddressListener(GroupAddressListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -13,7 +13,6 @@
|
||||
package org.openhab.binding.knx.internal.client;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
@ -39,7 +38,6 @@ public interface OutboundSpec {
|
||||
*
|
||||
* @return the group address
|
||||
*/
|
||||
@Nullable
|
||||
GroupAddress getGroupAddress();
|
||||
|
||||
/**
|
||||
@ -47,5 +45,13 @@ public interface OutboundSpec {
|
||||
*
|
||||
* @return the command/state
|
||||
*/
|
||||
Type getType();
|
||||
Type getValue();
|
||||
|
||||
/**
|
||||
* Check if group address to be used matches a given group address.
|
||||
*
|
||||
* @param groupAddress group address to be compared
|
||||
* @return true if addresses match
|
||||
*/
|
||||
boolean matchesDestination(GroupAddress groupAddress);
|
||||
}
|
||||
|
@ -19,23 +19,29 @@ import org.openhab.core.thing.ThingStatusDetail;
|
||||
/**
|
||||
* Callback interface which enables the KNXClient implementations to update the thing status.
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API.
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface StatusUpdateCallback {
|
||||
|
||||
/**
|
||||
* see BaseThingHandler
|
||||
* Updates the status of the thing.
|
||||
*
|
||||
* @param status
|
||||
* see {@link org.openhab.core.thing.binding.BaseThingHandler}
|
||||
*
|
||||
* @param status the status
|
||||
*/
|
||||
void updateStatus(ThingStatus status);
|
||||
|
||||
/**
|
||||
* see BaseThingHandler
|
||||
* Updates the status of the thing.
|
||||
*
|
||||
* @param status
|
||||
* see {@link org.openhab.core.thing.binding.BaseThingHandler}
|
||||
*
|
||||
* @param status the status
|
||||
* @param statusDetail the detail of the status
|
||||
* @param description the description of the status
|
||||
*/
|
||||
void updateStatus(ThingStatus status, ThingStatusDetail thingStatusDetail, String message);
|
||||
void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, String description);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
/**
|
||||
* {@link org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler} configuration
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
|
@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceConfig {
|
||||
|
||||
private String address = "";
|
||||
private boolean fetch = false;
|
||||
private int pingInterval = 0;
|
||||
|
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.dpt;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPT;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator2ByteFloat;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator2ByteUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteSigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator64BitSigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitSigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DptXlator2ByteSigned;
|
||||
|
||||
/**
|
||||
* This class provides the units for values depending on the DPT (if available)
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DPTUnits {
|
||||
private static final Map<String, String> DPT_UNIT_MAP = new HashMap<>();
|
||||
|
||||
private DPTUnits() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* get unit string for a given DPT
|
||||
*
|
||||
* @param dptId the KNX DPT
|
||||
* @return unit string
|
||||
*/
|
||||
public static @Nullable String getUnitForDpt(String dptId) {
|
||||
return DPT_UNIT_MAP.get(dptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* for testing purposes only
|
||||
*
|
||||
* @return stream of all unit strings
|
||||
*/
|
||||
static Stream<String> getAllUnitStrings() {
|
||||
return DPT_UNIT_MAP.values().stream();
|
||||
}
|
||||
|
||||
static {
|
||||
// try to get units from Calimeros "unit" field in DPTXlators
|
||||
List<Class<? extends DPTXlator>> translators = List.of(DPTXlator2ByteUnsigned.class, DptXlator2ByteSigned.class,
|
||||
DPTXlator2ByteFloat.class, DPTXlator4ByteUnsigned.class, DPTXlator4ByteSigned.class,
|
||||
DPTXlator4ByteFloat.class, DPTXlator64BitSigned.class);
|
||||
|
||||
for (Class<? extends DPTXlator> translator : translators) {
|
||||
Field[] fields = translator.getFields();
|
||||
for (Field field : fields) {
|
||||
try {
|
||||
Object o = field.get(null);
|
||||
if (o instanceof DPT) {
|
||||
DPT dpt = (DPT) o;
|
||||
String unit = dpt.getUnit().replaceAll(" ", "");
|
||||
// Calimero provides some units (like "ms⁻²") that can't be parsed by our library because of the
|
||||
// negative exponent
|
||||
// replace with /
|
||||
int index = unit.indexOf("⁻");
|
||||
if (index != -1) {
|
||||
unit = unit.substring(0, index - 1) + "/" + unit.substring(index - 1).replace("⁻", "");
|
||||
}
|
||||
if (!unit.isEmpty()) {
|
||||
DPT_UNIT_MAP.put(dpt.getID(), unit);
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override/fix units where Calimero data is unparsable or missing
|
||||
|
||||
// 8 bit unsigned (DPT 5)
|
||||
DPT_UNIT_MAP.put(DPTXlator8BitUnsigned.DPT_SCALING.getID(), Units.PERCENT.getSymbol()); // required to ensure
|
||||
// correct conversion
|
||||
DPT_UNIT_MAP.put(DPTXlator8BitUnsigned.DPT_ANGLE.getID(), "°"); // Calimero returns Unicode
|
||||
DPT_UNIT_MAP.put(DPTXlator8BitUnsigned.DPT_PERCENT_U8.getID(), Units.PERCENT.getSymbol()); // required to ensure
|
||||
// correct conversion
|
||||
|
||||
// 8bit signed (DPT 6)
|
||||
DPT_UNIT_MAP.put(DPTXlator8BitSigned.DPT_PERCENT_V8.getID(), Units.PERCENT.getSymbol()); // required to ensure
|
||||
// correct conversion
|
||||
|
||||
// two byte unsigned (DPT 7)
|
||||
DPT_UNIT_MAP.remove(DPTXlator2ByteUnsigned.DPT_VALUE_2_UCOUNT.getID()); // counts have no unit
|
||||
DPT_UNIT_MAP.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_10.getID(), "ms"); // according to spec, it is ms
|
||||
DPT_UNIT_MAP.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_100.getID(), "ms"); // according to spec, it is ms
|
||||
|
||||
// two byte signed (DPT 8)
|
||||
DPT_UNIT_MAP.remove(DptXlator2ByteSigned.DptValueCount.getID()); // pulses habe no unit
|
||||
|
||||
// 4 byte unsigned (DPT 12)
|
||||
DPT_UNIT_MAP.remove(DPTXlator4ByteUnsigned.DPT_VALUE_4_UCOUNT.getID()); // counts have no unit
|
||||
|
||||
// 4 byte signed (DPT 13)
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteSigned.DPT_REACTIVE_ENERGY.getID(), Units.VAR_HOUR.toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteSigned.DPT_REACTIVE_ENERGY_KVARH.getID(), Units.KILOVAR_HOUR.toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteSigned.DPT_APPARENT_ENERGY_KVAH.getID(),
|
||||
Units.KILOVOLT_AMPERE.multiply(Units.HOUR).toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteSigned.DPT_FLOWRATE.getID(), Units.CUBICMETRE_PER_HOUR.toString());
|
||||
DPT_UNIT_MAP.remove(DPTXlator4ByteSigned.DPT_COUNT.getID()); // counts have no unit
|
||||
|
||||
// four byte float (DPT 14)
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_CONDUCTANCE.getID(), Units.SIEMENS.toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_ANGULAR_MOMENTUM.getID(),
|
||||
Units.JOULE.multiply(Units.SECOND).toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_ACTIVITY.getID(), Units.BECQUEREL.toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_ELECTRICAL_CONDUCTIVITY.getID(),
|
||||
Units.SIEMENS.divide(SIUnits.METRE).toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_TORQUE.getID(), Units.NEWTON.multiply(SIUnits.METRE).toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_RESISTIVITY.getID(), Units.OHM.multiply(SIUnits.METRE).toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_ELECTRIC_DIPOLEMOMENT.getID(),
|
||||
Units.COULOMB.multiply(SIUnits.METRE).toString());
|
||||
// use definition based on SI units (just rewrite Vm to V*m);
|
||||
// another common definition uses C, to be handled in encoder
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_ELECTRIC_FLUX.getID(), Units.VOLT.multiply(SIUnits.METRE).toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_MAGNETIC_MOMENT.getID(),
|
||||
Units.AMPERE.multiply(SIUnits.SQUARE_METRE).toString());
|
||||
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_ELECTROMAGNETIC_MOMENT.getID(),
|
||||
Units.AMPERE.multiply(SIUnits.SQUARE_METRE).toString());
|
||||
|
||||
// 64 bit signed (DPT 29)
|
||||
DPT_UNIT_MAP.put(DPTXlator64BitSigned.DPT_REACTIVE_ENERGY.getID(), Units.VAR_HOUR.toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.dpt;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitSigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorString;
|
||||
|
||||
/**
|
||||
* This class provides support to determine compatibility between KNX DPTs and openHAB data types
|
||||
*
|
||||
* Parts of this code are based on the openHAB KNXCoreTypeMapper by Kai Kreuzer et al.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DPTUtil {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DPTUtil.class);
|
||||
|
||||
// DPT: "123.001", 1-3 digits main type (no leading zero), optional sub-type 3-4 digits (leading zeros allowed)
|
||||
public static final Pattern DPT_PATTERN = Pattern.compile("^(?<main>[1-9][0-9]{0,2})(?:\\.(?<sub>\\d{3,5}))?$");
|
||||
|
||||
// used to map vendor-specific data to standard DPT
|
||||
public static final Map<String, String> NORMALIZED_DPT = Map.of(//
|
||||
"232.60000", "232.600");
|
||||
|
||||
// fall back if no specific type is defined in DPT_TYPE_MAP
|
||||
private static final Map<String, Set<Class<? extends Type>>> DPT_MAIN_TYPE_MAP = Map.ofEntries( //
|
||||
Map.entry("1", Set.of(OnOffType.class)), //
|
||||
Map.entry("2", Set.of(DecimalType.class)), //
|
||||
Map.entry("3", Set.of(IncreaseDecreaseType.class)), //
|
||||
Map.entry("4", Set.of(StringType.class)), //
|
||||
Map.entry("5", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("6", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("7", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("8", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("9", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("10", Set.of(DateTimeType.class)), //
|
||||
Map.entry("11", Set.of(DateTimeType.class)), //
|
||||
Map.entry("12", Set.of(DecimalType.class)), //
|
||||
Map.entry("13", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("14", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("16", Set.of(StringType.class)), //
|
||||
Map.entry("17", Set.of(DecimalType.class)), //
|
||||
Map.entry("18", Set.of(DecimalType.class)), //
|
||||
Map.entry("19", Set.of(DateTimeType.class)), //
|
||||
Map.entry("20", Set.of(StringType.class)), //
|
||||
Map.entry("21", Set.of(StringType.class)), //
|
||||
Map.entry("22", Set.of(StringType.class)), //
|
||||
Map.entry("28", Set.of(StringType.class)), //
|
||||
Map.entry("29", Set.of(QuantityType.class, DecimalType.class)), //
|
||||
Map.entry("229", Set.of(DecimalType.class)), //
|
||||
Map.entry("232", Set.of(HSBType.class)), //
|
||||
Map.entry("242", Set.of(HSBType.class)), //
|
||||
Map.entry("251", Set.of(HSBType.class, PercentType.class)));
|
||||
|
||||
// compatible types for full DPTs
|
||||
private static final Map<String, Set<Class<? extends Type>>> DPT_TYPE_MAP = Map.ofEntries(
|
||||
Map.entry(DPTXlatorBoolean.DPT_UPDOWN.getID(), Set.of(UpDownType.class)), //
|
||||
Map.entry(DPTXlatorBoolean.DPT_OPENCLOSE.getID(), Set.of(OpenClosedType.class)), //
|
||||
Map.entry(DPTXlatorBoolean.DPT_START.getID(), Set.of(StopMoveType.class)), //
|
||||
Map.entry(DPTXlatorBoolean.DPT_WINDOW_DOOR.getID(), Set.of(OpenClosedType.class)), //
|
||||
Map.entry(DPTXlatorBoolean.DPT_SCENE_AB.getID(), Set.of(DecimalType.class)), //
|
||||
Map.entry(DPTXlator3BitControlled.DPT_CONTROL_BLINDS.getID(), Set.of(UpDownType.class)), //
|
||||
Map.entry(DPTXlator8BitUnsigned.DPT_SCALING.getID(),
|
||||
Set.of(QuantityType.class, DecimalType.class, PercentType.class)), //
|
||||
Map.entry(DPTXlator8BitSigned.DPT_STATUS_MODE3.getID(), Set.of(StringType.class)), //
|
||||
Map.entry(DPTXlatorString.DPT_STRING_8859_1.getID(), Set.of(StringType.class)), //
|
||||
Map.entry(DPTXlatorString.DPT_STRING_ASCII.getID(), Set.of(StringType.class)));
|
||||
|
||||
private DPTUtil() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* get allowed openHAB types for given DPT
|
||||
*
|
||||
* @param dptId the datapoint type id
|
||||
* @return Set of supported openHAB types (command or state)
|
||||
*/
|
||||
public static Set<Class<? extends Type>> getAllowedTypes(String dptId) {
|
||||
Set<Class<? extends Type>> allowedTypes = DPT_TYPE_MAP.get(dptId);
|
||||
if (allowedTypes == null) {
|
||||
Matcher m = DPT_PATTERN.matcher(dptId);
|
||||
if (!m.matches()) {
|
||||
LOGGER.warn("getAllowedTypes couldn't identify main number in dptID '{}'", dptId);
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
allowedTypes = DPT_MAIN_TYPE_MAP.getOrDefault(m.group("main"), Set.of());
|
||||
}
|
||||
return allowedTypes;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,382 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.dpt;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.disableUoM;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
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.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.core.util.ColorUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tuwien.auto.calimero.KNXException;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
import tuwien.auto.calimero.KNXIllegalArgumentException;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator1BitControlled;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorSceneControl;
|
||||
import tuwien.auto.calimero.dptxlator.TranslatorTypes;
|
||||
|
||||
/**
|
||||
* This class decodes raw data received from the KNX bus to an openHAB datatype
|
||||
*
|
||||
* Parts of this code are based on the openHAB KNXCoreTypeMapper by Kai Kreuzer et al.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ValueDecoder {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ValueDecoder.class);
|
||||
|
||||
private static final String TIME_DAY_FORMAT = "EEE, HH:mm:ss";
|
||||
private static final String TIME_FORMAT = "HH:mm:ss";
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd";
|
||||
// RGB: "r:123 g:123 b:123" value-range: 0-255
|
||||
private static final Pattern RGB_PATTERN = Pattern.compile("r:(?<r>\\d+) g:(?<g>\\d+) b:(?<b>\\d+)");
|
||||
// RGBW: "100 27 25 12 %", value range: 0-100, invalid values: "-"
|
||||
private static final Pattern RGBW_PATTERN = Pattern
|
||||
.compile("(?:(?<r>[\\d,.]+)|-)\\s(?:(?<g>[\\d,.]+)|-)\\s(?:(?<b>[\\d,.]+)|-)\\s(?:(?<w>[\\d,.]+)|-)\\s%");
|
||||
// xyY: "(0,123 0,123) 56 %", value range 0-1 for xy (comma as decimal point), 0-100 for Y, invalid values omitted
|
||||
private static final Pattern XYY_PATTERN = Pattern
|
||||
.compile("(?:\\((?<x>\\d+(?:,\\d+)?) (?<y>\\d+(?:,\\d+)?)\\))?\\s*(?:(?<Y>\\d+(?:,\\d+)?)\\s%)?");
|
||||
|
||||
/**
|
||||
* convert the raw value received to the corresponding openHAB value
|
||||
*
|
||||
* @param dptId the DPT of the given data
|
||||
* @param data a byte array containing the value
|
||||
* @param preferredType the preferred datatype for this conversion
|
||||
* @return the data converted to an openHAB Type (or null if conversion failed)
|
||||
*/
|
||||
public static @Nullable Type decode(String dptId, byte[] data, Class<? extends Type> preferredType) {
|
||||
try {
|
||||
DPTXlator translator = TranslatorTypes.createTranslator(0,
|
||||
DPTUtil.NORMALIZED_DPT.getOrDefault(dptId, dptId));
|
||||
translator.setData(data);
|
||||
String value = translator.getValue();
|
||||
|
||||
String id = dptId; // prefer using the user-supplied DPT
|
||||
|
||||
Matcher m = DPTUtil.DPT_PATTERN.matcher(id);
|
||||
if (!m.matches() || m.groupCount() != 2) {
|
||||
LOGGER.trace("User-Supplied DPT '{}' did not match for sub-type, using DPT returned from Translator",
|
||||
id);
|
||||
id = translator.getType().getID();
|
||||
m = DPTUtil.DPT_PATTERN.matcher(id);
|
||||
if (!m.matches() || m.groupCount() != 2) {
|
||||
LOGGER.warn("Couldn't identify main/sub number in dptID '{}'", id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
LOGGER.trace("Finally using datapoint DPT = {}", id);
|
||||
|
||||
String mainType = m.group("main");
|
||||
String subType = m.group("sub");
|
||||
|
||||
switch (mainType) {
|
||||
case "1":
|
||||
return handleDpt1(subType, translator);
|
||||
case "2":
|
||||
DPTXlator1BitControlled translator1BitControlled = (DPTXlator1BitControlled) translator;
|
||||
int decValue = (translator1BitControlled.getControlBit() ? 2 : 0)
|
||||
+ (translator1BitControlled.getValueBit() ? 1 : 0);
|
||||
return new DecimalType(decValue);
|
||||
case "3":
|
||||
return handleDpt3(subType, translator);
|
||||
case "10":
|
||||
return handleDpt10(value);
|
||||
case "11":
|
||||
return DateTimeType.valueOf(new SimpleDateFormat(DateTimeType.DATE_PATTERN)
|
||||
.format(new SimpleDateFormat(DATE_FORMAT).parse(value)));
|
||||
case "18":
|
||||
DPTXlatorSceneControl translatorSceneControl = (DPTXlatorSceneControl) translator;
|
||||
int decimalValue = translatorSceneControl.getSceneNumber();
|
||||
if (value.startsWith("learn")) {
|
||||
decimalValue += 0x80;
|
||||
}
|
||||
return new DecimalType(decimalValue);
|
||||
case "19":
|
||||
return handleDpt19(translator);
|
||||
case "16":
|
||||
case "20":
|
||||
case "21":
|
||||
case "22":
|
||||
case "28":
|
||||
return StringType.valueOf(value);
|
||||
case "232":
|
||||
return handleDpt232(value, subType);
|
||||
case "242":
|
||||
return handleDpt242(value);
|
||||
case "251":
|
||||
return handleDpt251(value, preferredType);
|
||||
default:
|
||||
return handleNumericDpt(id, translator, preferredType);
|
||||
}
|
||||
} catch (NumberFormatException | KNXFormatException | KNXIllegalArgumentException | ParseException e) {
|
||||
LOGGER.info("Translator couldn't parse data '{}' for datapoint type '{}' ({}).", data, dptId, e.getClass());
|
||||
} catch (KNXException e) {
|
||||
LOGGER.warn("Failed creating a translator for datapoint type '{}'.", dptId, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Type handleDpt1(String subType, DPTXlator translator) {
|
||||
DPTXlatorBoolean translatorBoolean = (DPTXlatorBoolean) translator;
|
||||
switch (subType) {
|
||||
case "008":
|
||||
return translatorBoolean.getValueBoolean() ? UpDownType.DOWN : UpDownType.UP;
|
||||
case "009":
|
||||
case "019":
|
||||
// This is wrong for DPT 1.009. It should be true -> CLOSE, false -> OPEN, but unfortunately
|
||||
// can't be fixed without breaking a lot of working installations.
|
||||
// The documentation has been updated to reflect that. / @J-N-K
|
||||
return translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
case "010":
|
||||
return translatorBoolean.getValueBoolean() ? StopMoveType.MOVE : StopMoveType.STOP;
|
||||
case "022":
|
||||
return DecimalType.valueOf(translatorBoolean.getValueBoolean() ? "1" : "0");
|
||||
default:
|
||||
return OnOffType.from(translatorBoolean.getValueBoolean());
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Type handleDpt3(String subType, DPTXlator translator) {
|
||||
DPTXlator3BitControlled translator3BitControlled = (DPTXlator3BitControlled) translator;
|
||||
if (translator3BitControlled.getStepCode() == 0) {
|
||||
LOGGER.debug("convertRawDataToType: KNX DPT_Control_Dimming: break received.");
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
switch (subType) {
|
||||
case "007":
|
||||
return translator3BitControlled.getControlBit() ? IncreaseDecreaseType.INCREASE
|
||||
: IncreaseDecreaseType.DECREASE;
|
||||
case "008":
|
||||
return translator3BitControlled.getControlBit() ? UpDownType.DOWN : UpDownType.UP;
|
||||
default:
|
||||
LOGGER.warn("DPT3, subtype '{}' is unknown.", subType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Type handleDpt10(String value) throws ParseException {
|
||||
if (value.contains("no-day")) {
|
||||
/*
|
||||
* KNX "no-day" needs special treatment since openHAB's DateTimeType doesn't support "no-day".
|
||||
* Workaround: remove the "no-day" String, parse the remaining time string, which will result in a
|
||||
* date of "1970-01-01".
|
||||
* Replace "no-day" with the current day name
|
||||
*/
|
||||
StringBuilder stb = new StringBuilder(value);
|
||||
int start = stb.indexOf("no-day");
|
||||
int end = start + "no-day".length();
|
||||
stb.replace(start, end, String.format(Locale.US, "%1$ta", Calendar.getInstance()));
|
||||
value = stb.toString();
|
||||
}
|
||||
Date date = null;
|
||||
try {
|
||||
date = new SimpleDateFormat(TIME_DAY_FORMAT, Locale.US).parse(value);
|
||||
} catch (ParseException pe) {
|
||||
date = new SimpleDateFormat(TIME_FORMAT, Locale.US).parse(value);
|
||||
throw pe;
|
||||
}
|
||||
return DateTimeType.valueOf(new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(date));
|
||||
}
|
||||
|
||||
private static @Nullable Type handleDpt19(DPTXlator translator) throws KNXFormatException {
|
||||
DPTXlatorDateTime translatorDateTime = (DPTXlatorDateTime) translator;
|
||||
if (translatorDateTime.isFaultyClock()) {
|
||||
// Not supported: faulty clock
|
||||
LOGGER.debug("KNX clock msg ignored: clock faulty bit set, which is not supported");
|
||||
return null;
|
||||
} else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
|
||||
&& translatorDateTime.isValidField(DPTXlatorDateTime.DATE)) {
|
||||
// Not supported: "/1/1" (month and day without year)
|
||||
LOGGER.debug("KNX clock msg ignored: no year, but day and month, which is not supported");
|
||||
return null;
|
||||
} else if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
|
||||
&& !translatorDateTime.isValidField(DPTXlatorDateTime.DATE)) {
|
||||
// Not supported: "1900" (year without month and day)
|
||||
LOGGER.debug("KNX clock msg ignored: no day and month, but year, which is not supported");
|
||||
return null;
|
||||
} else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
|
||||
&& !translatorDateTime.isValidField(DPTXlatorDateTime.DATE)
|
||||
&& !translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
|
||||
// Not supported: No year, no date and no time
|
||||
LOGGER.debug("KNX clock msg ignored: no day and month or year, which is not supported");
|
||||
return null;
|
||||
}
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
|
||||
&& !translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
|
||||
// Pure date format, no time information
|
||||
cal.setTimeInMillis(translatorDateTime.getValueMilliseconds());
|
||||
String value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime());
|
||||
return DateTimeType.valueOf(value);
|
||||
} else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
|
||||
&& translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
|
||||
// Pure time format, no date information
|
||||
cal.clear();
|
||||
cal.set(Calendar.HOUR_OF_DAY, translatorDateTime.getHour());
|
||||
cal.set(Calendar.MINUTE, translatorDateTime.getMinute());
|
||||
cal.set(Calendar.SECOND, translatorDateTime.getSecond());
|
||||
String value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime());
|
||||
return DateTimeType.valueOf(value);
|
||||
} else if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
|
||||
&& translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
|
||||
// Date format and time information
|
||||
cal.setTimeInMillis(translatorDateTime.getValueMilliseconds());
|
||||
String value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime());
|
||||
return DateTimeType.valueOf(value);
|
||||
} else {
|
||||
LOGGER.warn("Failed to convert '{}'", translator.getValue());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Type handleDpt232(String value, String subType) {
|
||||
Matcher rgb = RGB_PATTERN.matcher(value);
|
||||
if (rgb.matches()) {
|
||||
int r = Integer.parseInt(rgb.group("r"));
|
||||
int g = Integer.parseInt(rgb.group("g"));
|
||||
int b = Integer.parseInt(rgb.group("b"));
|
||||
|
||||
switch (subType) {
|
||||
case "600":
|
||||
return HSBType.fromRGB(r, g, b);
|
||||
case "60000":
|
||||
// MDT specific: mis-use 232.600 for hsv instead of rgb
|
||||
DecimalType hue = new DecimalType(coerceToRange(r * 360.0 / 255.0, 0.0, 359.9999));
|
||||
PercentType sat = new PercentType(BigDecimal.valueOf(coerceToRange(g / 2.55, 0.0, 100.0)));
|
||||
PercentType bright = new PercentType(BigDecimal.valueOf(coerceToRange(b / 2.55, 0.0, 100.0)));
|
||||
return new HSBType(hue, sat, bright);
|
||||
default:
|
||||
LOGGER.warn("Unknown subtype '232.{}', no conversion possible.", subType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
LOGGER.warn("Failed to convert '{}' (DPT 232): Pattern does not match", value);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable Type handleDpt242(String value) {
|
||||
Matcher xyY = XYY_PATTERN.matcher(value);
|
||||
if (xyY.matches()) {
|
||||
String stringx = xyY.group("x");
|
||||
String stringy = xyY.group("y");
|
||||
String stringY = xyY.group("Y");
|
||||
|
||||
if (stringx != null && stringy != null) {
|
||||
double x = Double.parseDouble(stringx.replace(",", "."));
|
||||
double y = Double.parseDouble(stringy.replace(",", "."));
|
||||
if (stringY == null) {
|
||||
return ColorUtil.xyToHsv(new double[] { x, y });
|
||||
} else {
|
||||
double Y = Double.parseDouble(stringY.replace(",", "."));
|
||||
return ColorUtil.xyToHsv(new double[] { x, y, Y });
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.warn("Failed to convert '{}' (DPT 242): Pattern does not match", value);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable Type handleDpt251(String value, Class<? extends Type> preferredType) {
|
||||
Matcher rgbw = RGBW_PATTERN.matcher(value);
|
||||
if (rgbw.matches()) {
|
||||
String rString = rgbw.group("r");
|
||||
String gString = rgbw.group("g");
|
||||
String bString = rgbw.group("b");
|
||||
String wString = rgbw.group("w");
|
||||
|
||||
if (rString != null && gString != null && bString != null && HSBType.class.equals(preferredType)) {
|
||||
// does not support PercentType and r,g,b valid -> HSBType
|
||||
int r = coerceToRange((int) (Double.parseDouble(rString.replace(",", ".")) * 2.55), 0, 255);
|
||||
int g = coerceToRange((int) (Double.parseDouble(gString.replace(",", ".")) * 2.55), 0, 255);
|
||||
int b = coerceToRange((int) (Double.parseDouble(bString.replace(",", ".")) * 2.55), 0, 255);
|
||||
|
||||
return HSBType.fromRGB(r, g, b);
|
||||
} else if (wString != null && PercentType.class.equals(preferredType)) {
|
||||
// does support PercentType and w valid -> PercentType
|
||||
BigDecimal w = new BigDecimal(wString.replace(",", "."));
|
||||
|
||||
return new PercentType(w);
|
||||
}
|
||||
}
|
||||
LOGGER.warn("Failed to convert '{}' (DPT 251): Pattern does not match or invalid content", value);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable Type handleNumericDpt(String id, DPTXlator translator, Class<? extends Type> preferredType)
|
||||
throws KNXFormatException {
|
||||
Set<Class<? extends Type>> allowedTypes = DPTUtil.getAllowedTypes(id);
|
||||
|
||||
double value = translator.getNumericValue();
|
||||
if (allowedTypes.contains(PercentType.class)
|
||||
&& (HSBType.class.equals(preferredType) || PercentType.class.equals(preferredType))) {
|
||||
return new PercentType(BigDecimal.valueOf(Math.round(value)));
|
||||
}
|
||||
|
||||
if (allowedTypes.contains(QuantityType.class) && !disableUoM) {
|
||||
String unit = DPTUnits.getUnitForDpt(id);
|
||||
if (unit != null) {
|
||||
return new QuantityType<>(value + " " + unit);
|
||||
} else {
|
||||
LOGGER.trace("Could not determine unit for DPT '{}', fallback to plain decimal", id);
|
||||
}
|
||||
}
|
||||
|
||||
if (allowedTypes.contains(DecimalType.class)) {
|
||||
return new DecimalType(value);
|
||||
}
|
||||
|
||||
LOGGER.warn("Failed to convert '{}' (DPT '{}'): no matching type found", value, id);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static double coerceToRange(double value, double min, double max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
private static int coerceToRange(int value, int min, int max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
}
|
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.dpt;
|
||||
|
||||
import static org.openhab.binding.knx.internal.dpt.DPTUtil.NORMALIZED_DPT;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.util.ColorUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tuwien.auto.calimero.KNXException;
|
||||
import tuwien.auto.calimero.dptxlator.DPT;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator1BitControlled;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator2ByteFloat;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorDate;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorTime;
|
||||
import tuwien.auto.calimero.dptxlator.TranslatorTypes;
|
||||
|
||||
/**
|
||||
* This class encodes openHAB data types to strings for sending via Calimero
|
||||
*
|
||||
* Parts of this code are based on the openHAB KNXCoreTypeMapper by Kai Kreuzer et al.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ValueEncoder {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ValueEncoder.class);
|
||||
|
||||
private ValueEncoder() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given value as String for outputting via Calimero.
|
||||
*
|
||||
* @param value the value
|
||||
* @param dptId the DPT id to use for formatting the string (e.g. 9.001)
|
||||
* @return the value formatted as String
|
||||
*/
|
||||
public static @Nullable String encode(Type value, String dptId) {
|
||||
Matcher m = DPTUtil.DPT_PATTERN.matcher(dptId);
|
||||
if (!m.matches() || m.groupCount() != 2) {
|
||||
LOGGER.warn("Couldn't identify main/sub number in dptId '{}'", dptId);
|
||||
return null;
|
||||
}
|
||||
|
||||
String mainNumber = m.group("main");
|
||||
|
||||
try {
|
||||
DPTXlator translator = TranslatorTypes.createTranslator(Integer.parseInt(mainNumber),
|
||||
NORMALIZED_DPT.getOrDefault(dptId, dptId));
|
||||
DPT dpt = translator.getType();
|
||||
|
||||
// check for HSBType first, because it extends PercentType as well
|
||||
if (value instanceof HSBType) {
|
||||
return handleHSBType(dptId, (HSBType) value);
|
||||
} else if (value instanceof OnOffType) {
|
||||
return OnOffType.OFF.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
|
||||
} else if (value instanceof UpDownType) {
|
||||
return UpDownType.UP.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
|
||||
} else if (value instanceof IncreaseDecreaseType) {
|
||||
DPT valueDPT = ((DPTXlator3BitControlled.DPT3BitControlled) dpt).getControlDPT();
|
||||
return IncreaseDecreaseType.DECREASE.equals(value) ? valueDPT.getLowerValue() + " 5"
|
||||
: valueDPT.getUpperValue() + " 5";
|
||||
} else if (value instanceof OpenClosedType) {
|
||||
return OpenClosedType.CLOSED.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
|
||||
} else if (value instanceof StopMoveType) {
|
||||
return StopMoveType.STOP.equals(value) ? dpt.getLowerValue() : dpt.getUpperValue();
|
||||
} else if (value instanceof PercentType) {
|
||||
int intValue = ((PercentType) value).intValue();
|
||||
return "251.600".equals(dptId) ? String.format("- - - %d %%", intValue) : String.valueOf(intValue);
|
||||
} else if (value instanceof DecimalType || value instanceof QuantityType<?>) {
|
||||
return handleNumericTypes(dptId, mainNumber, dpt, value);
|
||||
} else if (value instanceof StringType) {
|
||||
return value.toString();
|
||||
} else if (value instanceof DateTimeType) {
|
||||
return handleDateTimeType(dptId, (DateTimeType) value);
|
||||
}
|
||||
} catch (KNXException e) {
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("An exception occurred converting value {} to dpt id {}: error message={}", value, dptId,
|
||||
e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
LOGGER.debug("formatAsDPTString: Couldn't convert value {} to dpt id {} (no mapping).", value, dptId);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given internal <code>dateType</code> to a knx readable String
|
||||
* according to the target datapoint type <code>dpt</code>.
|
||||
*
|
||||
* @param value the input value
|
||||
* @param dptId the target datapoint type
|
||||
*
|
||||
* @return a String which contains either an ISO8601 formatted date (yyyy-mm-dd),
|
||||
* a formatted 24-hour clock with the day of week prepended (Mon, 12:00:00) or
|
||||
* a formatted 24-hour clock (12:00:00)
|
||||
*/
|
||||
private static @Nullable String handleDateTimeType(String dptId, DateTimeType value) {
|
||||
if (DPTXlatorDate.DPT_DATE.getID().equals(dptId)) {
|
||||
return value.format("%tF");
|
||||
} else if (DPTXlatorTime.DPT_TIMEOFDAY.getID().equals(dptId)) {
|
||||
return value.format(Locale.US, "%1$ta, %1$tT");
|
||||
} else if (DPTXlatorDateTime.DPT_DATE_TIME.getID().equals(dptId)) {
|
||||
return value.format(Locale.US, "%tF %1$tT");
|
||||
}
|
||||
LOGGER.warn("Could not format DateTimeType for datapoint type '{}'", dptId);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String handleHSBType(String dptId, HSBType hsb) {
|
||||
switch (dptId) {
|
||||
case "232.600":
|
||||
return "r:" + convertPercentToByte(hsb.getRed()) + " g:" + convertPercentToByte(hsb.getGreen()) + " b:"
|
||||
+ convertPercentToByte(hsb.getBlue());
|
||||
case "232.60000":
|
||||
// MDT specific: mis-use 232.600 for hsv instead of rgb
|
||||
int hue = hsb.getHue().toBigDecimal().multiply(BigDecimal.valueOf(255))
|
||||
.divide(BigDecimal.valueOf(360), 2, RoundingMode.HALF_UP).intValue();
|
||||
return "r:" + hue + " g:" + convertPercentToByte(hsb.getSaturation()) + " b:"
|
||||
+ convertPercentToByte(hsb.getBrightness());
|
||||
case "242.600":
|
||||
double[] xyY = ColorUtil.hsbToXY(hsb);
|
||||
return String.format("(%,.4f %,.4f) %,.1f %%", xyY[0], xyY[1], xyY[2] * 100.0);
|
||||
case "251.600":
|
||||
return String.format("%d %d %d - %%", hsb.getRed().intValue(), hsb.getGreen().intValue(),
|
||||
hsb.getBlue().intValue());
|
||||
case "5.003":
|
||||
return hsb.getHue().toString();
|
||||
default:
|
||||
return hsb.getBrightness().toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static String handleNumericTypes(String dptId, String mainNumber, DPT dpt, Type value) {
|
||||
BigDecimal bigDecimal;
|
||||
if (value instanceof DecimalType decimalType) {
|
||||
bigDecimal = decimalType.toBigDecimal();
|
||||
} else {
|
||||
String unit = DPTUnits.getUnitForDpt(dptId);
|
||||
|
||||
// exception for DPT using temperature differences
|
||||
// - conversion °C or °F to K is wrong for differences,
|
||||
// - stick to the unit given, fix the scaling for °F
|
||||
// 9.002 DPT_Value_Tempd
|
||||
// 9.003 DPT_Value_Tempa
|
||||
// 9.023 DPT_KelvinPerPercent
|
||||
if (DPTXlator2ByteFloat.DPT_TEMPERATURE_DIFFERENCE.getID().equals(dptId)
|
||||
|| DPTXlator2ByteFloat.DPT_TEMPERATURE_GRADIENT.getID().equals(dptId)
|
||||
|| DPTXlator2ByteFloat.DPT_KELVIN_PER_PERCENT.getID().equals(dptId)) {
|
||||
// match unicode character or °C
|
||||
if (value.toString().contains(SIUnits.CELSIUS.getSymbol()) || value.toString().contains("°C")) {
|
||||
unit = unit.replace("K", "°C");
|
||||
} else if (value.toString().contains("°F")) {
|
||||
unit = unit.replace("K", "°F");
|
||||
value = ((QuantityType<?>) value).multiply(BigDecimal.valueOf(5.0 / 9.0));
|
||||
}
|
||||
} else if (DPTXlator4ByteFloat.DPT_LIGHT_QUANTITY.getID().equals(dptId)) {
|
||||
if (!value.toString().contains("J")) {
|
||||
unit = unit.replace("J", "lm*s");
|
||||
}
|
||||
} else if (DPTXlator4ByteFloat.DPT_ELECTRIC_FLUX.getID().equals(dptId)) {
|
||||
// use alternate definition of flux
|
||||
if (value.toString().contains("C")) {
|
||||
unit = "C";
|
||||
}
|
||||
}
|
||||
|
||||
if (unit != null) {
|
||||
QuantityType<?> converted = ((QuantityType<?>) value).toUnit(unit);
|
||||
if (converted == null) {
|
||||
LOGGER.warn("Could not convert {} to unit {}, stripping unit only. Check your configuration.",
|
||||
value, unit);
|
||||
bigDecimal = ((QuantityType<?>) value).toBigDecimal();
|
||||
} else {
|
||||
bigDecimal = converted.toBigDecimal();
|
||||
}
|
||||
} else {
|
||||
bigDecimal = ((QuantityType<?>) value).toBigDecimal();
|
||||
}
|
||||
}
|
||||
switch (mainNumber) {
|
||||
case "2":
|
||||
DPT valueDPT = ((DPTXlator1BitControlled.DPT1BitControlled) dpt).getValueDPT();
|
||||
switch (bigDecimal.intValue()) {
|
||||
case 0:
|
||||
return "0 " + valueDPT.getLowerValue();
|
||||
case 1:
|
||||
return "0 " + valueDPT.getUpperValue();
|
||||
case 2:
|
||||
return "1 " + valueDPT.getLowerValue();
|
||||
default:
|
||||
return "1 " + valueDPT.getUpperValue();
|
||||
}
|
||||
case "18":
|
||||
int intVal = bigDecimal.intValue();
|
||||
if (intVal > 63) {
|
||||
return "learn " + (intVal - 0x80);
|
||||
} else {
|
||||
return "activate " + intVal;
|
||||
}
|
||||
default:
|
||||
return bigDecimal.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* convert 0...100% to 1 byte 0..255
|
||||
*
|
||||
* @param percent
|
||||
* @return int 0..255
|
||||
*/
|
||||
private static int convertPercentToByte(PercentType percent) {
|
||||
return percent.toBigDecimal().multiply(BigDecimal.valueOf(255))
|
||||
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP).intValue();
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ package org.openhab.binding.knx.internal.factory;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -38,6 +39,7 @@ import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
@ -54,17 +56,23 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
|
||||
THING_TYPE_IP_BRIDGE, THING_TYPE_SERIAL_BRIDGE);
|
||||
|
||||
@Nullable
|
||||
private NetworkAddressService networkAddressService;
|
||||
private final NetworkAddressService networkAddressService;
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
@Activate
|
||||
public KNXHandlerFactory(final @Reference NetworkAddressService networkAddressService,
|
||||
public KNXHandlerFactory(final @Reference NetworkAddressService networkAddressService, Map<String, Object> config,
|
||||
final @Reference TranslationProvider translationProvider, final @Reference LocaleProvider localeProvider,
|
||||
final @Reference SerialPortManager serialPortManager) {
|
||||
KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider);
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.serialPortManager = serialPortManager;
|
||||
SerialTransportAdapter.setSerialPortManager(serialPortManager);
|
||||
modified(config);
|
||||
}
|
||||
|
||||
@Modified
|
||||
protected void modified(Map<String, Object> config) {
|
||||
disableUoM = (boolean) config.getOrDefault(CONFIG_DISABLE_UOM, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,226 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.handler;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
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.knx.internal.client.DeviceInspector;
|
||||
import org.openhab.binding.knx.internal.client.DeviceInspector.Result;
|
||||
import org.openhab.binding.knx.internal.client.KNXClient;
|
||||
import org.openhab.binding.knx.internal.config.DeviceConfig;
|
||||
import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tuwien.auto.calimero.IndividualAddress;
|
||||
import tuwien.auto.calimero.KNXException;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
|
||||
/**
|
||||
* Base class for KNX thing handlers.
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractKNXThingHandler extends BaseThingHandler implements GroupAddressListener {
|
||||
|
||||
private static final int INITIAL_PING_DELAY = 5;
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class);
|
||||
|
||||
protected @Nullable IndividualAddress address;
|
||||
private @Nullable ScheduledFuture<?> descriptionJob;
|
||||
private boolean filledDescription = false;
|
||||
private final Random random = new Random();
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public AbstractKNXThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
protected final ScheduledExecutorService getScheduler() {
|
||||
return getBridgeHandler().getScheduler();
|
||||
}
|
||||
|
||||
protected final ScheduledExecutorService getBackgroundScheduler() {
|
||||
return getBridgeHandler().getBackgroundScheduler();
|
||||
}
|
||||
|
||||
protected final KNXBridgeBaseThingHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("The bridge must not be null and must be initialized");
|
||||
}
|
||||
|
||||
protected final KNXClient getClient() {
|
||||
return getBridgeHandler().getClient();
|
||||
}
|
||||
|
||||
protected final boolean describeDevice(@Nullable IndividualAddress address) {
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
DeviceInspector inspector = new DeviceInspector(getClient().getDeviceInfoClient(), address);
|
||||
Result result = inspector.readDeviceInfo();
|
||||
if (result != null) {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.putAll(result.getProperties());
|
||||
updateProperties(properties);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected final String asduToHex(byte[] asdu) {
|
||||
final char[] hexCode = "0123456789ABCDEF".toCharArray();
|
||||
StringBuilder sb = new StringBuilder(2 + asdu.length * 2);
|
||||
sb.append("0x");
|
||||
for (byte b : asdu) {
|
||||
sb.append(hexCode[(b >> 4) & 0xF]);
|
||||
sb.append(hexCode[(b & 0xF)]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected final void restart() {
|
||||
if (address != null) {
|
||||
getClient().restartNetworkDevice(address);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||
attachToClient();
|
||||
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
detachFromClient();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
attachToClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
detachFromClient();
|
||||
}
|
||||
|
||||
protected abstract void scheduleReadJobs();
|
||||
|
||||
protected abstract void cancelReadFutures();
|
||||
|
||||
private void pollDeviceStatus() {
|
||||
try {
|
||||
if (address != null && getClient().isConnected()) {
|
||||
logger.debug("Polling individual address '{}'", address);
|
||||
boolean isReachable = getClient().isReachable(address);
|
||||
if (isReachable) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
DeviceConfig config = getConfigAs(DeviceConfig.class);
|
||||
if (!filledDescription && config.getFetch()) {
|
||||
Future<?> descriptionJob = this.descriptionJob;
|
||||
if (descriptionJob == null || descriptionJob.isCancelled()) {
|
||||
long initialDelay = Math.round(config.getPingInterval() * random.nextFloat());
|
||||
this.descriptionJob = getBackgroundScheduler().schedule(() -> {
|
||||
filledDescription = describeDevice(address);
|
||||
}, initialDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
} catch (KNXException e) {
|
||||
logger.debug("An error occurred while testing the reachability of a thing '{}': {}", getThing().getUID(),
|
||||
e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
KNXTranslationProvider.I18N.getLocalizedException(e));
|
||||
}
|
||||
}
|
||||
|
||||
protected void attachToClient() {
|
||||
if (!getClient().isConnected()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
}
|
||||
DeviceConfig config = getConfigAs(DeviceConfig.class);
|
||||
try {
|
||||
if (!config.getAddress().isEmpty()) {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
address = new IndividualAddress(config.getAddress());
|
||||
|
||||
long pingInterval = config.getPingInterval();
|
||||
long initialPingDelay = Math.round(INITIAL_PING_DELAY * random.nextFloat());
|
||||
|
||||
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if ((pollingJob == null || pollingJob.isCancelled())) {
|
||||
logger.debug("'{}' will be polled every {}s", getThing().getUID(), pingInterval);
|
||||
this.pollingJob = getBackgroundScheduler().scheduleWithFixedDelay(() -> pollDeviceStatus(),
|
||||
initialPingDelay, pingInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (KNXFormatException e) {
|
||||
logger.debug("An exception occurred while setting the individual address '{}': {}", config.getAddress(),
|
||||
e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
KNXTranslationProvider.I18N.getLocalizedException(e));
|
||||
}
|
||||
getClient().registerGroupAddressListener(this);
|
||||
scheduleReadJobs();
|
||||
}
|
||||
|
||||
protected void detachFromClient() {
|
||||
final var pollingJobSynced = pollingJob;
|
||||
if (pollingJobSynced != null) {
|
||||
pollingJobSynced.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
final var descriptionJobSynced = descriptionJob;
|
||||
if (descriptionJobSynced != null) {
|
||||
descriptionJobSynced.cancel(true);
|
||||
descriptionJob = null;
|
||||
}
|
||||
cancelReadFutures();
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
|
||||
if (handler != null) {
|
||||
handler.getClient().unregisterGroupAddressListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,37 +15,48 @@ package org.openhab.binding.knx.internal.handler;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
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.knx.internal.KNXBindingConstants;
|
||||
import org.openhab.binding.knx.internal.KNXTypeMapper;
|
||||
import org.openhab.binding.knx.internal.channel.KNXChannelType;
|
||||
import org.openhab.binding.knx.internal.channel.KNXChannelTypes;
|
||||
import org.openhab.binding.knx.internal.channel.KNXChannel;
|
||||
import org.openhab.binding.knx.internal.channel.KNXChannelFactory;
|
||||
import org.openhab.binding.knx.internal.client.AbstractKNXClient;
|
||||
import org.openhab.binding.knx.internal.client.DeviceInspector;
|
||||
import org.openhab.binding.knx.internal.client.InboundSpec;
|
||||
import org.openhab.binding.knx.internal.client.KNXClient;
|
||||
import org.openhab.binding.knx.internal.client.OutboundSpec;
|
||||
import org.openhab.binding.knx.internal.config.DeviceConfig;
|
||||
import org.openhab.binding.knx.internal.dpt.KNXCoreTypeMapper;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.binding.knx.internal.dpt.DPTUtil;
|
||||
import org.openhab.binding.knx.internal.dpt.ValueDecoder;
|
||||
import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
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.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
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.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -61,19 +72,26 @@ import tuwien.auto.calimero.datapoint.Datapoint;
|
||||
* bus and updating the channels correspondingly.
|
||||
*
|
||||
* @author Simon Kaufmann - Initial contribution and API
|
||||
* @author Jan N. Klug - Refactored for performance
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
|
||||
public class DeviceThingHandler extends BaseThingHandler implements GroupAddressListener {
|
||||
private static final int INITIAL_PING_DELAY = 5;
|
||||
private final Logger logger = LoggerFactory.getLogger(DeviceThingHandler.class);
|
||||
|
||||
private final KNXTypeMapper typeHelper = new KNXCoreTypeMapper();
|
||||
private final Set<GroupAddress> groupAddresses = ConcurrentHashMap.newKeySet();
|
||||
private final Set<GroupAddress> groupAddressesWriteBlockedOnce = ConcurrentHashMap.newKeySet();
|
||||
private final Set<OutboundSpec> groupAddressesRespondingSpec = ConcurrentHashMap.newKeySet();
|
||||
private final ExpiringCacheMap<GroupAddress, @Nullable Boolean> groupAddressesWriteBlocked = new ExpiringCacheMap<>(
|
||||
Duration.ofMillis(1000));
|
||||
private final Map<GroupAddress, OutboundSpec> groupAddressesRespondingSpec = new ConcurrentHashMap<>();
|
||||
private final Map<GroupAddress, ScheduledFuture<?>> readFutures = new ConcurrentHashMap<>();
|
||||
private final Map<ChannelUID, ScheduledFuture<?>> channelFutures = new ConcurrentHashMap<>();
|
||||
private final Map<ChannelUID, KNXChannel> knxChannels = new ConcurrentHashMap<>();
|
||||
private final Random random = new Random();
|
||||
protected @Nullable IndividualAddress address;
|
||||
private int readInterval;
|
||||
private @Nullable ScheduledFuture<?> descriptionJob;
|
||||
private boolean filledDescription = false;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public DeviceThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
@ -81,43 +99,34 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
attachToClient();
|
||||
DeviceConfig config = getConfigAs(DeviceConfig.class);
|
||||
readInterval = config.getReadInterval();
|
||||
initializeGroupAddresses();
|
||||
}
|
||||
|
||||
private void initializeGroupAddresses() {
|
||||
forAllChannels((selector, channelConfiguration) -> {
|
||||
groupAddresses.addAll(selector.getReadAddresses(channelConfiguration));
|
||||
groupAddresses.addAll(selector.getWriteAddresses(channelConfiguration));
|
||||
groupAddresses.addAll(selector.getListenAddresses(channelConfiguration));
|
||||
// gather all GAs from channel configurations and create channels
|
||||
getThing().getChannels().forEach(channel -> {
|
||||
KNXChannel knxChannel = KNXChannelFactory.createKnxChannel(channel);
|
||||
knxChannels.put(channel.getUID(), knxChannel);
|
||||
groupAddresses.addAll(knxChannel.getAllGroupAddresses());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelChannelFutures();
|
||||
freeGroupAddresses();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void cancelChannelFutures() {
|
||||
for (ChannelUID channelUID : channelFutures.keySet()) {
|
||||
channelFutures.computeIfPresent(channelUID, (k, v) -> {
|
||||
v.cancel(true);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void freeGroupAddresses() {
|
||||
groupAddresses.clear();
|
||||
groupAddressesWriteBlockedOnce.clear();
|
||||
groupAddressesWriteBlocked.clear();
|
||||
groupAddressesRespondingSpec.clear();
|
||||
knxChannels.clear();
|
||||
|
||||
detachFromClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelReadFutures() {
|
||||
for (GroupAddress groupAddress : readFutures.keySet()) {
|
||||
readFutures.computeIfPresent(groupAddress, (k, v) -> {
|
||||
@ -127,62 +136,31 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ChannelFunction {
|
||||
void apply(KNXChannelType channelType, Configuration configuration) throws KNXException;
|
||||
}
|
||||
|
||||
private void withKNXType(ChannelUID channelUID, ChannelFunction function) {
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
if (channel == null) {
|
||||
logger.warn("Channel '{}' does not exist", channelUID);
|
||||
return;
|
||||
}
|
||||
withKNXType(channel, function);
|
||||
}
|
||||
|
||||
private void withKNXType(Channel channel, ChannelFunction function) {
|
||||
try {
|
||||
KNXChannelType selector = getKNXChannelType(channel);
|
||||
function.apply(selector, channel.getConfiguration());
|
||||
} catch (KNXException e) {
|
||||
logger.warn("An error occurred on channel {}: {}", channel.getUID(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void forAllChannels(ChannelFunction function) {
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
withKNXType(channel, function);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (!isControl(channelUID)) {
|
||||
withKNXType(channelUID, (selector, configuration) -> {
|
||||
scheduleRead(selector, configuration);
|
||||
});
|
||||
KNXChannel knxChannel = knxChannels.get(channelUID);
|
||||
if (knxChannel == null) {
|
||||
logger.warn("Channel '{}' received a channel linked event, but no KNXChannel found", channelUID);
|
||||
return;
|
||||
}
|
||||
if (!knxChannel.isControl()) {
|
||||
scheduleRead(knxChannel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduleReadJobs() {
|
||||
cancelReadFutures();
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
if (isLinked(channel.getUID().getId()) && !isControl(channel.getUID())) {
|
||||
withKNXType(channel, (selector, configuration) -> {
|
||||
scheduleRead(selector, configuration);
|
||||
});
|
||||
for (KNXChannel knxChannel : knxChannels.values()) {
|
||||
if (isLinked(knxChannel.getChannelUID()) && !knxChannel.isControl()) {
|
||||
scheduleRead(knxChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRead(KNXChannelType selector, Configuration configuration) throws KNXFormatException {
|
||||
List<InboundSpec> readSpecs = selector.getReadSpec(configuration);
|
||||
private void scheduleRead(KNXChannel knxChannel) {
|
||||
List<InboundSpec> readSpecs = knxChannel.getReadSpec();
|
||||
for (InboundSpec readSpec : readSpecs) {
|
||||
for (GroupAddress groupAddress : readSpec.getGroupAddresses()) {
|
||||
scheduleReadJob(groupAddress, readSpec.getDPT());
|
||||
}
|
||||
readSpec.getGroupAddresses().forEach(ga -> scheduleReadJob(ga, readSpec.getDPT()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,7 +179,7 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
|
||||
private void readDatapoint(GroupAddress groupAddress, String dpt) {
|
||||
if (getClient().isConnected()) {
|
||||
if (!isDPTSupported(dpt)) {
|
||||
if (DPTUtil.getAllowedTypes(dpt).isEmpty()) {
|
||||
logger.warn("DPT '{}' is not supported by the KNX binding", dpt);
|
||||
return;
|
||||
}
|
||||
@ -215,89 +193,71 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
return groupAddresses.contains(destination);
|
||||
}
|
||||
|
||||
/** KNXIO remember controls, removeIf may be null */
|
||||
@SuppressWarnings("null")
|
||||
private void rememberRespondingSpec(OutboundSpec commandSpec, boolean add) {
|
||||
GroupAddress ga = commandSpec.getGroupAddress();
|
||||
if (ga != null) {
|
||||
groupAddressesRespondingSpec.removeIf(spec -> spec.getGroupAddress().equals(ga));
|
||||
}
|
||||
if (add) {
|
||||
groupAddressesRespondingSpec.add(commandSpec);
|
||||
}
|
||||
logger.trace("rememberRespondingSpec handled commandSpec for '{}' size '{}' added '{}'", ga,
|
||||
groupAddressesRespondingSpec.size(), add);
|
||||
}
|
||||
|
||||
/** Handling commands triggered from openHAB */
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Handling command '{}' for channel '{}'", command, channelUID);
|
||||
if (command instanceof RefreshType && !isControl(channelUID)) {
|
||||
KNXChannel knxChannel = knxChannels.get(channelUID);
|
||||
if (knxChannel == null) {
|
||||
logger.warn("Channel '{}' received command, but no KNXChannel found", channelUID);
|
||||
return;
|
||||
}
|
||||
if (command instanceof RefreshType && !knxChannel.isControl()) {
|
||||
logger.debug("Refreshing channel '{}'", channelUID);
|
||||
withKNXType(channelUID, (selector, configuration) -> {
|
||||
scheduleRead(selector, configuration);
|
||||
});
|
||||
scheduleRead(knxChannel);
|
||||
} else {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_RESET:
|
||||
if (address != null) {
|
||||
restart();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
withKNXType(channelUID, (selector, channelConfiguration) -> {
|
||||
OutboundSpec commandSpec = selector.getCommandSpec(channelConfiguration, typeHelper, command);
|
||||
// only send GroupValueWrite to KNX if GA is not blocked once
|
||||
if (commandSpec != null
|
||||
&& !groupAddressesWriteBlockedOnce.remove(commandSpec.getGroupAddress())) {
|
||||
getClient().writeToKNX(commandSpec);
|
||||
if (isControl(channelUID)) {
|
||||
rememberRespondingSpec(commandSpec, true);
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
"None of the configured GAs on channel '{}' could handle the command '{}' of type '{}'",
|
||||
channelUID, command, command.getClass().getSimpleName());
|
||||
if (CHANNEL_RESET.equals(channelUID.getId())) {
|
||||
if (address != null) {
|
||||
restart();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
OutboundSpec commandSpec = knxChannel.getCommandSpec(command);
|
||||
// only send GroupValueWrite to KNX if GA is not blocked once
|
||||
if (commandSpec != null) {
|
||||
GroupAddress destination = commandSpec.getGroupAddress();
|
||||
if (knxChannel.isControl()) {
|
||||
// always remember, otherwise we might send an old state
|
||||
groupAddressesRespondingSpec.put(destination, commandSpec);
|
||||
}
|
||||
});
|
||||
break;
|
||||
if (groupAddressesWriteBlocked.get(destination) != null) {
|
||||
logger.debug("Write to {} blocked for 1s/one call after read.", destination);
|
||||
groupAddressesWriteBlocked.invalidate(destination);
|
||||
} else {
|
||||
getClient().writeToKNX(commandSpec);
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
"None of the configured GAs on channel '{}' could handle the command '{}' of type '{}'",
|
||||
channelUID, command, command.getClass().getSimpleName());
|
||||
}
|
||||
} catch (KNXException e) {
|
||||
logger.warn("An error occurred while handling command '{}' on channel '{}': {}", command,
|
||||
channelUID, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isControl(ChannelUID channelUID) {
|
||||
ChannelTypeUID channelTypeUID = getChannelTypeUID(channelUID);
|
||||
return CONTROL_CHANNEL_TYPES.contains(channelTypeUID.getId());
|
||||
}
|
||||
|
||||
private ChannelTypeUID getChannelTypeUID(ChannelUID channelUID) {
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
Objects.requireNonNull(channel);
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
Objects.requireNonNull(channelTypeUID);
|
||||
return channelTypeUID;
|
||||
}
|
||||
|
||||
/** KNXIO */
|
||||
private void sendGroupValueResponse(Channel channel, GroupAddress destination) {
|
||||
Set<GroupAddress> rsa = getKNXChannelType(channel).getWriteAddresses(channel.getConfiguration());
|
||||
private void sendGroupValueResponse(ChannelUID channelUID, GroupAddress destination) {
|
||||
KNXChannel knxChannel = knxChannels.get(channelUID);
|
||||
if (knxChannel == null) {
|
||||
return;
|
||||
}
|
||||
Set<GroupAddress> rsa = knxChannel.getWriteAddresses();
|
||||
if (!rsa.isEmpty()) {
|
||||
logger.trace("onGroupRead size '{}'", rsa.size());
|
||||
withKNXType(channel, (selector, configuration) -> {
|
||||
Optional<OutboundSpec> os = groupAddressesRespondingSpec.stream().filter(spec -> {
|
||||
GroupAddress groupAddress = spec.getGroupAddress();
|
||||
if (groupAddress != null) {
|
||||
return groupAddress.equals(destination);
|
||||
}
|
||||
return false;
|
||||
}).findFirst();
|
||||
if (os.isPresent()) {
|
||||
logger.trace("onGroupRead respondToKNX '{}'", os.get().getGroupAddress());
|
||||
/** KNXIO: sending real "GroupValueResponse" to the KNX bus. */
|
||||
getClient().respondToKNX(os.get());
|
||||
OutboundSpec os = groupAddressesRespondingSpec.get(destination);
|
||||
if (os != null) {
|
||||
logger.trace("onGroupRead respondToKNX '{}'",
|
||||
os.getGroupAddress()); /* KNXIO: sending real "GroupValueResponse" to the KNX bus. */
|
||||
try {
|
||||
getClient().respondToKNX(os);
|
||||
} catch (KNXException e) {
|
||||
logger.warn("An error occurred on channel {}: {}", channelUID, e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,22 +268,25 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
public void onGroupRead(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu) {
|
||||
logger.trace("onGroupRead Thing '{}' received a GroupValueRead telegram from '{}' for destination '{}'",
|
||||
getThing().getUID(), source, destination);
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
if (isControl(channel.getUID())) {
|
||||
withKNXType(channel, (selector, configuration) -> {
|
||||
OutboundSpec responseSpec = selector.getResponseSpec(configuration, destination,
|
||||
RefreshType.REFRESH);
|
||||
if (responseSpec != null) {
|
||||
logger.trace("onGroupRead isControl -> postCommand");
|
||||
// This event should be sent to KNX as GroupValueResponse immediately.
|
||||
sendGroupValueResponse(channel, destination);
|
||||
// Send REFRESH to openHAB to get this event for scripting with postCommand
|
||||
// and remember to ignore/block this REFRESH to be sent back to KNX as GroupValueWrite after
|
||||
// postCommand is done!
|
||||
groupAddressesWriteBlockedOnce.add(destination);
|
||||
postCommand(channel.getUID().getId(), RefreshType.REFRESH);
|
||||
for (KNXChannel knxChannel : knxChannels.values()) {
|
||||
if (knxChannel.isControl()) {
|
||||
OutboundSpec responseSpec = knxChannel.getResponseSpec(destination, RefreshType.REFRESH);
|
||||
if (responseSpec != null) {
|
||||
logger.trace("onGroupRead isControl -> postCommand");
|
||||
// This event should be sent to KNX as GroupValueResponse immediately.
|
||||
sendGroupValueResponse(knxChannel.getChannelUID(), destination);
|
||||
|
||||
// block write attempts for 1s or 1 request to prevent loops
|
||||
if (!groupAddressesWriteBlocked.containsKey(destination)) {
|
||||
groupAddressesWriteBlocked.put(destination, () -> null);
|
||||
}
|
||||
});
|
||||
groupAddressesWriteBlocked.putValue(destination, true);
|
||||
|
||||
// Send REFRESH to openHAB to get this event for scripting with postCommand
|
||||
// and remember to ignore/block this REFRESH to be sent back to KNX as GroupValueWrite after
|
||||
// postCommand is done!
|
||||
postCommand(knxChannel.getChannelUID(), RefreshType.REFRESH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -346,91 +309,219 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
logger.debug("onGroupWrite Thing '{}' received a GroupValueWrite telegram from '{}' for destination '{}'",
|
||||
getThing().getUID(), source, destination);
|
||||
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
withKNXType(channel, (selector, configuration) -> {
|
||||
InboundSpec listenSpec = selector.getListenSpec(configuration, destination);
|
||||
if (listenSpec != null) {
|
||||
logger.trace(
|
||||
"onGroupWrite Thing '{}' processes a GroupValueWrite telegram for destination '{}' for channel '{}'",
|
||||
getThing().getUID(), destination, channel.getUID());
|
||||
/**
|
||||
* Remember current KNXIO outboundSpec only if it is a control channel.
|
||||
*/
|
||||
if (isControl(channel.getUID())) {
|
||||
logger.trace("onGroupWrite isControl");
|
||||
Type type = typeHelper.toType(
|
||||
new CommandDP(destination, getThing().getUID().toString(), 0, listenSpec.getDPT()),
|
||||
asdu);
|
||||
if (type != null) {
|
||||
OutboundSpec commandSpec = selector.getCommandSpec(configuration, typeHelper, type);
|
||||
if (commandSpec != null) {
|
||||
rememberRespondingSpec(commandSpec, true);
|
||||
}
|
||||
for (KNXChannel knxChannel : knxChannels.values()) {
|
||||
InboundSpec listenSpec = knxChannel.getListenSpec(destination);
|
||||
if (listenSpec != null) {
|
||||
logger.trace(
|
||||
"onGroupWrite Thing '{}' processes a GroupValueWrite telegram for destination '{}' for channel '{}'",
|
||||
getThing().getUID(), destination, knxChannel.getChannelUID());
|
||||
/**
|
||||
* Remember current KNXIO outboundSpec only if it is a control channel.
|
||||
*/
|
||||
if (knxChannel.isControl()) {
|
||||
logger.trace("onGroupWrite isControl");
|
||||
Type value = ValueDecoder.decode(listenSpec.getDPT(), asdu, knxChannel.preferredType());
|
||||
if (value != null) {
|
||||
OutboundSpec commandSpec = knxChannel.getCommandSpec(value);
|
||||
if (commandSpec != null) {
|
||||
groupAddressesRespondingSpec.put(destination, commandSpec);
|
||||
}
|
||||
}
|
||||
processDataReceived(destination, asdu, listenSpec, channel.getUID());
|
||||
}
|
||||
});
|
||||
processDataReceived(destination, asdu, listenSpec, knxChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processDataReceived(GroupAddress destination, byte[] asdu, InboundSpec listenSpec,
|
||||
ChannelUID channelUID) {
|
||||
if (!isDPTSupported(listenSpec.getDPT())) {
|
||||
KNXChannel knxChannel) {
|
||||
if (DPTUtil.getAllowedTypes(listenSpec.getDPT()).isEmpty()) {
|
||||
logger.warn("DPT '{}' is not supported by the KNX binding.", listenSpec.getDPT());
|
||||
return;
|
||||
}
|
||||
|
||||
Datapoint datapoint = new CommandDP(destination, getThing().getUID().toString(), 0, listenSpec.getDPT());
|
||||
Type type = typeHelper.toType(datapoint, asdu);
|
||||
|
||||
if (type != null) {
|
||||
if (isControl(channelUID)) {
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
Object repeat = channel != null ? channel.getConfiguration().get(KNXBindingConstants.REPEAT_FREQUENCY)
|
||||
: null;
|
||||
int frequency = repeat != null ? ((BigDecimal) repeat).intValue() : 0;
|
||||
if (KNXBindingConstants.CHANNEL_DIMMER_CONTROL.equals(getChannelTypeUID(channelUID).getId())
|
||||
&& (type instanceof UnDefType || type instanceof IncreaseDecreaseType) && frequency > 0) {
|
||||
Type value = ValueDecoder.decode(listenSpec.getDPT(), asdu, knxChannel.preferredType());
|
||||
if (value != null) {
|
||||
if (knxChannel.isControl()) {
|
||||
ChannelUID channelUID = knxChannel.getChannelUID();
|
||||
int frequency;
|
||||
if (KNXBindingConstants.CHANNEL_DIMMER_CONTROL.equals(knxChannel.getChannelType())) {
|
||||
// if we have a dimmer control channel, check if a frequency is defined
|
||||
Channel channel = getThing().getChannel(channelUID);
|
||||
if (channel == null) {
|
||||
logger.warn("Failed to find channel for ChannelUID '{}'", channelUID);
|
||||
return;
|
||||
}
|
||||
frequency = ((BigDecimal) Objects.requireNonNullElse(
|
||||
channel.getConfiguration().get(KNXBindingConstants.REPEAT_FREQUENCY), BigDecimal.ZERO))
|
||||
.intValue();
|
||||
} else {
|
||||
// disable dimming by binding
|
||||
frequency = 0;
|
||||
}
|
||||
if ((value instanceof UnDefType || value instanceof IncreaseDecreaseType) && frequency > 0) {
|
||||
// continuous dimming by the binding
|
||||
if (UnDefType.UNDEF.equals(type)) {
|
||||
channelFutures.computeIfPresent(channelUID, (k, v) -> {
|
||||
v.cancel(false);
|
||||
return null;
|
||||
});
|
||||
} else if (type instanceof IncreaseDecreaseType) {
|
||||
channelFutures.compute(channelUID, (k, v) -> {
|
||||
if (v != null) {
|
||||
v.cancel(true);
|
||||
}
|
||||
return scheduler.scheduleWithFixedDelay(() -> postCommand(channelUID, (Command) type), 0,
|
||||
frequency, TimeUnit.MILLISECONDS);
|
||||
});
|
||||
// cancel a running scheduler before adding a new (and only add if not UnDefType)
|
||||
ScheduledFuture<?> oldFuture = channelFutures.remove(channelUID);
|
||||
if (oldFuture != null) {
|
||||
oldFuture.cancel(true);
|
||||
}
|
||||
if (value instanceof IncreaseDecreaseType) {
|
||||
channelFutures.put(channelUID, scheduler.scheduleWithFixedDelay(
|
||||
() -> postCommand(channelUID, (Command) value), 0, frequency, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
} else {
|
||||
if (type instanceof Command) {
|
||||
if (value instanceof Command command) {
|
||||
logger.trace("processDataReceived postCommand new value '{}' for GA '{}'", asdu, address);
|
||||
postCommand(channelUID, (Command) type);
|
||||
postCommand(channelUID, command);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (type instanceof State && !(type instanceof UnDefType)) {
|
||||
updateState(channelUID, (State) type);
|
||||
if (value instanceof State state && !(value instanceof UnDefType)) {
|
||||
updateState(knxChannel.getChannelUID(), state);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String s = asduToHex(asdu);
|
||||
logger.warn(
|
||||
"Ignoring KNX bus data: couldn't transform to any Type (destination='{}', datapoint='{}', data='{}')",
|
||||
destination, datapoint, s);
|
||||
"Ignoring KNX bus data for channel '{}': couldn't transform to any Type (GA='{}', DPT='{}', data='{}')",
|
||||
knxChannel.getChannelUID(), destination, listenSpec.getDPT(), HexUtils.bytesToHex(asdu));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDPTSupported(@Nullable String dpt) {
|
||||
return typeHelper.toTypeClass(dpt) != null;
|
||||
protected final ScheduledExecutorService getScheduler() {
|
||||
return getBridgeHandler().getScheduler();
|
||||
}
|
||||
|
||||
private KNXChannelType getKNXChannelType(Channel channel) {
|
||||
return KNXChannelTypes.getType(channel.getChannelTypeUID());
|
||||
protected final ScheduledExecutorService getBackgroundScheduler() {
|
||||
return getBridgeHandler().getBackgroundScheduler();
|
||||
}
|
||||
|
||||
protected final KNXBridgeBaseThingHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("The bridge must not be null and must be initialized");
|
||||
}
|
||||
|
||||
protected final KNXClient getClient() {
|
||||
return getBridgeHandler().getClient();
|
||||
}
|
||||
|
||||
protected final boolean describeDevice(@Nullable IndividualAddress address) {
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
DeviceInspector inspector = new DeviceInspector(getClient().getDeviceInfoClient(), address);
|
||||
DeviceInspector.Result result = inspector.readDeviceInfo();
|
||||
if (result != null) {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.putAll(result.getProperties());
|
||||
updateProperties(properties);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected final void restart() {
|
||||
if (address != null) {
|
||||
getClient().restartNetworkDevice(address);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||
attachToClient();
|
||||
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
detachFromClient();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
private void pollDeviceStatus() {
|
||||
try {
|
||||
if (address != null && getClient().isConnected()) {
|
||||
logger.debug("Polling individual address '{}'", address);
|
||||
boolean isReachable = getClient().isReachable(address);
|
||||
if (isReachable) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
DeviceConfig config = getConfigAs(DeviceConfig.class);
|
||||
if (!filledDescription && config.getFetch()) {
|
||||
Future<?> descriptionJob = this.descriptionJob;
|
||||
if (descriptionJob == null || descriptionJob.isCancelled()) {
|
||||
long initialDelay = Math.round(config.getPingInterval() * random.nextFloat());
|
||||
this.descriptionJob = getBackgroundScheduler().schedule(() -> {
|
||||
filledDescription = describeDevice(address);
|
||||
}, initialDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
} catch (KNXException e) {
|
||||
logger.debug("An error occurred while testing the reachability of a thing '{}': {}", getThing().getUID(),
|
||||
e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
KNXTranslationProvider.I18N.getLocalizedException(e));
|
||||
}
|
||||
}
|
||||
|
||||
protected void attachToClient() {
|
||||
if (!getClient().isConnected()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
}
|
||||
DeviceConfig config = getConfigAs(DeviceConfig.class);
|
||||
try {
|
||||
if (!config.getAddress().isEmpty()) {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
address = new IndividualAddress(config.getAddress());
|
||||
|
||||
long pingInterval = config.getPingInterval();
|
||||
long initialPingDelay = Math.round(INITIAL_PING_DELAY * random.nextFloat());
|
||||
|
||||
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if ((pollingJob == null || pollingJob.isCancelled())) {
|
||||
logger.debug("'{}' will be polled every {}s", getThing().getUID(), pingInterval);
|
||||
this.pollingJob = getBackgroundScheduler().scheduleWithFixedDelay(this::pollDeviceStatus,
|
||||
initialPingDelay, pingInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (KNXFormatException e) {
|
||||
logger.debug("An exception occurred while setting the individual address '{}': {}", config.getAddress(),
|
||||
e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
KNXTranslationProvider.I18N.getLocalizedException(e));
|
||||
}
|
||||
getClient().registerGroupAddressListener(this);
|
||||
scheduleReadJobs();
|
||||
}
|
||||
|
||||
protected void detachFromClient() {
|
||||
final var pollingJobSynced = pollingJob;
|
||||
if (pollingJobSynced != null) {
|
||||
pollingJobSynced.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
final var descriptionJobSynced = descriptionJob;
|
||||
if (descriptionJobSynced != null) {
|
||||
descriptionJobSynced.cancel(true);
|
||||
descriptionJob = null;
|
||||
}
|
||||
cancelReadFutures();
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
|
||||
if (handler != null) {
|
||||
handler.getClient().unregisterGroupAddressListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,4 +7,12 @@
|
||||
<name>KNX Binding</name>
|
||||
<description>This binding supports connecting to a KNX bus</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="disableUoM" type="boolean">
|
||||
<default>false</default>
|
||||
<label>Disable UoM</label>
|
||||
<description>This disables Units of Measurement support for incoming values.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</addon:addon>
|
||||
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Holger Friedrich - Initial Contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class KNXChannelFactoryTest {
|
||||
|
||||
/**
|
||||
* This test checks if channels with invalid channelTypeUID lead to the intended exception.
|
||||
* Side effect is testing if KNXChannelFactory can be instantiated (this is not the case e.g. when types with
|
||||
* duplicate channel types are created)
|
||||
*/
|
||||
@Test
|
||||
public void testNullChannelUidFails() {
|
||||
Channel channel = mock(Channel.class);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
KNXChannelFactory.createKnxChannel(channel);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidChannelUidFails() {
|
||||
Channel channel = mock(Channel.class);
|
||||
when(channel.getChannelTypeUID()).thenReturn(new ChannelTypeUID("a:b:c"));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
KNXChannelFactory.createKnxChannel(channel);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { CHANNEL_COLOR, CHANNEL_COLOR_CONTROL, CHANNEL_CONTACT, CHANNEL_CONTACT_CONTROL,
|
||||
CHANNEL_DATETIME, CHANNEL_DATETIME_CONTROL, CHANNEL_DIMMER, CHANNEL_DIMMER_CONTROL, CHANNEL_NUMBER,
|
||||
CHANNEL_NUMBER_CONTROL, CHANNEL_ROLLERSHUTTER, CHANNEL_ROLLERSHUTTER_CONTROL, CHANNEL_STRING,
|
||||
CHANNEL_STRING_CONTROL, CHANNEL_SWITCH, CHANNEL_SWITCH_CONTROL })
|
||||
public void testSuccess(String channeltype) {
|
||||
Channel channel = mock(Channel.class);
|
||||
Configuration configuration = new Configuration(
|
||||
Map.of("key1", "5.001:<1/2/3+4/5/6+1/5/6", "key2", "1.001:7/1/9+1/1/2"));
|
||||
when(channel.getChannelTypeUID()).thenReturn(new ChannelTypeUID("knx:" + channeltype));
|
||||
when(channel.getConfiguration()).thenReturn(configuration);
|
||||
when(channel.getAcceptedItemType()).thenReturn("none");
|
||||
|
||||
assertNotNull(KNXChannelFactory.createKnxChannel(channel));
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class KNXChannelTest {
|
||||
|
||||
@Test
|
||||
public void invalidFails() {
|
||||
GroupAddressConfiguration res = GroupAddressConfiguration.parse("5.001:<1/3/22+0/3/22+<0/8/15");
|
||||
assertNull(res);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithDptMultipleWithRead() throws KNXFormatException {
|
||||
GroupAddressConfiguration res = GroupAddressConfiguration.parse("5.001:<1/3/22+0/3/22+<0/7/15");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("5.001", res.getDPT());
|
||||
assertEquals(new GroupAddress("1/3/22"), res.getMainGA());
|
||||
assertTrue(res.getReadGAs().contains(res.getMainGA()));
|
||||
assertEquals(3, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithDptMultipleWithoutRead() throws KNXFormatException {
|
||||
GroupAddressConfiguration res = GroupAddressConfiguration.parse("5.001:1/3/22+0/3/22+0/7/15");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("5.001", res.getDPT());
|
||||
assertEquals(new GroupAddress("1/3/22"), res.getMainGA());
|
||||
assertFalse(res.getReadGAs().contains(res.getMainGA()));
|
||||
assertEquals(3, res.getListenGAs().size());
|
||||
assertEquals(0, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithoutDptSingleWithoutRead() throws KNXFormatException {
|
||||
GroupAddressConfiguration res = GroupAddressConfiguration.parse("1/3/22");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertNull(res.getDPT());
|
||||
assertEquals(new GroupAddress("1/3/22"), res.getMainGA());
|
||||
assertFalse(res.getReadGAs().contains(res.getMainGA()));
|
||||
assertEquals(1, res.getListenGAs().size());
|
||||
assertEquals(0, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithoutDptSingleWithRead() throws KNXFormatException {
|
||||
GroupAddressConfiguration res = GroupAddressConfiguration.parse("<1/3/22");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertNull(res.getDPT());
|
||||
assertEquals(new GroupAddress("1/3/22"), res.getMainGA());
|
||||
assertTrue(res.getReadGAs().contains(res.getMainGA()));
|
||||
assertEquals(1, res.getListenGAs().size());
|
||||
assertEquals(1, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseTwoLevel() throws KNXFormatException {
|
||||
GroupAddressConfiguration res = GroupAddressConfiguration.parse("5.001:<3/1024+<4/1025");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals(new GroupAddress("3/1024"), res.getMainGA());
|
||||
assertTrue(res.getReadGAs().contains(res.getMainGA()));
|
||||
assertEquals(2, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseFreeLevel() throws KNXFormatException {
|
||||
GroupAddressConfiguration res = GroupAddressConfiguration.parse("5.001:<4610+<4611");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals(new GroupAddress("4610"), res.getMainGA());
|
||||
assertEquals(2, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChannelGaParsing() throws KNXFormatException {
|
||||
Channel channel = mock(Channel.class);
|
||||
Configuration configuration = new Configuration(
|
||||
Map.of("key1", "5.001:<1/2/3+4/5/6+1/5/6", "key2", "1.001:7/1/9+1/1/2"));
|
||||
when(channel.getChannelTypeUID()).thenReturn(new ChannelTypeUID("a:b:c"));
|
||||
when(channel.getConfiguration()).thenReturn(configuration);
|
||||
when(channel.getAcceptedItemType()).thenReturn("none");
|
||||
|
||||
MyKNXChannel knxChannel = new MyKNXChannel(channel);
|
||||
|
||||
Set<GroupAddress> listenAddresses = knxChannel.getAllGroupAddresses();
|
||||
assertEquals(5, listenAddresses.size());
|
||||
// we don't check the content since parsing has been checked before and the quantity is correct
|
||||
Set<GroupAddress> writeAddresses = knxChannel.getWriteAddresses();
|
||||
assertEquals(2, writeAddresses.size());
|
||||
assertTrue(writeAddresses.contains(new GroupAddress("1/2/3")));
|
||||
assertTrue(writeAddresses.contains(new GroupAddress("7/1/9")));
|
||||
}
|
||||
|
||||
private static class MyKNXChannel extends KNXChannel {
|
||||
public MyKNXChannel(Channel channel) {
|
||||
super(Set.of("key1", "key2"), List.of(UnDefType.class), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultDPT(String gaConfigKey) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.channel;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API.
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class KNXChannelTypeTest {
|
||||
|
||||
private KNXChannelType ct = new MyKNXChannelType("");
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
ct = new MyKNXChannelType("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithDptMultipleWithRead() {
|
||||
ChannelConfiguration res = ct.parse("5.001:<1/3/22+0/3/22+<0/8/15");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("5.001", res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertTrue(res.getMainGA().isRead());
|
||||
assertEquals(3, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithDptMultipleWithoutRead() {
|
||||
ChannelConfiguration res = ct.parse("5.001:1/3/22+0/3/22+0/8/15");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("5.001", res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertFalse(res.getMainGA().isRead());
|
||||
assertEquals(3, res.getListenGAs().size());
|
||||
assertEquals(0, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithoutDptSingleWithoutRead() {
|
||||
ChannelConfiguration res = ct.parse("1/3/22");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertNull(res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertFalse(res.getMainGA().isRead());
|
||||
assertEquals(1, res.getListenGAs().size());
|
||||
assertEquals(0, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithoutDptSingleWitRead() {
|
||||
ChannelConfiguration res = ct.parse("<1/3/22");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertNull(res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertTrue(res.getMainGA().isRead());
|
||||
assertEquals(1, res.getListenGAs().size());
|
||||
assertEquals(1, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseTwoLevel() {
|
||||
ChannelConfiguration res = ct.parse("5.001:<3/1024+<4/1025");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("3/1024", res.getMainGA().getGA());
|
||||
assertEquals(2, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseFreeLevel() {
|
||||
ChannelConfiguration res = ct.parse("5.001:<4610+<4611");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("4610", res.getMainGA().getGA());
|
||||
assertEquals(2, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
private static class MyKNXChannelType extends KNXChannelType {
|
||||
public MyKNXChannelType(String channelTypeID) {
|
||||
super(channelTypeID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultDPT(String gaConfigKey) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,411 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.dpt;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator2ByteUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteSigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator64BitSigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitSigned;
|
||||
import tuwien.auto.calimero.dptxlator.DptXlator2ByteSigned;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class DPTTest {
|
||||
|
||||
@Test
|
||||
void testToDPTValueTrailingZeroesStrippedOff() {
|
||||
assertEquals("3", ValueEncoder.encode(new DecimalType("3"), "17.001"));
|
||||
assertEquals("3", ValueEncoder.encode(new DecimalType("3.0"), "17.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToDPTValueDecimalType() {
|
||||
assertEquals("23.1", ValueEncoder.encode(new DecimalType("23.1"), "9.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT5ValueFromQuantityType() {
|
||||
assertEquals("80", ValueEncoder.encode(new QuantityType<>("80 %"), "5.001"));
|
||||
|
||||
assertEquals("180", ValueEncoder.encode(new QuantityType<>("180 °"), "5.003"));
|
||||
assertTrue(ValueEncoder.encode(new QuantityType<>("3.14 rad"), "5.003").startsWith("179."));
|
||||
assertEquals("80", ValueEncoder.encode(new QuantityType<>("80 %"), "5.004"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT7ValueFromQuantityType() {
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "7.002"));
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "7.003"));
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "7.004"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1000 ms"), "7.005"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("60 s"), "7.006"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("60 min"), "7.007"));
|
||||
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1 m"), "7.011"));
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 mA"), "7.012"));
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 lx"), "7.013"));
|
||||
|
||||
assertEquals("3000", ValueEncoder.encode(new QuantityType<>("3000 K"), "7.600"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT8ValueFromQuantityType() {
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "8.002"));
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "8.003"));
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "8.004"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1000 ms"), "8.005"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("60 s"), "8.006"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("60 min"), "8.007"));
|
||||
|
||||
assertEquals("180", ValueEncoder.encode(new QuantityType<>("180 °"), "8.011"));
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1 km"), "8.012"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT9ValueFromQuantityType() {
|
||||
assertEquals("23.1", ValueEncoder.encode(new QuantityType<>("23.1 °C"), "9.001"));
|
||||
assertEquals(5.0,
|
||||
Double.parseDouble(Objects.requireNonNull(ValueEncoder.encode(new QuantityType<>("41 °F"), "9.001"))));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("274.15 K"), "9.001"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K"), "9.002"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1000 mK"), "9.002"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °C"), "9.002"));
|
||||
assertTrue(ValueEncoder.encode(new QuantityType<>("1 °F"), "9.002").startsWith("0.55"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K/h"), "9.003"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °C/h"), "9.003"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1000 mK/h"), "9.003"));
|
||||
assertEquals("600", ValueEncoder.encode(new QuantityType<>("10 K/min"), "9.003"));
|
||||
assertEquals("100", ValueEncoder.encode(new QuantityType<>("100 lx"), "9.004"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m/s"), "9.005"));
|
||||
assertTrue(ValueEncoder.encode(new QuantityType<>("1.94 kn"), "9.005").startsWith("0.99"));
|
||||
assertEquals(1.0, Double
|
||||
.parseDouble(Objects.requireNonNull(ValueEncoder.encode(new QuantityType<>("3.6 km/h"), "9.005"))));
|
||||
assertEquals("456", ValueEncoder.encode(new QuantityType<>("456 Pa"), "9.006"));
|
||||
assertEquals("70", ValueEncoder.encode(new QuantityType<>("70 %"), "9.007"));
|
||||
assertEquals("8", ValueEncoder.encode(new QuantityType<>("8 ppm"), "9.008"));
|
||||
assertEquals("9", ValueEncoder.encode(new QuantityType<>("9 m³/h"), "9.009"));
|
||||
assertEquals("10", ValueEncoder.encode(new QuantityType<>("10 s"), "9.010"));
|
||||
assertEquals("11", ValueEncoder.encode(new QuantityType<>("0.011 s"), "9.011"));
|
||||
|
||||
assertEquals("20", ValueEncoder.encode(new QuantityType<>("20 mV"), "9.020"));
|
||||
assertEquals("20", ValueEncoder.encode(new QuantityType<>("0.02 V"), "9.020"));
|
||||
assertEquals("21", ValueEncoder.encode(new QuantityType<>("21 mA"), "9.021"));
|
||||
assertEquals("21", ValueEncoder.encode(new QuantityType<>("0.021 A"), "9.021"));
|
||||
assertEquals("12", ValueEncoder.encode(new QuantityType<>("12 W/m²"), "9.022"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K/%"), "9.023"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °C/%"), "9.023"));
|
||||
assertTrue(ValueEncoder.encode(new QuantityType<>("1 °F/%"), "9.023").startsWith("0.55"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 kW"), "9.024"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 l/h"), "9.025"));
|
||||
assertEquals("60", ValueEncoder.encode(new QuantityType<>("1 l/min"), "9.025"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 l/m²"), "9.026"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °F"), "9.027"));
|
||||
assertTrue(ValueEncoder.encode(new QuantityType<>("-12 °C"), "9.027").startsWith("10."));
|
||||
assertEquals("10", ValueEncoder.encode(new QuantityType<>("10 km/h"), "9.028"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 g/m³"), "9.029"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 µg/m³"), "9.030"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT10ValueFromQuantityType() {
|
||||
// DateTimeTyype, not QuantityType
|
||||
assertEquals("Wed, 17:30:00", ValueEncoder.encode(new DateTimeType("2019-06-12T17:30:00Z"), "10.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT11ValueFromQuantityType() {
|
||||
// DateTimeTyype, not QuantityType
|
||||
assertEquals("2019-06-12", ValueEncoder.encode(new DateTimeType("2019-06-12T17:30:00Z"), "11.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT12ValueFromQuantityType() {
|
||||
// 12.001: dimensionless
|
||||
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 s"), "12.100"));
|
||||
assertEquals("2", ValueEncoder.encode(new QuantityType<>("2 min"), "12.101"));
|
||||
assertEquals("3", ValueEncoder.encode(new QuantityType<>("3 h"), "12.102"));
|
||||
|
||||
assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1 m^3"), "12.1200"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 l"), "12.1200"));
|
||||
assertEquals("2", ValueEncoder.encode(new QuantityType<>("2 m³"), "12.1201"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT13ValueFromQuantityType() {
|
||||
// 13.001 dimensionless
|
||||
assertEquals("24", ValueEncoder.encode(new QuantityType<>("24 m³/h"), "13.002"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("24 m³/d"), "13.002"));
|
||||
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 Wh"), "13.010"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 VAh"), "13.011"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 varh"), "13.012"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 kWh"), "13.013"));
|
||||
assertEquals("4.2", ValueEncoder.encode(new QuantityType<>("4200 VAh"), "13.014"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 kvarh"), "13.015"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 MWh"), "13.016"));
|
||||
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 s"), "13.100"));
|
||||
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 l"), "13.1200"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 m³"), "13.1201"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT14ValueFromQuantityType() {
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m/s²"), "14.000"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 rad/s²"), "14.001"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J/mol"), "14.002"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 /s"), "14.003"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 mol"), "14.004"));
|
||||
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 rad"), "14.006"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °"), "14.007"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J*s"), "14.008"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 rad/s"), "14.009"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m²"), "14.010"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 F"), "14.011"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C/m²"), "14.012"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C/m³"), "14.013"));
|
||||
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m²/N"), "14.014"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 S"), "14.015"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 S/m"), "14.016"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 kg/m³"), "14.017"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C"), "14.018"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 A"), "14.019"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 A/m²"), "14.020"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C*m"), "14.021"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C/m²"), "14.022"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 V/m"), "14.023"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C"), "14.024"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C/m²"), "14.025"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 C/m²"), "14.026"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 V"), "14.027"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 V"), "14.028"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 A*m²"), "14.029"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 V"), "14.030"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J"), "14.031"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 N"), "14.032"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Hz"), "14.033"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 rad/s"), "14.034"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J/K"), "14.035"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 W"), "14.036"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J"), "14.037"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Ohm"), "14.038"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m"), "14.039"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J"), "14.040"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 lm*s"), "14.040"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 cd/m²"), "14.041"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 lm"), "14.042"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 cd"), "14.043"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 A/m"), "14.044"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Wb"), "14.045"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 T"), "14.046"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 A*m²"), "14.047"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 T"), "14.048"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 A/m"), "14.049"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 A"), "14.050"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 kg"), "14.051"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 kg/s"), "14.052"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 N/s"), "14.053"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 rad"), "14.054"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °"), "14.055"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 W"), "14.056"));
|
||||
// 14.057: dimensionless
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Pa"), "14.058"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Ohm"), "14.059"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Ohm"), "14.060"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Ohm*m"), "14.061"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 H"), "14.062"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 sr"), "14.063"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 W/m²"), "14.064"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m/s"), "14.065"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 Pa"), "14.066"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 N/m"), "14.067"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °C"), "14.068"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K"), "14.069"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K"), "14.070"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J/K"), "14.071"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 W/m/K"), "14.072"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 V/K"), "14.073"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 s"), "14.074"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 N*m"), "14.075"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J"), "14.075"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m³"), "14.076"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m³/s"), "14.077"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 N"), "14.078"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 J"), "14.079"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 VA"), "14.080"));
|
||||
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m³/h"), "14.1200"));
|
||||
assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 l/s"), "14.1201"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT19ValueFromQuantityType() {
|
||||
// DateTimeTyype, not QuantityType
|
||||
assertEquals("2019-06-12 17:30:00", ValueEncoder.encode(new DateTimeType("2019-06-12T17:30:00Z"), "19.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT29ValueFromQuantityType() {
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 Wh"), "29.010"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 VAh"), "29.011"));
|
||||
assertEquals("42", ValueEncoder.encode(new QuantityType<>("42 varh"), "29.012"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dpt232RgbValue() {
|
||||
// input data
|
||||
byte[] data = new byte[] { 123, 45, 67 };
|
||||
|
||||
// this is the old implementation
|
||||
String value = "r:123 g:45 b:67";
|
||||
int r = Integer.parseInt(value.split(" ")[0].split(":")[1]);
|
||||
int g = Integer.parseInt(value.split(" ")[1].split(":")[1]);
|
||||
int b = Integer.parseInt(value.split(" ")[2].split(":")[1]);
|
||||
HSBType expected = HSBType.fromRGB(r, g, b);
|
||||
|
||||
assertEquals(expected, ValueDecoder.decode("232.600", data, HSBType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dpt232HsbValue() {
|
||||
// input data
|
||||
byte[] data = new byte[] { 123, 45, 67 };
|
||||
|
||||
HSBType hsbType = (HSBType) ValueDecoder.decode("232.60000", data, HSBType.class);
|
||||
|
||||
Assertions.assertNotNull(hsbType);
|
||||
Objects.requireNonNull(hsbType);
|
||||
assertEquals(173.6, hsbType.getHue().doubleValue(), 0.1);
|
||||
assertEquals(17.6, hsbType.getSaturation().doubleValue(), 0.1);
|
||||
assertEquals(26.3, hsbType.getBrightness().doubleValue(), 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dpt252EncoderTest() {
|
||||
// input data
|
||||
byte[] data = new byte[] { 0x26, 0x2b, 0x31, 0x00, 0x00, 0x0e };
|
||||
HSBType hsbType = (HSBType) ValueDecoder.decode("251.600", data, HSBType.class);
|
||||
|
||||
assertNotNull(hsbType);
|
||||
assertEquals(207, hsbType.getHue().doubleValue(), 0.1);
|
||||
assertEquals(22, hsbType.getSaturation().doubleValue(), 0.1);
|
||||
assertEquals(18, hsbType.getBrightness().doubleValue(), 0.1);
|
||||
}
|
||||
|
||||
// This test checks all our overrides for units. It allows to detect unnecessary overrides when we
|
||||
// update Calimero library
|
||||
@Test
|
||||
public void unitFixes() {
|
||||
// 8bit signed (DPT 6)
|
||||
assertEquals(DPTXlator8BitSigned.DPT_PERCENT_V8.getUnit(), Units.PERCENT.getSymbol());
|
||||
|
||||
// two byte unsigned (DPT 7)
|
||||
assertNotEquals("", DPTXlator2ByteUnsigned.DPT_VALUE_2_UCOUNT.getUnit()); // counts have no unit
|
||||
assertNotEquals(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_10.getUnit(), "ms"); // according to spec, it is ms
|
||||
assertNotEquals(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_100.getUnit(), "ms"); // according to spec, it is ms
|
||||
|
||||
// two byte signed (DPT 8, DPTXlator is missing in calimero 2.5-M1)
|
||||
assertNotEquals("", DptXlator2ByteSigned.DptValueCount.getUnit()); // pulses habe no unit
|
||||
|
||||
// 4 byte unsigned (DPT 12)
|
||||
assertNotEquals("", DPTXlator4ByteUnsigned.DPT_VALUE_4_UCOUNT.getUnit()); // counts have no unit
|
||||
|
||||
// 4 byte signed (DPT 13)
|
||||
assertNotEquals(DPTXlator4ByteSigned.DPT_REACTIVE_ENERGY.getUnit(), Units.VAR_HOUR.toString());
|
||||
assertNotEquals(DPTXlator4ByteSigned.DPT_REACTIVE_ENERGY_KVARH.getUnit(), Units.KILOVAR_HOUR.toString());
|
||||
assertNotEquals(DPTXlator4ByteSigned.DPT_APPARENT_ENERGY_KVAH.getUnit(), "kVA*h");
|
||||
assertNotEquals(DPTXlator4ByteSigned.DPT_FLOWRATE.getUnit(), Units.CUBICMETRE_PER_HOUR.toString());
|
||||
assertNotEquals("", DPTXlator4ByteSigned.DPT_COUNT.getUnit()); // counts have no unit
|
||||
|
||||
// four byte float (DPT 14)
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_CONDUCTANCE.getUnit(), Units.SIEMENS.toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_ANGULAR_MOMENTUM.getUnit(),
|
||||
Units.JOULE.multiply(Units.SECOND).toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_ACTIVITY.getUnit(), Units.BECQUEREL.toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_ELECTRICAL_CONDUCTIVITY.getUnit(),
|
||||
Units.SIEMENS.divide(SIUnits.METRE).toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_TORQUE.getUnit(), Units.NEWTON.multiply(SIUnits.METRE).toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_RESISTIVITY.getUnit(), Units.OHM.multiply(SIUnits.METRE).toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_ELECTRIC_DIPOLEMOMENT.getUnit(),
|
||||
Units.COULOMB.multiply(SIUnits.METRE).toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_ELECTRIC_FLUX.getUnit(), Units.VOLT.multiply(SIUnits.METRE).toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_MAGNETIC_MOMENT.getUnit(),
|
||||
Units.AMPERE.multiply(SIUnits.SQUARE_METRE).toString());
|
||||
assertNotEquals(DPTXlator4ByteFloat.DPT_ELECTROMAGNETIC_MOMENT.getUnit(),
|
||||
Units.AMPERE.multiply(SIUnits.SQUARE_METRE).toString());
|
||||
|
||||
// 64 bit signed (DPT 29)
|
||||
assertNotEquals(DPTXlator64BitSigned.DPT_REACTIVE_ENERGY.getUnit(), Units.VAR_HOUR.toString());
|
||||
}
|
||||
|
||||
private static Stream<String> unitProvider() {
|
||||
return DPTUnits.getAllUnitStrings();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("unitProvider")
|
||||
public void unitsValid(String unit) {
|
||||
String valueStr = "1 " + unit;
|
||||
QuantityType<?> value = new QuantityType<>(valueStr);
|
||||
Assertions.assertNotNull(value);
|
||||
}
|
||||
|
||||
private static Stream<String> rgbValueProvider() {
|
||||
return Stream.of("r:0 g:0 b:0", "r:255 g:255 b:255");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("rgbValueProvider")
|
||||
public void rgbTest(String value) {
|
||||
Assertions.assertNotNull(ValueDecoder.decode("232.600", value.getBytes(StandardCharsets.UTF_8), HSBType.class));
|
||||
}
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.knx.internal.dpt;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Simon Kaufmann - initial contribution and API
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class KNXCoreTypeMapperTest {
|
||||
|
||||
@Test
|
||||
void testToDPTValueTrailingZeroesStrippedOff() {
|
||||
assertEquals("3", new KNXCoreTypeMapper().toDPTValue(new DecimalType("3"), "17.001"));
|
||||
assertEquals("3", new KNXCoreTypeMapper().toDPTValue(new DecimalType("3.0"), "17.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT5ValueFromQuantityType() {
|
||||
assertEquals("80.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("80 %"), "5.001"));
|
||||
|
||||
assertEquals("180.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("180 °"), "5.003"));
|
||||
assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3.14 rad"), "5.003").startsWith("179."));
|
||||
assertEquals("80.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("80 %"), "5.004"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT7ValueFromQuantityType() {
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.002"));
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.003"));
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.004"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.005"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 s"), "7.006"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 min"), "7.007"));
|
||||
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m"), "7.011"));
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 mA"), "7.012"));
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 lx"), "7.013"));
|
||||
|
||||
assertEquals("3000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3000 K"), "7.600"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT8ValueFromQuantityType() {
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.002"));
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.003"));
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.004"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.005"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 s"), "8.006"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 min"), "8.007"));
|
||||
|
||||
assertEquals("180.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("180 °"), "8.011"));
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 km"), "8.012"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT9ValueFromQuantityType() {
|
||||
assertEquals("23.1", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("23.1 °C"), "9.001"));
|
||||
assertEquals("5.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("41 °F"), "9.001"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("274.15 K"), "9.001"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K"), "9.002"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 mK"), "9.002"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C"), "9.002"));
|
||||
assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °F"), "9.002").startsWith("0.55"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K/h"), "9.003"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C/h"), "9.003"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 mK/h"), "9.003"));
|
||||
assertEquals("600.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("10 K/min"), "9.003"));
|
||||
assertEquals("100.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("100 lx"), "9.004"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m/s"), "9.005"));
|
||||
assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1.94 kn"), "9.005").startsWith("0.99"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3.6 km/h"), "9.005"));
|
||||
assertEquals("456.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("456 Pa"), "9.006"));
|
||||
assertEquals("70.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("70 %"), "9.007"));
|
||||
assertEquals("8.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("8 ppm"), "9.008"));
|
||||
assertEquals("9.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("9 m³/h"), "9.009"));
|
||||
assertEquals("10.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("10 s"), "9.010"));
|
||||
assertEquals("11.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("0.011 s"), "9.011"));
|
||||
|
||||
assertEquals("20.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("20 mV"), "9.020"));
|
||||
assertEquals("20.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("0.02 V"), "9.020"));
|
||||
assertEquals("21.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("21 mA"), "9.021"));
|
||||
assertEquals("21.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("0.021 A"), "9.021"));
|
||||
assertEquals("12.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("12 W/m²"), "9.022"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K/%"), "9.023"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C/%"), "9.023"));
|
||||
assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °F/%"), "9.023").startsWith("0.55"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kW"), "9.024"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/h"), "9.025"));
|
||||
assertEquals("60.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/min"), "9.025"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/m²"), "9.026"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °F"), "9.027"));
|
||||
assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("-12 °C"), "9.027").startsWith("10."));
|
||||
assertEquals("10.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("10 km/h"), "9.028"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 g/m³"), "9.029"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 µg/m³"), "9.030"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT10ValueFromQuantityType() {
|
||||
// DateTimeTyype, not QuantityType
|
||||
assertEquals("Wed, 17:30:00",
|
||||
new KNXCoreTypeMapper().toDPTValue(new DateTimeType("2019-06-12T17:30:00Z"), "10.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT11ValueFromQuantityType() {
|
||||
// DateTimeTyype, not QuantityType
|
||||
assertEquals("2019-06-12",
|
||||
new KNXCoreTypeMapper().toDPTValue(new DateTimeType("2019-06-12T17:30:00Z"), "11.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT12ValueFromQuantityType() {
|
||||
// 12.001: dimensionless
|
||||
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 s"), "12.100"));
|
||||
assertEquals("2.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("2 min"), "12.101"));
|
||||
assertEquals("3.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3 h"), "12.102"));
|
||||
|
||||
assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m^3"), "12.1200"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l"), "12.1200"));
|
||||
assertEquals("2.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("2 m³"), "12.1201"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT13ValueFromQuantityType() {
|
||||
// 13.001 dimensionless
|
||||
assertEquals("24.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("24 m³/h"), "13.002"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("24 m³/d"), "13.002"));
|
||||
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 Wh"), "13.010"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 VAh"), "13.011"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 varh"), "13.012"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 kWh"), "13.013"));
|
||||
assertEquals("4.2", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("4200 VAh"), "13.014"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 kvarh"), "13.015"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 MWh"), "13.016"));
|
||||
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 s"), "13.100"));
|
||||
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 l"), "13.1200"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 m³"), "13.1201"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT14ValueFromQuantityType() {
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m/s²"), "14.000"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad/s²"), "14.001"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J/mol"), "14.002"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 /s"), "14.003"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 mol"), "14.004"));
|
||||
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad"), "14.006"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °"), "14.007"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J*s"), "14.008"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad/s"), "14.009"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m²"), "14.010"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 F"), "14.011"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.012"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m³"), "14.013"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m²/N"), "14.014"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 S"), "14.015"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 S/m"), "14.016"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kg/m³"), "14.017"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C"), "14.018"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A"), "14.019"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A/m²"), "14.020"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C*m"), "14.021"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.022"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V/m"), "14.023"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C"), "14.024"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.025"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.026"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V"), "14.027"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V"), "14.028"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A*m²"), "14.029"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V"), "14.030"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.031"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N"), "14.032"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Hz"), "14.033"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad/s"), "14.034"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J/K"), "14.035"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W"), "14.036"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.037"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm"), "14.038"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m"), "14.039"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.040"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 lm*s"), "14.040"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 cd/m²"), "14.041"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 lm"), "14.042"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 cd"), "14.043"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A/m"), "14.044"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Wb"), "14.045"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 T"), "14.046"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A*m²"), "14.047"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 T"), "14.048"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A/m"), "14.049"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A"), "14.050"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kg"), "14.051"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kg/s"), "14.052"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N/s"), "14.053"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad"), "14.054"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °"), "14.055"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W"), "14.056"));
|
||||
// 14.057: dimensionless
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Pa"), "14.058"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm"), "14.059"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm"), "14.060"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm*m"), "14.061"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 H"), "14.062"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 sr"), "14.063"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W/m²"), "14.064"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m/s"), "14.065"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Pa"), "14.066"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N/m"), "14.067"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C"), "14.068"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K"), "14.069"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K"), "14.070"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J/K"), "14.071"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W/m/K"), "14.072"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V/K"), "14.073"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 s"), "14.074"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N*m"), "14.075"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.075"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m³"), "14.076"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m³/s"), "14.077"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N"), "14.078"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.079"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 VA"), "14.080"));
|
||||
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m³/h"), "14.1200"));
|
||||
assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/s"), "14.1201"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT19ValueFromQuantityType() {
|
||||
// DateTimeTyype, not QuantityType
|
||||
assertEquals("2019-06-12 17:30:00",
|
||||
new KNXCoreTypeMapper().toDPTValue(new DateTimeType("2019-06-12T17:30:00Z"), "19.001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
void testToDPT29ValueFromQuantityType() {
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 Wh"), "29.010"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 VAh"), "29.011"));
|
||||
assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 varh"), "29.012"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user