[knx] Code rework (#17420)

* [knx] Code rework

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Holger Friedrich 2024-09-15 22:48:34 +02:00 committed by Ciprian Pascu
parent 87303e4356
commit ada40c1fc5
24 changed files with 125 additions and 239 deletions

View File

@ -131,7 +131,7 @@ public abstract class KNXChannel {
return new WriteSpecImpl(entry.getValue(), dpt, command);
}
}
// if we didn't find a match, check if we find a sub-type match
// if we didn't find a match, check if we find a subtype match
for (Map.Entry<String, GroupAddressConfiguration> entry : groupAddressConfigurations.entrySet()) {
String dpt = Objects.requireNonNullElse(entry.getValue().getDPT(), getDefaultDPT(entry.getKey()));
Set<Class<? extends Type>> expectedTypeClasses = DPTUtil.getAllowedTypes(dpt);

View File

@ -56,7 +56,7 @@ public final class KNXChannelFactory {
.map(Map.Entry::getValue).findFirst()
.orElseThrow(() -> new IllegalArgumentException(channelTypeUID + " is not a valid channel type ID"));
// typecast to avoid warning about unsafe return type; we know that the lookup returns non null values
// typecast to avoid warning about unsafe return type; we know that the lookup returns non-null values
return (KNXChannel) supplier.apply(channel);
}
}

View File

@ -46,18 +46,12 @@ class TypeColor extends KNXChannel {
@Override
protected String getDefaultDPT(String gaConfigKey) {
if (gaConfigKey.equals(HSB_GA)) {
return DPTXlatorRGB.DPT_RGB.getID();
}
if (gaConfigKey.equals(INCREASE_DECREASE_GA)) {
return DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID();
}
if (gaConfigKey.equals(SWITCH_GA)) {
return DPTXlatorBoolean.DPT_SWITCH.getID();
}
if (gaConfigKey.equals(POSITION_GA)) {
return DPTXlator8BitUnsigned.DPT_SCALING.getID();
}
throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
return switch (gaConfigKey) {
case HSB_GA -> DPTXlatorRGB.DPT_RGB.getID();
case INCREASE_DECREASE_GA -> DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID();
case SWITCH_GA -> DPTXlatorBoolean.DPT_SWITCH.getID();
case POSITION_GA -> DPTXlator8BitUnsigned.DPT_SCALING.getID();
default -> throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
};
}
}

View File

@ -15,7 +15,6 @@ 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;
@ -45,15 +44,11 @@ class TypeDimmer extends KNXChannel {
@Override
protected String getDefaultDPT(String gaConfigKey) {
if (Objects.equals(gaConfigKey, INCREASE_DECREASE_GA)) {
return DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID();
}
if (Objects.equals(gaConfigKey, SWITCH_GA)) {
return DPTXlatorBoolean.DPT_SWITCH.getID();
}
if (Objects.equals(gaConfigKey, POSITION_GA)) {
return DPTXlator8BitUnsigned.DPT_SCALING.getID();
}
throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
return switch (gaConfigKey) {
case INCREASE_DECREASE_GA -> DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID();
case SWITCH_GA -> DPTXlatorBoolean.DPT_SWITCH.getID();
case POSITION_GA -> DPTXlator8BitUnsigned.DPT_SCALING.getID();
default -> throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
};
}
}

View File

@ -15,7 +15,6 @@ 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;
@ -45,15 +44,11 @@ class TypeRollershutter extends KNXChannel {
@Override
protected String getDefaultDPT(String gaConfigKey) {
if (Objects.equals(gaConfigKey, UP_DOWN_GA)) {
return DPTXlatorBoolean.DPT_UPDOWN.getID();
}
if (Objects.equals(gaConfigKey, STOP_MOVE_GA)) {
return DPTXlatorBoolean.DPT_START.getID();
}
if (Objects.equals(gaConfigKey, POSITION_GA)) {
return DPTXlator8BitUnsigned.DPT_SCALING.getID();
}
throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
return switch (gaConfigKey) {
case UP_DOWN_GA -> DPTXlatorBoolean.DPT_UPDOWN.getID();
case STOP_MOVE_GA -> DPTXlatorBoolean.DPT_START.getID();
case POSITION_GA -> DPTXlator8BitUnsigned.DPT_SCALING.getID();
default -> throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
};
}
}

View File

@ -234,7 +234,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
// Protected ctor using given ManagementClientImpl is available (custom class to be inherited)
managementProcedures = new CustomManagementProceduresImpl(managementClient, tl);
// OH helper for reading device info, based on managementClient above
// OpenHab helper for reading device info, based on managementClient above
deviceInfoClient = new DeviceInfoClientImpl(managementClient);
// ProcessCommunicator provides main KNX communication (Calimero).

View File

@ -51,23 +51,7 @@ public class DeviceInspector {
private final DeviceInfoClient client;
private final IndividualAddress address;
public static class Result {
private final Map<String, String> properties;
private final Set<GroupAddress> groupAddresses;
public Result(Map<String, String> properties, Set<GroupAddress> groupAddresses) {
super();
this.properties = properties;
this.groupAddresses = groupAddresses;
}
public Map<String, String> getProperties() {
return properties;
}
public Set<GroupAddress> getGroupAddresses() {
return groupAddresses;
}
public record Result(Map<String, String> properties, Set<GroupAddress> groupAddresses) {
}
public DeviceInspector(DeviceInfoClient client, IndividualAddress address) {
@ -114,7 +98,7 @@ public class DeviceInspector {
* task immediately on connection loss or thing deconstruction.
*
* @param address Individual address of KNX device
* @return List of device properties
* @return Map of device properties
* @throws InterruptedException
*/
private Map<String, String> readDeviceProperties(IndividualAddress address) throws InterruptedException {
@ -179,7 +163,7 @@ public class DeviceInspector {
if (!maxApdu.isEmpty()) {
logger.trace("Max APDU of device {} is {} bytes (routing)", address, maxApdu);
} else {
// fallback: MAX_APDU_LENGTH; if availble set the default is 14 according to spec
// fallback: MAX_APDU_LENGTH; if available set the default is 14 according to spec
Thread.sleep(OPERATION_INTERVAL);
try {
byte[] result = getClient().readDeviceProperties(address, ADDRESS_TABLE_OBJECT,
@ -247,7 +231,7 @@ public class DeviceInspector {
logger.debug("Identified device {} as \"{}\"", address, result);
ret.put(FRIENDLY_NAME, result);
} else {
// this is due to devices which have a buggy implememtation (and show a broken string also
// this is due to devices which have a buggy implementation (and show a broken string also
// in ETS tool)
logger.debug("Ignoring FRIENDLY_NAME of device {} as it contains non-printable characters",
address);
@ -288,7 +272,7 @@ public class DeviceInspector {
* Currently only data from DD0 is returned; DD2 is just logged in debug mode.
*
* @param address Individual address of KNX device
* @return List of device properties
* @return Map of device properties
* @throws InterruptedException
*/
private Map<String, String> readDeviceDescription(IndividualAddress address) throws InterruptedException {
@ -315,7 +299,7 @@ public class DeviceInspector {
if (data != null) {
try {
final DD2 dd = DeviceDescriptor.DD2.from(data);
logger.debug("The device with address {} is has DD2 {}", address, dd.toString());
logger.debug("The device with address {} is has DD2 {}", address, dd);
} catch (KNXIllegalArgumentException e) {
logger.warn("Can not parse device descriptor 2 of device with address {}: {}", address,
e.getMessage());
@ -342,21 +326,14 @@ public class DeviceInspector {
}
private static String getMediumType(int type) {
switch (type) {
case 0:
return "TP";
case 1:
return "PL";
case 2:
return "RF";
case 3:
return "TP0 (deprecated)";
case 4:
return "PL123 (deprecated)";
case 5:
return "IP";
default:
return "unknown (" + type + ")";
}
return switch (type) {
case 0 -> "TP";
case 1 -> "PL";
case 2 -> "RF";
case 3 -> "TP0 (deprecated)";
case 4 -> "PL123 (deprecated)";
case 5 -> "IP";
default -> "unknown (" + type + ")";
};
}
}

View File

@ -37,7 +37,7 @@ public interface InboundSpec {
/**
* Get the affected group addresses.
*
* @return a list of group addresses.
* @return a Set of group addresses.
*/
Set<GroupAddress> getGroupAddresses();
}

View File

@ -66,7 +66,7 @@ public class SerialClient extends AbstractKNXClient {
/**
* try automatic detection of cEMI devices via the PEI identification frame
*
* @implNote This is based on an vendor specific extension and may not work for other devices.
* @implNote This is based on a vendor specific extension and may not work for other devices.
*/
protected boolean detectCemi() throws InterruptedException {
final byte[] peiIdentifyReqFrame = { (byte) 0xa7 };

View File

@ -42,7 +42,7 @@ import tuwien.auto.calimero.serial.spi.SerialCom;
* {@literal @}ServiceProvider annotation (biz.aQute.bnd.annotation) automatically creates the file
* /META-INF/services/tuwien.auto.calimero.serial.spi.SerialCom
* to register SerialTransportAdapter to the service loader.
* Additional attributes for SerialTansportAdapter can be specified as well, e.g.
* Additional attributes for SerialTransportAdapter can be specified as well, e.g.
* attribute = { "position=1" }
* and will be part of MANIFEST.MF
*

View File

@ -54,7 +54,7 @@ public class KNXCommandExtension extends AbstractConsoleCommandExtension impleme
if (args.length == 1 && CMD_LIST_UNKNOWN_GA.equalsIgnoreCase(args[0])) {
for (KNXBridgeBaseThingHandler bridgeHandler : knxHandlerFactory.getBridges()) {
console.println("KNX bridge \"" + bridgeHandler.getThing().getLabel()
+ "\": group address, type, number of bytes, and number of occurence since last reload of binding:");
+ "\": group address, type, number of bytes, and number of occurrence since last reload of binding:");
for (Entry<String, Long> entry : bridgeHandler.getCommandExtensionData().unknownGA().entrySet()) {
console.println(entry.getKey() + " " + entry.getValue());
}

View File

@ -144,7 +144,7 @@ public class DPTUnits {
DPT_UNIT_MAP.put(DPTXlator4ByteFloat.DPT_ELECTROMAGNETIC_MOMENT.getID(),
Units.AMPERE.multiply(SIUnits.SQUARE_METRE).toString());
// 64 bit signed (DPT 29)
// 64-bit signed (DPT 29)
DPT_UNIT_MAP.put(DPTXlator64BitSigned.DPT_REACTIVE_ENERGY.getID(), Units.VAR_HOUR.toString());
}
}

View File

@ -50,7 +50,7 @@ import tuwien.auto.calimero.dptxlator.DPTXlatorString;
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)
// DPT: "123.001", 1-3 digits main type (no leading zero), optional subtype 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

View File

@ -241,32 +241,34 @@ public class ValueDecoder {
private static Type handleDpt1(String subType, DPTXlator translator, Class<? extends Type> preferredType) {
DPTXlatorBoolean translatorBoolean = (DPTXlatorBoolean) translator;
switch (subType) {
case "008":
return translatorBoolean.getValueBoolean() ? UpDownType.DOWN : UpDownType.UP;
case "009":
case "019":
return switch (subType) {
case "008" -> translatorBoolean.getValueBoolean() ? UpDownType.DOWN : UpDownType.UP;
case "009", "019" -> {
// default is OpenClosedType (Contact), but it may be mapped to OnOffType as well
if (OnOffType.class.equals(preferredType)) {
return OnOffType.from(translatorBoolean.getValueBoolean());
yield OnOffType.from(translatorBoolean.getValueBoolean());
}
// 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:
yield translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
// 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
}
case "010" -> translatorBoolean.getValueBoolean() ? StopMoveType.MOVE : StopMoveType.STOP;
case "022" -> DecimalType.valueOf(translatorBoolean.getValueBoolean() ? "1" : "0");
default -> {
// default is OnOffType (Switch), but it may be mapped to OpenClosedType as well
if (OpenClosedType.class.equals(preferredType)) {
return translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
yield translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
}
return OnOffType.from(translatorBoolean.getValueBoolean());
}
yield OnOffType.from(translatorBoolean.getValueBoolean());
}
};
}
private static @Nullable Type handleDpt3(String subType, DPTXlator translator) {
@ -275,17 +277,16 @@ public class ValueDecoder {
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:
return switch (subType) {
case "007" -> translator3BitControlled.getControlBit() ? IncreaseDecreaseType.INCREASE
: IncreaseDecreaseType.DECREASE;
case "008" -> translator3BitControlled.getControlBit() ? UpDownType.DOWN : UpDownType.UP;
default -> {
// should never happen unless Calimero introduces new subtypes
LOGGER.warn("DPT3, subtype '{}' is unknown. Please open an issue.", subType);
return null;
}
yield null;
}
};
}
private static Type handleDpt10(String value) throws ParseException {

View File

@ -198,14 +198,14 @@ public class ValueEncoder {
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
// match Unicode character or °C
if (value.toString().contains(SIUnits.CELSIUS.getSymbol()) || value.toString().contains("°C")) {
if (unit != null) {
unit = unit.replace("K", "°C");
}
} else if (value.toString().contains("°F")) {
// an new approach to handle temperature differences was introduced to core
// after 4.0, stripping the unit and and creating a new QuantityType works
// A new approach to handle temperature differences was introduced to core
// after 4.0, stripping the unit and creating a new QuantityType works
// both with core release 4.0 and current snapshot
boolean perPercent = value.toString().contains("/%");
value = new QuantityType<>(((QuantityType<?>) value).doubleValue() * 5.0 / 9.0, Units.KELVIN);
@ -248,16 +248,12 @@ public class ValueEncoder {
return bigDecimal.stripTrailingZeros().toPlainString();
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();
}
return switch (bigDecimal.intValue()) {
case 0 -> "0 " + valueDPT.getLowerValue();
case 1 -> "0 " + valueDPT.getUpperValue();
case 2 -> "1 " + valueDPT.getLowerValue();
default -> "1 " + valueDPT.getUpperValue();
};
case "18":
int intVal = bigDecimal.intValue();
if (intVal > 63) {
@ -281,7 +277,7 @@ public class ValueEncoder {
/**
* convert 0...100% to 1 byte 0..255
*
* @param percent
* @param percent percentage 0..1
* @return int 0..255
*/
private static int convertPercentToByte(PercentType percent) {

View File

@ -370,9 +370,8 @@ public class DeviceThingHandler extends BaseThingHandler implements GroupAddress
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.
*/
// 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());
@ -478,7 +477,7 @@ public class DeviceThingHandler extends BaseThingHandler implements GroupAddress
DeviceInspector.Result result = inspector.readDeviceInfo();
if (result != null) {
Map<String, String> properties = editProperties();
properties.putAll(result.getProperties());
properties.putAll(result.properties());
updateProperties(properties);
return true;
}

View File

@ -367,7 +367,7 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
/***
* Show all secure group addresses and surrogates. A surrogate is the device which is asked to carry out an indirect
* read/write request.
* Simpler approach w/o surrogates: Security.defaultInstallation().groupSenders().toString());
* Simpler approach w/o surrogates: Security.defaultInstallation().groupSenders().toString();
*/
public static String secHelperGetSecureGroupAddresses(final Security openhabSecurity) {
Map<GroupAddress, Set<String>> groupSendersWithSurrogate = new HashMap<GroupAddress, Set<String>>();
@ -379,7 +379,7 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
IndividualAddress surrogate = null;
try {
surrogate = senders.getOrDefault(ga, Set.of()).stream().findAny().get();
} catch (NoSuchElementException e) {
} catch (NoSuchElementException ignored) {
}
Set<String> devices = new HashSet<String>();
for (var device : entry.getValue()) {

View File

@ -26,7 +26,7 @@ import org.osgi.framework.FrameworkUtil;
* This class provides translations. It is a helper class for i18n / localization efforts.
*
* @implNote It is implemented as a static singleton, enforced by the single-element enum pattern.
* @apiNote {@link #setProvider(LocaleProvider, TranslationProvider)} must be called to provide tanslation service,
* @apiNote {@link #setProvider(LocaleProvider, TranslationProvider)} must be called to provide translation service,
* otherwise all functions will return untranslated text.
* Thread safety is ensured.
* @author Holger Friedrich - Initial contribution
@ -38,9 +38,9 @@ public enum KNXTranslationProvider {
private @Nullable LocaleProvider localeProvider;
private @Nullable TranslationProvider translationProvider;
private Bundle bundle;
private final Bundle bundle;
private KNXTranslationProvider() {
KNXTranslationProvider() {
localeProvider = null;
translationProvider = null;
bundle = FrameworkUtil.getBundle(this.getClass());
@ -59,7 +59,7 @@ public enum KNXTranslationProvider {
final TranslationProvider translationProvider = this.translationProvider;
final LocaleProvider localeProvider = this.localeProvider;
if (translationProvider != null) {
// localeProvider might be null, but if not, getLocale will return NonNull Locale
// localeProvider might be null, but if not, getLocale will return NonNull Locale;
// locale cannot be cached, as getLocale() will return different result once locale is changed by user
final Locale locale = (localeProvider != null) ? localeProvider.getLocale() : Locale.getDefault();
final String res = translationProvider.getText(bundle, text, text, locale, arguments);
@ -67,7 +67,7 @@ public enum KNXTranslationProvider {
return res;
}
}
// translating not possible, we still have the original text without any subsititutions
// translating not possible, we still have the original text without any substitutions
if (arguments == null || arguments.length == 0) {
return text;
}
@ -80,14 +80,14 @@ public enum KNXTranslationProvider {
*
* @param e any exception
* @return localized message in form [description (translated)] [class name], [e.getLocalizedMessage (not
* translated)]), empty string for null. May possibly change in further releases.
* translated)], empty string for null. May change in further releases.
*/
public String getLocalizedException(final Throwable e) {
StringBuilder res = new StringBuilder();
final String exName = e.getClass().getSimpleName();
final String key = "exception." + exName;
final String translatedDescription = KNXTranslationProvider.I18N.get(key);
Boolean foundTranslation = !key.equals(translatedDescription);
boolean foundTranslation = !key.equals(translatedDescription);
// detailed message cannot be translated, e.getLocalizedMessage will likely return English
String detail = e.getLocalizedMessage();
if (detail == null) {

View File

@ -45,9 +45,7 @@ class KNXChannelFactoryTest {
public void testNullChannelUidFails() {
Channel channel = Objects.requireNonNull(mock(Channel.class));
assertThrows(IllegalArgumentException.class, () -> {
KNXChannelFactory.createKnxChannel(channel);
});
assertThrows(IllegalArgumentException.class, () -> KNXChannelFactory.createKnxChannel(channel));
}
@Test
@ -55,9 +53,7 @@ class KNXChannelFactoryTest {
Channel channel = Objects.requireNonNull(mock(Channel.class));
when(channel.getChannelTypeUID()).thenReturn(new ChannelTypeUID("a:b:c"));
assertThrows(IllegalArgumentException.class, () -> {
KNXChannelFactory.createKnxChannel(channel);
});
assertThrows(IllegalArgumentException.class, () -> KNXChannelFactory.createKnxChannel(channel));
}
@ParameterizedTest
@ -65,11 +61,11 @@ class KNXChannelFactoryTest {
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) {
public void testSuccess(String channelType) {
Channel channel = Objects.requireNonNull(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.getChannelTypeUID()).thenReturn(new ChannelTypeUID("knx:" + channelType));
when(channel.getConfiguration()).thenReturn(configuration);
when(channel.getAcceptedItemType()).thenReturn("none");

View File

@ -1,42 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import java.util.Collections;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler.CommandExtensionData;
import org.openhab.core.thing.ThingUID;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.link.KNXNetworkLink;
/**
* {@link AbstractKNXClient} implementation for test, using {@link DummyKNXNetworkLink}.
*
* @author Holger Friedrich - initial contribution and API.
*
*/
@NonNullByDefault
public class DummyClient extends AbstractKNXClient {
public DummyClient() {
super(0, new ThingUID("dummy connection"), 0, 0, 0, null, new CommandExtensionData(Collections.emptyMap()),
null, null);
}
@Override
protected KNXNetworkLink establishConnection() throws KNXException, InterruptedException {
return new DummyKNXNetworkLink();
}
}

View File

@ -26,7 +26,6 @@ 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.binding.knx.internal.itests.Back2BackTest;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
@ -36,8 +35,6 @@ import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.util.ColorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.dptxlator.DPTXlator2ByteUnsigned;
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat;
@ -54,8 +51,6 @@ import tuwien.auto.calimero.dptxlator.DptXlator2ByteSigned;
*/
@NonNullByDefault
class DPTTest {
public static final Logger LOGGER = LoggerFactory.getLogger(Back2BackTest.class);
@Test
void testDptBroken() {
assertNull(ValueEncoder.encode(new DecimalType(), "9.042.1"));
@ -529,7 +524,7 @@ class DPTTest {
assertNotEquals(DPTXlator4ByteFloat.DPT_ELECTROMAGNETIC_MOMENT.getUnit(),
Units.AMPERE.multiply(SIUnits.SQUARE_METRE).toString());
// 64 bit signed (DPT 29)
// 64-bit signed (DPT 29)
assertNotEquals(DPTXlator64BitSigned.DPT_REACTIVE_ENERGY.getUnit(), Units.VAR_HOUR.toString());
}

View File

@ -58,28 +58,21 @@ class KNXBridgeBaseThingHandlerTest {
// router password configured, length must be 16 bytes in hex notation
assertTrue(handler.initializeSecurity("", "", "D947B12DDECAD528B1D5A88FD347F284", "", "", "", ""));
assertTrue(handler.initializeSecurity("", "", "0xD947B12DDECAD528B1D5A88FD347F284", "", "", "", ""));
assertThrows(KnxSecureException.class, () -> {
handler.initializeSecurity("", "", "wrongLength", "", "", "", "");
});
assertThrows(KnxSecureException.class, () -> handler.initializeSecurity("", "", "wrongLength", "", "", "", ""));
// tunnel configuration
assertTrue(handler.initializeSecurity("", "", "", "da", "1", "pw", ""));
// cTunnelUser is restricted to a number >0
assertThrows(KnxSecureException.class, () -> {
handler.initializeSecurity("", "", "", "da", "0", "pw", "");
});
assertThrows(KnxSecureException.class, () -> {
handler.initializeSecurity("", "", "", "da", "eins", "pw", "");
});
assertThrows(KnxSecureException.class, () -> handler.initializeSecurity("", "", "", "da", "0", "pw", ""));
assertThrows(KnxSecureException.class, () -> handler.initializeSecurity("", "", "", "da", "eins", "pw", ""));
// at least one setting for tunnel is given, count as try to configure secure tunnel
// plausibility is checked during initialize()
assertTrue(handler.initializeSecurity("", "", "", "da", "", "", ""));
assertTrue(handler.initializeSecurity("", "", "", "", "1", "", ""));
assertTrue(handler.initializeSecurity("", "", "", "", "", "pw", ""));
assertThrows(KnxSecureException.class, () -> {
handler.initializeSecurity("nonExistingFile.xml", "", "", "", "", "", "");
});
assertThrows(KnxSecureException.class,
() -> handler.initializeSecurity("nonExistingFile.xml", "", "", "", "", "", ""));
Properties pBackup = new Properties(System.getProperties());
try {
@ -91,12 +84,12 @@ class KNXBridgeBaseThingHandlerTest {
p.put(OpenHAB.CONFIG_DIR_PROG_ARGUMENT, testFile.getParent().replaceAll("misc$", ""));
System.setProperties(p);
assertTrue(handler.initializeSecurity(testFile.getName().toString(), passwordString, "", "", "", "pw", ""));
assertTrue(handler.initializeSecurity(testFile.getName(), passwordString, "", "", "", "pw", ""));
assertThrows(KnxSecureException.class, () -> {
assertTrue(handler.initializeSecurity(testFile.getName().toString(), "wrong", "", "", "", "pw", ""));
});
assertThrows(KnxSecureException.class,
() -> assertTrue(handler.initializeSecurity(testFile.getName(), "wrong", "", "", "", "pw", "")));
} catch (URISyntaxException e) {
fail("should never happen");
} finally {
// properties are not persistent, but change may interference with other tests -> restore
System.setProperties(pBackup);

View File

@ -12,10 +12,7 @@
*/
package org.openhab.binding.knx.internal.itests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Arrays;
import java.util.HashSet;
@ -71,7 +68,6 @@ import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
* handled by this test. However, new subtypes are not detected.
*
* @see DummyKNXNetworkLink
* @see DummyClient
* @author Holger Friedrich - Initial contribution
*
*/
@ -80,7 +76,6 @@ public class Back2BackTest {
public static final Logger LOGGER = LoggerFactory.getLogger(Back2BackTest.class);
static Set<Integer> dptTested = new HashSet<>();
static final byte[] F32_MINUS_ONE = new byte[] { (byte) 0xbf, (byte) 0x80, 0, 0 };
boolean testsMissing = false;
/**
* helper method for integration tests
@ -90,7 +85,7 @@ public class Back2BackTest {
* @param ohReferenceData OpenHAB data type, initialized to known good value
* @param maxDistance byte array containing maximal deviations when comparing byte arrays (rawData against created
* KNX frame), may be empty if no deviation is considered
* @param bitmask to mask certain bits in the raw to raw comparison, required for multi-valued KNX frames
* @param bitmask to mask certain bits in the raw to raw comparison, required for multivalued KNX frames
*/
void helper(String dpt, byte[] rawData, Type ohReferenceData, byte[] maxDistance, byte[] bitmask) {
try {
@ -104,7 +99,7 @@ public class Back2BackTest {
DPTUtil.NORMALIZED_DPT.getOrDefault(dpt, dpt));
// 0) check usage of helper()
assertEquals(true, rawData.length > 0);
assertTrue(rawData.length > 0);
if (maxDistance.length == 0) {
maxDistance = new byte[rawData.length];
}
@ -115,7 +110,7 @@ public class Back2BackTest {
}
assertEquals(rawData.length, bitmask.length, "incorrect length of bitmask array");
int mainType = Integer.parseUnsignedInt(dpt.substring(0, dpt.indexOf('.')));
dptTested.add(Integer.valueOf(mainType));
dptTested.add(mainType);
// check if OH would be able to send out a frame, given the type
Set<Integer> knownWorking = Set.of(1, 3, 5);
if (!knownWorking.contains(mainType)) {
@ -146,7 +141,8 @@ public class Back2BackTest {
// 2) check the encoding (ohData to raw data)
//
// Test approach is to a) encode the value into String format using ValueEncoder.encode(),
// Test approach is to
// a) encode the value into String format using ValueEncoder.encode(),
// b) pass it to Calimero for conversion into a raw representation, and
// c) finally grab raw data bytes from a custom KNXNetworkLink implementation
String enc = ValueEncoder.encode(ohData, dpt);
@ -178,7 +174,7 @@ public class Back2BackTest {
pc.close();
} catch (KNXException e) {
LOGGER.warn("exception occurred", e.toString());
LOGGER.warn("exception occurred: {}", e.toString());
assertEquals("", e.toString());
}
}
@ -949,10 +945,10 @@ public class Back2BackTest {
};
TranslatorTypes.getAllMainTypes().forEach((i, t) -> {
if (!dptTested.contains(i)) {
LOGGER.warn("missing tests for main DPT type " + i);
LOGGER.warn("missing tests for main DPT type {}", i);
wrapper.testsMissing = true;
}
});
assertEquals(false, wrapper.testsMissing, "add tests for new DPT main types");
assertFalse(wrapper.testsMissing, "add tests for new DPT main types");
}
}

View File

@ -138,18 +138,15 @@ public class KNXSecurityTest {
Security openhabSecurity = Security.newSecurity();
openhabSecurity.useKeyring(keys, password);
assertThrows(KnxSecureException.class, () -> {
KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
});
assertThrows(KnxSecureException.class,
() -> KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString));
assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
.isPresent());
// now check tunnel (expected to fail, not included)
IndividualAddress secureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
assertThrows(KnxSecureException.class, () -> {
KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
secureTunnelSourceAddr);
});
assertThrows(KnxSecureException.class, () -> KNXBridgeBaseThingHandler
.secHelperReadTunnelConfig(Optional.empty(), passwordString, secureTunnelSourceAddr));
assertTrue(KNXBridgeBaseThingHandler
.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
.isEmpty());
@ -170,18 +167,15 @@ public class KNXSecurityTest {
Security openhabSecurity = Security.newSecurity();
openhabSecurity.useKeyring(keys, password);
assertThrows(KnxSecureException.class, () -> {
KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
});
assertThrows(KnxSecureException.class,
() -> KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString));
assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
.isEmpty());
// now check tunnel
IndividualAddress secureTunnelSourceAddr = new IndividualAddress(1, 1, 2);
assertThrows(KnxSecureException.class, () -> {
KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
secureTunnelSourceAddr);
});
assertThrows(KnxSecureException.class, () -> KNXBridgeBaseThingHandler
.secHelperReadTunnelConfig(Optional.empty(), passwordString, secureTunnelSourceAddr));
assertTrue(KNXBridgeBaseThingHandler
.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
.isPresent());
@ -205,9 +199,8 @@ public class KNXSecurityTest {
openhabSecurity.useKeyring(keys, password);
// now check router settings:
assertThrows(KnxSecureException.class, () -> {
KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
});
assertThrows(KnxSecureException.class,
() -> KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString));
String bbKeyHex = "D947B12DDECAD528B1D5A88FD347F284";
byte[] bbKeyParsedLower = KNXBridgeBaseThingHandler.secHelperParseBackboneKey(bbKeyHex.toLowerCase());
byte[] bbKeyParsedUpper = KNXBridgeBaseThingHandler.secHelperParseBackboneKey(bbKeyHex);
@ -225,10 +218,8 @@ public class KNXSecurityTest {
// now check tunnel settings:
IndividualAddress secureTunnelSourceAddr = new IndividualAddress(1, 1, 2);
IndividualAddress noSecureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
assertThrows(KnxSecureException.class, () -> {
KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
secureTunnelSourceAddr);
});
assertThrows(KnxSecureException.class, () -> KNXBridgeBaseThingHandler
.secHelperReadTunnelConfig(Optional.empty(), passwordString, secureTunnelSourceAddr));
assertTrue(KNXBridgeBaseThingHandler
.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, noSecureTunnelSourceAddr)
.isEmpty());