From 53895670891dab751a666a87b39173c45048aca7 Mon Sep 17 00:00:00 2001 From: ML19821 <96960914+ML19821@users.noreply.github.com> Date: Sun, 11 May 2025 15:02:43 +0200 Subject: [PATCH] [freeathome] Add Wireless Blind Actuator support, Improve connection handling (#18631) * [freeathome] Enhancement: Added support for Wireless Blind Actuator devices Signed-off-by: Martin Littkovsky <2018turtle@proton.me> --- .../FreeAtHomeBridgeHandlerConfiguration.java | 1 + .../datamodel/FreeAtHomeDeviceChannel.java | 142 +++-- .../handler/FreeAtHomeBridgeHandler.java | 517 ++++++++++++++---- .../handler/FreeAtHomeDeviceHandler.java | 20 +- .../internal/util/FidTranslationUtils.java | 137 ++++- .../FreeAtHomeHttpCommunicationException.java | 3 +- .../src/main/resources/OH-INF/addon/addon.xml | 2 +- ...ystem.properties => freeathome.properties} | 7 +- .../resources/OH-INF/thing/bridge-type.xml | 14 + 9 files changed, 634 insertions(+), 209 deletions(-) rename bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/i18n/{freeathomesystem.properties => freeathome.properties} (97%) diff --git a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/configuration/FreeAtHomeBridgeHandlerConfiguration.java b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/configuration/FreeAtHomeBridgeHandlerConfiguration.java index 7f29e5cee3d..ce2fcce1646 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/configuration/FreeAtHomeBridgeHandlerConfiguration.java +++ b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/configuration/FreeAtHomeBridgeHandlerConfiguration.java @@ -28,4 +28,5 @@ public class FreeAtHomeBridgeHandlerConfiguration { public String ipAddress = ""; public String username = ""; public String password = ""; + public boolean sendKeepAliveMessage = false; } diff --git a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/datamodel/FreeAtHomeDeviceChannel.java b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/datamodel/FreeAtHomeDeviceChannel.java index 73b9df161eb..c75938d1b87 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/datamodel/FreeAtHomeDeviceChannel.java +++ b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/datamodel/FreeAtHomeDeviceChannel.java @@ -84,7 +84,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -97,12 +97,12 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 17, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -116,7 +116,7 @@ public class FreeAtHomeDeviceChannel { newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 256, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 1, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -129,23 +129,23 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 6, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 7, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1027, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT_AS_OUTPUT, 256, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -161,40 +161,40 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 304, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 333, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 331, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 54, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 51, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 320, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 68, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 58, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 56, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 66, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); // Additional channel for RTC device if (Integer.parseInt(channelFunctionID, 16) == FID_ROOM_TEMPERATURE_CONTROLLER_MASTER_WITHOUT_FAN) { newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 48, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); } break; @@ -208,12 +208,12 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); // 53 AL_WINDOW_DOOR Window/Door Open = 1 / closed = 0 newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 53, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); // 41 AL_WINDOW_DOOR_POSITION Window/Door position Delivers position for Window/Door(Open/Tilted/Closed) newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 41, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -225,7 +225,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 4, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -238,7 +238,7 @@ public class FreeAtHomeDeviceChannel { newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 61698, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 61697, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -251,7 +251,7 @@ public class FreeAtHomeDeviceChannel { newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 256, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 2, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -264,7 +264,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 2, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -277,7 +277,7 @@ public class FreeAtHomeDeviceChannel { newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 256, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 2, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -291,19 +291,46 @@ public class FreeAtHomeDeviceChannel { newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 272, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 17, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 256, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 1, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); + + break; + } + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE0: + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE1: + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE2: + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE3: + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE4: + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE5: + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE6: + case FID_DIMMING_SENSOR_PUSHBUTTON_TYPE7: { + this.channelId = channelId; + + logger.debug("Dimming actuator channel - Channel FID: 0x{}", channelFunctionID); + + FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); + newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 17, channelId, channelObject); + newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 272, channelId, channelObject); + + addDatapointGroup(newDatapointGroup); + + newDatapointGroup = new FreeAtHomeDatapointGroup(); + newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1, channelId, channelObject); + newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 256, channelId, channelObject); + + addDatapointGroup(newDatapointGroup); break; } case FID_AWNING_ACTUATOR: case FID_ATTIC_WINDOW_ACTUATOR: case FID_BLIND_ACTUATOR: + case FID_BLIND_ACTUATOR_WIRELESS: case FID_SHUTTER_ACTUATOR: { this.channelId = channelId; @@ -312,17 +339,17 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 32, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 288, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 33, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 288, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 289, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 35, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); /* * 290 AL_CURRENT_ABSOLUTE_POSITION_SLATS_PERCENTAGE Current Absolute Position Slats Percentage Indicate @@ -333,7 +360,14 @@ public class FreeAtHomeDeviceChannel { newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 290, channelId, channelObject); newDatapointGroup.addDatapointToGroup(DatapointDirection.INPUT, 36, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); + + break; + } + case FID_BLIND_SENSOR: { + this.channelId = channelId; + + logger.warn("Blind sensor channel - Channel FID: 0x{}, not implemented yet", channelFunctionID); break; } @@ -344,11 +378,11 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1026, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1027, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -359,15 +393,15 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 39, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1029, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1030, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -378,17 +412,17 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); if (newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 38, channelId, channelObject)) { - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); } newDatapointGroup = new FreeAtHomeDatapointGroup(); if (newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1024, channelId, channelObject)) { - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); } newDatapointGroup = new FreeAtHomeDatapointGroup(); if (newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 304, channelId, channelObject)) { - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); } break; @@ -400,15 +434,15 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 37, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1025, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1028, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -419,7 +453,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1564, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -430,7 +464,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1563, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -441,7 +475,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 337, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -452,7 +486,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1562, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -463,7 +497,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1565, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -474,7 +508,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1566, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -485,7 +519,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1567, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -496,7 +530,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1569, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -507,7 +541,7 @@ public class FreeAtHomeDeviceChannel { FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 1568, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } @@ -516,19 +550,18 @@ public class FreeAtHomeDeviceChannel { logger.debug("Wind Alarm channel - Channel FID: 0x{}", channelFunctionID); FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 37, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; } - case FID_RAIN_ALARM_SENSOR: { // 0x000E Wind Alarm + case FID_RAIN_ALARM_SENSOR: { // 0x000E Rain Alarm this.channelId = channelId; logger.debug("Rain Alarm channel - Channel FID: 0x{}", channelFunctionID); FreeAtHomeDatapointGroup newDatapointGroup = new FreeAtHomeDatapointGroup(); newDatapointGroup.addDatapointToGroup(DatapointDirection.OUTPUT, 39, channelId, channelObject); - AddDatapointGroup(newDatapointGroup); + addDatapointGroup(newDatapointGroup); break; - } case FID_SCENE_SENSOR: { this.channelId = channelId; @@ -537,7 +570,6 @@ public class FreeAtHomeDeviceChannel { break; } - default: { logger.debug("Unknown channel found - Channel FID: 0x{}", channelFunctionID); @@ -584,7 +616,7 @@ public class FreeAtHomeDeviceChannel { } } - private void AddDatapointGroup(FreeAtHomeDatapointGroup DatapointGroup) { + private void addDatapointGroup(FreeAtHomeDatapointGroup DatapointGroup) { if (DatapointGroup.isValid()) { logger.debug("Datapoint group is added"); datapointGroups.add(DatapointGroup); diff --git a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeBridgeHandler.java b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeBridgeHandler.java index 914c1d8c22e..2467c8e18bd 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeBridgeHandler.java +++ b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeBridgeHandler.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Base64; import java.util.Collection; @@ -83,33 +84,37 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc private final Logger logger = LoggerFactory.getLogger(FreeAtHomeBridgeHandler.class); - private Map mapEventListeners = new ConcurrentHashMap<>(); + private final Map mapEventListeners = new ConcurrentHashMap<>(); // Clients for the network communication - private HttpClient httpClient; + private final HttpClient httpClient; private @Nullable WebSocketClient websocketClient = null; - private FreeAtHomeWebsocketMonitorThread socketMonitor = new FreeAtHomeWebsocketMonitorThread(); + private final FreeAtHomeWebsocketMonitorThread socketMonitor = new FreeAtHomeWebsocketMonitorThread(); private @Nullable QueuedThreadPool jettyThreadPool = null; private volatile @Nullable Session websocketSession = null; - private String sysApUID = "00000000-0000-0000-0000-000000000000"; + private final String sysApUID = "00000000-0000-0000-0000-000000000000"; private String ipAddress = ""; private String username = ""; private String password = ""; + private boolean sendKeepAliveMessage = true; private String baseUrl = ""; private String authField = ""; - private Lock lock = new ReentrantLock(); - private AtomicBoolean httpConnectionOK = new AtomicBoolean(false); - private Condition websocketSessionEstablished = lock.newCondition(); + private String sysapVersion = ""; + + private final Lock lock = new ReentrantLock(); + private final AtomicBoolean httpConnectionOK = new AtomicBoolean(false); + private final Condition websocketSessionEstablished = lock.newCondition(); + private volatile long lastReceivedTime = 0; int numberOfComponents = 0; - private static final int BRIDGE_WEBSOCKET_RECONNECT_DELAY = 60; - private static final int BRIDGE_WEBSOCKET_TIMEOUT = 90; - private static final int BRIDGE_WEBSOCKET_KEEPALIVE = 50; + private static final int BRIDGE_WEBSOCKET_RECONNECT_DELAY = 5; // Seconds + private static final int BRIDGE_WEBSOCKET_TIMEOUT = 90; // Seconds + private static final int BRIDGE_WEBSOCKET_KEEPALIVE = 10; // Seconds private static final String BRIDGE_URL_GETDEVICELIST = "/rest/devicelist"; public FreeAtHomeBridgeHandler(Bridge thing, HttpClient client) { @@ -131,10 +136,85 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc return List.of(FreeAtHomeDiscoveryService.class); } + /** + * Method to fetch SysApp Version + */ + public boolean fetchSysapVersion() { + String url = baseUrl + "/rest/configuration"; + try { + HttpClient client = httpClient; + Request req = client.newRequest(url); + + if (req == null) { + logger.warn("Invalid request object in fetchSysapVersion with the URL [ {} ]", url); + return false; + } + + ContentResponse response = req.send(); + + if (response.getStatus() != 200) { + logger.warn("HTTP request failed in fetchSysapVersion with status [{}] and reason [{}]", + response.getStatus(), response.getReason()); + return false; + } + + String configString = new String(response.getContent()); + + JsonReader reader = new JsonReader(new StringReader(configString)); + reader.setLenient(true); // Deprecated: use reader.setStrictness(Strictness.LENIENT) in future. Kept for + // backward compatibility with older library versions. + JsonElement jsonTree = JsonParser.parseReader(reader); + + if (!jsonTree.isJsonObject()) { + logger.warn("Invalid jsonObject in fetchSysapVersion with the URL [ {} ]", url); + return false; + } + + JsonObject jsonObject = jsonTree.getAsJsonObject(); + JsonObject sysapObject = jsonObject.getAsJsonObject(sysApUID); + + if (sysapObject == null) { + logger.warn("SysAP object not found in fetchSysapVersion with the URL [ {} ]", url); + return false; + } + + JsonObject sysapDetails = sysapObject.getAsJsonObject("sysap"); + if (sysapDetails == null) { + logger.warn("SysAP details not found in fetchSysapVersion with the URL [ {} ]", url); + return false; + } + + JsonElement versionElement = sysapDetails.get("version"); + if (versionElement == null || !versionElement.isJsonPrimitive()) { + logger.warn("Version not found or invalid in fetchSysapVersion with the URL [ {} ]", url); + return false; + } + + sysapVersion = versionElement.getAsString(); + logger.debug("SysAP version fetched: {}", sysapVersion); + return true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.warn("Http communication interrupted in fetchSysapVersion: {}", e.getMessage()); + return false; + } catch (ExecutionException | TimeoutException e) { + logger.warn("Http communication exception in fetchSysapVersion: {}", e.getMessage()); + return false; + } catch (JsonParseException e) { + logger.warn("Invalid JSON in fetchSysapVersion: {}", e.getMessage()); + return false; + } catch (Exception e) { + logger.warn("Unexpected error in fetchSysapVersion: {}", e.getMessage()); + return false; + } + } + /** * Method to get the device list */ public List getDeviceDeviceList() throws FreeAtHomeHttpCommunicationException { + fetchSysapVersion(); + List listOfComponentId = new ArrayList(); boolean ret = false; @@ -232,7 +312,8 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc String deviceString = new String(response.getContent()); JsonReader reader = new JsonReader(new StringReader(deviceString)); - reader.setLenient(true); + reader.setLenient(true); // Deprecated: use reader.setStrictness(Strictness.LENIENT) in future. Kept for + // backward compatibility with older library versions. JsonElement jsonTree = JsonParser.parseReader(reader); if (!jsonTree.isJsonObject()) { @@ -312,7 +393,8 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc String deviceString = new String(response.getContent()); JsonReader reader = new JsonReader(new StringReader(deviceString)); - reader.setLenient(true); + reader.setLenient(true); // Deprecated: use reader.setStrictness(Strictness.LENIENT) in future. Kept for + // backward compatibility with older library versions. JsonElement jsonTree = JsonParser.parseReader(reader); if (!jsonTree.isJsonObject()) { @@ -403,7 +485,8 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc */ public void setDatapointOnWebsocketFeedback(String receivedText) { JsonReader reader = new JsonReader(new StringReader(receivedText)); - reader.setLenient(true); + reader.setLenient(true); // Deprecated: use reader.setStrictness(Strictness.LENIENT) in future. Kept for + // backward compatibility with older library versions. JsonElement jsonTree = JsonParser.parseReader(reader); // check the output @@ -439,7 +522,8 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc public void markDeviceRemovedOnWebsocketFeedback(String receivedText) { JsonReader reader = new JsonReader(new StringReader(receivedText)); - reader.setLenient(true); + reader.setLenient(true); // Deprecated: use reader.setStrictness(Strictness.LENIENT) in future. Kept for + // backward compatibility with older library versions. JsonElement jsonTree = JsonParser.parseReader(reader); // check the output @@ -470,80 +554,114 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc } /** - * Method to open Http connection + * Establishes an HTTP connection to the free@home system. + * This method sets up authentication and tests the connection by making a request. + * + * @return true if the HTTP connection is successfully established, false otherwise. */ public boolean openHttpConnection() { - boolean ret = false; + logger.debug("Attempting to open HTTP connection to free@home system"); try { - // Add authentication credentials. + // Set up authentication for the HTTP client AuthenticationStore auth = httpClient.getAuthenticationStore(); + URI baseUri = new URI(baseUrl); + auth.addAuthenticationResult(new BasicAuthentication.BasicResult(baseUri, username, password)); - URI uri1 = new URI(baseUrl); - auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri1, username, password)); + // Construct the URL for the device list (used as a test endpoint) + String testUrl = baseUrl + BRIDGE_URL_GETDEVICELIST; + logger.debug("Test URL for HTTP connection: {}", testUrl); - String url = baseUrl + BRIDGE_URL_GETDEVICELIST; - - Request req = httpClient.newRequest(url); - ContentResponse res = req.send(); - - // check status - if (res.getStatus() == HttpStatus.OK_200) { - // response OK - httpConnectionOK.set(true); - - ret = true; - - logger.debug("HTTP connection to SysAP is OK"); - } else { - // response NOK, set error - httpConnectionOK.set(false); - - ret = false; + // Create and send a test request + Request request = httpClient.newRequest(testUrl); + if (request == null) { + throw new IllegalStateException("Failed to create HTTP request"); } - } catch (URISyntaxException | InterruptedException | ExecutionException | TimeoutException ex) { - logger.debug("Initial HTTP connection to SysAP is not successful"); - ret = false; + // Send the request and get the response + ContentResponse response = request.send(); + + // Check the response status + int statusCode = response.getStatus(); + + if (statusCode == HttpStatus.OK_200) { + logger.debug("HTTP connection to free@home system established successfullystatus code: {}", statusCode); + httpConnectionOK.set(true); + return true; + } else { + logger.warn("HTTP connection failed. Status code: {}, Reason: {}", statusCode, response.getReason()); + httpConnectionOK.set(false); + return false; + } + } catch (URISyntaxException e) { + logger.warn("Invalid URI syntax for base URL: {}", baseUrl, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.warn("HTTP connection attempt was interrupted", e); + } catch (TimeoutException e) { + logger.warn("HTTP connection attempt timed out", e); + } catch (ExecutionException e) { + logger.warn("Error executing HTTP request", e); + } catch (Exception e) { + logger.warn("Unexpected error while opening HTTP connection", e); } - return ret; + httpConnectionOK.set(false); + return false; } /** - * Method to connect the websocket session + * Method to connect the WebSocket session. + * Attempts to establish a WebSocket connection to the SysAP and handles authentication. + * + * @return true if the connection attempt is initiated successfully, false otherwise */ public boolean connectWebsocketSession() { boolean ret = false; + // Create the WebSocket URI using the configured IP address URI uri = URI.create("ws://" + ipAddress + "/fhapi/v1/api/ws"); + // Combine username and password for authentication String authString = username + ":" + password; - // create base64 encoder + // Create a Base64 encoder Base64.Encoder bas64Encoder = Base64.getEncoder(); - // Encoding string using encoder object + // Encode the authentication string to Base64 String authStringEnc = bas64Encoder.encodeToString(authString.getBytes()); + // Set the Authorization header value authField = "Basic " + authStringEnc; WebSocketClient localWebsocketClient = websocketClient; try { - // Start socket client + // Start the WebSocket client if it exists if (localWebsocketClient != null) { localWebsocketClient.setMaxTextMessageBufferSize(8 * 1024); - localWebsocketClient.setMaxIdleTimeout(BRIDGE_WEBSOCKET_TIMEOUT * 60 * 1000); - localWebsocketClient.setConnectTimeout(BRIDGE_WEBSOCKET_TIMEOUT * 60 * 1000); + + // Set the maximum idle timeout for the WebSocket connection. + // If no activity occurs within this time, the connection will be closed automatically. + localWebsocketClient.setMaxIdleTimeout(BRIDGE_WEBSOCKET_TIMEOUT * 1000); // milliseconds + + // Set the connection timeout for the WebSocket client. + // This defines how long the client should wait before considering the connection attempt as failed. + // Like the idle timeout, the value is converted from seconds to milliseconds. + localWebsocketClient.setConnectTimeout(BRIDGE_WEBSOCKET_TIMEOUT * 1000); // milliseconds localWebsocketClient.start(); ClientUpgradeRequest request = new ClientUpgradeRequest(); request.setHeader("Authorization", authField); - request.setTimeout(BRIDGE_WEBSOCKET_TIMEOUT, TimeUnit.MINUTES); + + // Set the timeout for the WebSocket upgrade process (i.e., the time allowed for the handshake to + // complete). + // Unlike `setConnectTimeout()`, which applies to the lower-level network connection, this timeout + // applies specifically to the WebSocket upgrade request. + // The timeout is specified in SECONDS, using `TimeUnit.SECONDS`. + request.setTimeout(BRIDGE_WEBSOCKET_TIMEOUT, TimeUnit.SECONDS); localWebsocketClient.connect(this, uri, request); - logger.debug("Websocket connection to SysAP is OK, timeout: {}", BRIDGE_WEBSOCKET_TIMEOUT); - + logger.debug("WebSocket connection attempt initiated, timeout: {} seconds", BRIDGE_WEBSOCKET_TIMEOUT); ret = true; } else { ret = false; @@ -599,45 +717,67 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc } /** - * Method to open the websocket connection + * Opens a WebSocket connection to the free@home system. + * This method initializes the thread pool and WebSocket client if they don't exist. + * + * @return true if the WebSocket connection is successfully opened or already exists, false otherwise. */ public boolean openWebSocketConnection() { boolean ret = false; - QueuedThreadPool localThreadPool = jettyThreadPool; + try { + logger.debug("Current Jetty version: {}", org.eclipse.jetty.util.Jetty.VERSION); - if (localThreadPool == null) { - jettyThreadPool = new QueuedThreadPool(); + QueuedThreadPool localThreadPool = jettyThreadPool; - localThreadPool = jettyThreadPool; + if (localThreadPool == null) { + // Create a new thread pool if it doesn't exist + jettyThreadPool = new QueuedThreadPool(); + localThreadPool = jettyThreadPool; - if (localThreadPool != null) { - localThreadPool.setName(FreeAtHomeBridgeHandler.class.getSimpleName()); - localThreadPool.setDaemon(true); - localThreadPool.setStopTimeout(0); - - ret = true; + if (localThreadPool != null) { + localThreadPool.setName(FreeAtHomeBridgeHandler.class.getSimpleName()); + localThreadPool.setDaemon(true); + localThreadPool.setStopTimeout(0); + } else { + throw new IllegalStateException("Failed to create QueuedThreadPool"); + } } - } - WebSocketClient localWebSocketClient = websocketClient; + WebSocketClient localWebSocketClient = websocketClient; - if (localWebSocketClient == null) { - websocketClient = new WebSocketClient(httpClient); + if (localWebSocketClient == null) { + // Create a new WebSocket client if it doesn't exist + logger.debug("Creating new WebSocketClient with Jetty version {}", + org.eclipse.jetty.util.Jetty.VERSION); + localWebSocketClient = new WebSocketClient(httpClient); + websocketClient = localWebSocketClient; - localWebSocketClient = websocketClient; - - if (localWebSocketClient != null) { - localWebSocketClient.setExecutor(jettyThreadPool); - - socketMonitor.start(); - - ret = true; + if (localWebSocketClient != null) { + // Set the executor immediately after creation, before any start + localWebSocketClient.setExecutor(localThreadPool); + // Do not start the client here; let connectWebsocketSession() handle it, see + // localWebsocketClient.start() there + socketMonitor.start(); + ret = true; + } else { + throw new IllegalStateException("WebSocketClient initialization failed"); + } } else { - ret = false; + if (localWebSocketClient.isStarted()) { + logger.debug("WebSocketClient is already started, skipping setExecutor()"); + ret = true; // Client exists and is running, no need to reconfigure + } else { + // Set executor only if the client is not yet started + logger.debug("WebSocketClient exists but not started, setting executor"); + localWebSocketClient.setExecutor(localThreadPool); + socketMonitor.start(); + ret = true; + } } - } else { - ret = true; + } catch (Exception e) { + logger.warn("Error in openWebSocketConnection: {}", e.getMessage()); + ret = false; } return ret; @@ -653,6 +793,8 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc // load configuration FreeAtHomeBridgeHandlerConfiguration locConfig = getConfigAs(FreeAtHomeBridgeHandlerConfiguration.class); + sendKeepAliveMessage = locConfig.sendKeepAliveMessage; + ipAddress = locConfig.ipAddress; if (ipAddress.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, @@ -707,63 +849,133 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc /** * Method to dispose */ + @SuppressWarnings("null") @Override public void dispose() { // let run out the thread + logger.debug("Dispose called, interrupting socket monitor thread"); socketMonitor.interrupt(); + logger.debug("Closing WebSocket connection"); closeWebSocketConnection(); + + if (jettyThreadPool != null) { + try { + logger.debug("Stopping Jetty thread pool"); + jettyThreadPool.stop(); + } catch (Exception e) { + logger.warn("Error stopping Jetty thread pool: {}", e.getMessage()); + } finally { + jettyThreadPool = null; + } + } } /** - * Thread that maintains connection via Websocket. + * Inner class implementing the WebSocket monitor thread. + * This thread continuously monitors the WebSocket connection and attempts to reconnect if it fails. */ private class FreeAtHomeWebsocketMonitorThread extends Thread { - // initial delay to initiate connection - private AtomicInteger reconnectDelay = new AtomicInteger(); + // AtomicInteger to manage the delay (in seconds) before attempting to reconnect the WebSocket. + // This ensures thread-safe updates to the reconnect delay value across multiple threads. + private final AtomicInteger reconnectDelay = new AtomicInteger(); + /** + * Default constructor for the FreeAtHomeWebsocketMonitorThread. + * Initializes a new instance of the monitor thread without any specific configuration. + * The reconnectDelay is implicitly initialized to 0 by AtomicInteger's default constructor. + */ public FreeAtHomeWebsocketMonitorThread() { + // No additional initialization required at this point. + // The reconnectDelay is already set to 0 by default via AtomicInteger. } + /** + * Main execution method of the monitor thread. + * Runs a loop that checks the HTTP connection status and manages WebSocket reconnection attempts. + */ @Override public void run() { - // set initial connect delay to 0 + // Initialize reconnect delay to 0 reconnectDelay.set(0); + int reconnectCounter = 0; // Counter for reconnection attempts - try { - while (!isInterrupted()) { + while (!isInterrupted()) { + try { if (httpConnectionOK.get()) { + reconnectCounter++; + logger.debug("httpConnectionOK: {}", httpConnectionOK.get()); + logger.debug("Attempting WebSocket connection, attempt #{}", reconnectCounter); + + // Attempt to establish the WebSocket connection if (connectSession()) { + logger.debug("WebSocket connection established, starting monitoring"); + int aliveCounter = 0; // Counter for successful alive checks + + // Inner loop to monitor the active WebSocket connection while (isSocketConnectionAlive()) { + logger.debug( + "isSocketConnectionAlive is true, aliveCounter {}, (re)connectCounter {}, sleeping for {}s, SysApp Version {}, jetty {}, lastReceived {}ms ago", + aliveCounter++, reconnectCounter, BRIDGE_WEBSOCKET_KEEPALIVE, sysapVersion, + org.eclipse.jetty.util.Jetty.VERSION, + lastReceivedTime == 0 ? "nothing recieved yet" + : System.currentTimeMillis() - lastReceivedTime); + + // Sleep for the keep-alive interval to periodically check the connection TimeUnit.SECONDS.sleep(BRIDGE_WEBSOCKET_KEEPALIVE); - logger.debug("Sending keep-alive message {}", System.currentTimeMillis()); - sendWebsocketKeepAliveMessage("keep-alive"); + // Send keep-alive message or ping based on configuration + if (sendKeepAliveMessage) { + logger.debug("Sending keep-alive message, System.currentTimeMillis {}ms", + System.currentTimeMillis()); + sendWebsocketKeepAliveMessage("keep-alive"); + } else { + logger.debug("Sending ping message, System.currentTimeMillis {}ms", + System.currentTimeMillis()); + sendWebsocketPing(); + } } + logger.debug("Socket connection closed - isSocketConnectionAlive == false"); + } else { + // Log if the connection attempt failed + logger.debug("WebSocket connection attempt failed"); } - logger.debug("Socket connection closed"); - reconnectDelay.set(BRIDGE_WEBSOCKET_RECONNECT_DELAY); + + // Delay before the next reconnect attempt + logger.debug("Delaying (re)connect request by {} seconds", BRIDGE_WEBSOCKET_RECONNECT_DELAY); + TimeUnit.SECONDS.sleep(BRIDGE_WEBSOCKET_RECONNECT_DELAY); } else { + logger.debug("httpConnectionOK NOT True, this should not happen"); + logger.debug("Retrying in {} seconds", BRIDGE_WEBSOCKET_RECONNECT_DELAY); TimeUnit.SECONDS.sleep(BRIDGE_WEBSOCKET_RECONNECT_DELAY); } + } catch (InterruptedException e) { + // Handle thread interruption (e.g., during shutdown) + Thread.currentThread().interrupt(); + logger.debug("WebSocket monitor thread interrupted as expected during shutdown"); + } catch (IOException e) { + // Handle IO errors (e.g., from sendWebsocketKeepAliveMessage or sendWebsocketPing) + logger.warn("Error in WebSocket communication: {}", e.getMessage()); + try { + // Delay before retrying after an IO error + logger.debug("Retrying after IO error in {} seconds", BRIDGE_WEBSOCKET_RECONNECT_DELAY); + TimeUnit.SECONDS.sleep(BRIDGE_WEBSOCKET_RECONNECT_DELAY); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + logger.debug("Interrupted during IO error recovery {}", ie.getMessage()); + } } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/comm-error.general-websocket-issue"); - } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/comm-error.websocket-keep-alive-error"); } + // Log when the thread stops + logger.debug("WebSocket monitor thread stopped"); } private boolean connectSession() throws InterruptedException { int delay = reconnectDelay.get(); if (delay > 0) { - logger.debug("Delaying (re)connect request by {} seconds.", reconnectDelay); + logger.debug("Delaying (re)connect request by {} seconds.", delay); TimeUnit.SECONDS.sleep(delay); } @@ -778,15 +990,25 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc return false; } - if (websocketSession == null) { - lock.lock(); - try { - websocketSessionEstablished.await(); - } finally { - lock.unlock(); + // Wait for connection to be established or fail with a timeout + lock.lock(); + try { + if (websocketSession == null) { + boolean established = websocketSessionEstablished.await(BRIDGE_WEBSOCKET_TIMEOUT, TimeUnit.SECONDS); + if (!established || websocketSession == null) { + logger.trace("WebSocket connection timed out or failed during establishment"); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/comm-error.general-websocket-issue"); + reconnectDelay.set(BRIDGE_WEBSOCKET_RECONNECT_DELAY); + return false; + } } + } finally { + lock.unlock(); } + logger.debug("WebSocket session successfully established"); + reconnectDelay.set(0); // Reset delay on successful connection return true; } } @@ -803,14 +1025,40 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc } /** - * Get socket alive state - * - * @throws InterruptedException + * Send ping message to SysAp */ - public boolean isSocketConnectionAlive() throws InterruptedException { + public void sendWebsocketPing() throws IOException { Session localSession = websocketSession; - return (localSession != null) ? localSession.isOpen() : false; + if (localSession != null) { + localSession.getRemote().sendPing(ByteBuffer.wrap("ping".getBytes())); + } + } + + /** + * Get socket alive state + * + * @throws InterruptedException + */ + public boolean isSocketConnectionAlive() { + Session localSession = websocketSession; + + if (localSession == null) { + logger.debug("Socket connection is null"); + return false; + } else if (!localSession.isOpen()) { + logger.debug("Socket connection is closed"); + return false; + } else if (lastReceivedTime != 0) { + long timeSinceLastReceived = System.currentTimeMillis() - lastReceivedTime; + if (timeSinceLastReceived > BRIDGE_WEBSOCKET_TIMEOUT * 1000) { + logger.warn("No data received for {} ms, assuming connection is dead", timeSinceLastReceived); + localSession.close(StatusCode.ABNORMAL, "No data received"); + return false; + } + return true; + } + return true; } /** @@ -819,7 +1067,8 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc @Override public void onWebSocketClose(int statusCode, @Nullable String reason) { websocketSession = null; - logger.debug("Socket Closed: [ {} ] {}", statusCode, reason); + lastReceivedTime = 0; + logger.warn("Socket Closed: [ {} ] {}", statusCode, reason); } /** @@ -831,12 +1080,29 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc if (localSession != null) { websocketSession = localSession; + localSession.setIdleTimeout(2 * BRIDGE_WEBSOCKET_KEEPALIVE * 1000); // Unit is milliseconds, + // sendWebsocketPing() and + // sendWebsocketKeepAliveMessage() are + // called every + // BRIDGE_WEBSOCKET_KEEPALIVE, so the + // timeout should be at least 2 times + // that + logger.debug("WebSocket connection to SysAP established successfully, timeout: {} ms", + localSession.getIdleTimeout()); - localSession.setIdleTimeout(-1); + // Fetch the SysAP version after a successful connection establishment + if (fetchSysapVersion()) { + logger.debug("SysAP version fetched successfully after reconnect: {}", sysapVersion); + updateStatus(ThingStatus.ONLINE); // Set the status to ONLINE + } else { + logger.warn("Failed to fetch SysAP version after successful reconnect"); + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/comm-error.fetch-version-error"); + } - logger.debug("Socket Connected - Timeout {} - sesson: {}", localSession.getIdleTimeout(), session); + lastReceivedTime = System.currentTimeMillis(); } else { - logger.debug("Socket Connected - Timeout (invalid) - sesson: (invalid)"); + logger.debug("Socket Connected - Timeout (invalid) - session: (invalid)"); } lock.lock(); @@ -852,13 +1118,31 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc */ @Override public void onWebSocketError(@Nullable Throwable cause) { + Session localSession = websocketSession; + + // Log the error with details if available + if (cause != null) { + logger.warn("WebSocket error occurred: {}", cause.getLocalizedMessage()); + } else { + logger.warn("WebSocket error occurred: unknown cause"); + } + + // Check and close the session if it is still open + if (localSession != null && localSession.isOpen()) { + try { + localSession.close(StatusCode.ABNORMAL, "Closed due to error"); + logger.debug("WebSocket session closed due to error"); + } catch (Exception e) { + logger.warn("Failed to close WebSocket session: {}", e.getMessage()); + } + } + + // Set the session to null to indicate it is no longer valid websocketSession = null; - if (cause != null) { - logger.debug("Socket Error: {}", cause.getLocalizedMessage()); - } else { - logger.debug("Socket Error: unknown"); - } + // Update the thing status to OFFLINE with error details + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "WebSocket connection failed: " + (cause != null ? cause.getMessage() : "unknown error")); } /** @@ -867,7 +1151,7 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc @Override @NonNullByDefault({}) public void onWebSocketBinary(byte[] payload, int offset, int len) { - logger.debug("Binary message received via websocket"); + logger.warn("Binary message received via websocket - It shall not happen with the free@home SysAp"); } /** @@ -876,6 +1160,7 @@ public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSoc @Override public void onWebSocketText(@Nullable String message) { if (message != null) { + lastReceivedTime = System.currentTimeMillis(); if (message.toLowerCase(Locale.US).contains("bye")) { Session localSession = websocketSession; diff --git a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeDeviceHandler.java b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeDeviceHandler.java index 2af2f14e9fd..a2f754e25c0 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeDeviceHandler.java +++ b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/handler/FreeAtHomeDeviceHandler.java @@ -75,13 +75,13 @@ public class FreeAtHomeDeviceHandler extends BaseThingHandler implements FreeAtH private final Logger logger = LoggerFactory.getLogger(FreeAtHomeDeviceHandler.class); private FreeAtHomeDeviceDescription device = new FreeAtHomeDeviceDescription(); - private FreeAtHomeChannelTypeProvider channelTypeProvider; - private TranslationProvider i18nProvider; - private Locale locale; + private final FreeAtHomeChannelTypeProvider channelTypeProvider; + private final TranslationProvider i18nProvider; + private final Locale locale; private Bundle bundle; - private Map mapChannelUID = new HashMap(); - private Map mapEventToChannelUID = new HashMap(); + private final Map mapChannelUID = new HashMap(); + private final Map mapEventToChannelUID = new HashMap(); public FreeAtHomeDeviceHandler(Thing thing, FreeAtHomeChannelTypeProvider channelTypeProvider, TranslationProvider i18nProvider, LocaleProvider localeProvider) { @@ -305,10 +305,12 @@ public class FreeAtHomeDeviceHandler extends BaseThingHandler implements FreeAtH } } + @Override public void onDeviceRemoved() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE); } + @Override public void onDeviceStateChanged(String event, String valueString) { // Get the channle UID belonging to this event ChannelUID channelUID = mapEventToChannelUID.get(event); @@ -499,8 +501,8 @@ public class FreeAtHomeDeviceHandler extends BaseThingHandler implements FreeAtH // in case of output channel, register it for updates if (outputDatapoint != null) { - String eventDatapointID = new String(device.getDeviceId() + "/" + channel.getChannelId() + "/" - + outputDatapoint.getDatapointId()); + String eventDatapointID = device.getDeviceId() + "/" + channel.getChannelId() + "/" + + outputDatapoint.getDatapointId(); mapEventToChannelUID.put(eventDatapointID, channelUID); } @@ -572,8 +574,8 @@ public class FreeAtHomeDeviceHandler extends BaseThingHandler implements FreeAtH // in case of output channel, register it for updates if (outputDatapoint != null) { - String eventDatapointID = new String(device.getDeviceId() + "/" + channel.getChannelId() + "/" - + outputDatapoint.getDatapointId()); + String eventDatapointID = device.getDeviceId() + "/" + channel.getChannelId() + "/" + + outputDatapoint.getDatapointId(); mapEventToChannelUID.put(eventDatapointID, channelUID); } diff --git a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FidTranslationUtils.java b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FidTranslationUtils.java index f9ce65d3888..cbe1491707f 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FidTranslationUtils.java +++ b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FidTranslationUtils.java @@ -29,8 +29,11 @@ public class FidTranslationUtils { public static final int FID_UNKNOWN = 0xFFFFAAFF; // Unknown // free@home constants + // Function IDs https://developer.eu.mybuildings.abb.com/fah_cloud/reference/functionids + // Pairing IDs https://developer.eu.mybuildings.abb.com/fah_cloud/references/pairingids public static final int FID_SWITCH_SENSOR = 0x0000; // Control element public static final int FID_DIMMING_SENSOR = 0x0001; // Dimming sensor + public static final int FID_SHUTTER_SENSOR = 0x0002; // Shutter Sensor public static final int FID_BLIND_SENSOR = 0x0003; // Blind sensor public static final int FID_STAIRCASE_LIGHT_SENSOR = 0x0004; // Stairwell light sensor public static final int FID_FORCE_ON_OFF_SENSOR = 0x0005; // Force On/Off sensor @@ -51,6 +54,8 @@ public class FidTranslationUtils { public static final int FID_UNDERFLOOR_HEATING = 0x0015; // Underfloor heating public static final int FID_FAN_COIL = 0x0016; // Fan Coil public static final int FID_TWO_LEVEL_CONTROLLER = 0x0017; // Two-level controller + public static final int FID_PUSH_BUTTON_SENSOR = 0x0018; // Push button + public static final int FID_RING_INDICATION_SENSOR = 0x0019; // Ring indicator public static final int FID_DES_DOOR_OPENER_ACTUATOR = 0x001A; // Door opener public static final int FID_PROXY = 0x001B; // Proxy public static final int FID_DES_LEVEL_CALL_ACTUATOR = 0x001D; // Door Map.entry System Call Level Actuator @@ -60,15 +65,19 @@ public class FidTranslationUtils { public static final int FID_DES_LIGHT_SWITCH_ACTUATOR = 0x0021; // Corridor light public static final int FID_ROOM_TEMPERATURE_CONTROLLER_MASTER_WITHOUT_FAN = 0x0023; // Room temperature controller public static final int FID_COOLING_ACTUATOR = 0x0024; // Cooling mode + public static final int FID_DAY_NIGHT_SENSOR = 0x0025; // Day/night sensor public static final int FID_HEATING_ACTUATOR = 0x0027; // Heating mode public static final int FID_FORCE_UP_DOWN_SENSOR = 0x0028; // Force-position blind public static final int FID_HEATING_COOLING_ACTUATOR = 0x0029; // Auto. heating/cooling mode public static final int FID_HEATING_COOLING_SENSOR = 0x002A; // Switchover heating/cooling public static final int FID_DES_DEVICE_SETTINGS = 0x002B; // Device settings + public static final int FID_SACE_BLIND_ACTUATOR = 0x002C; // Space blind + public static final int FID_RGB_SENSOR = 0x002D; // RGB sensor public static final int FID_RGB_W_ACTUATOR = 0x002E; // Dim actuator public static final int FID_RGB_ACTUATOR = 0x002F; // Dim actuator public static final int FID_PANEL_SWITCH_SENSOR = 0x0030; // Control element public static final int FID_PANEL_DIMMING_SENSOR = 0x0031; // Dimming sensor + public static final int FID_PANEL_SHUTTER_SENSOR = 0x0032; // Shutter public static final int FID_PANEL_BLIND_SENSOR = 0x0033; // Blind sensor public static final int FID_PANEL_STAIRCASE_LIGHT_SENSOR = 0x0034; // Stairwell light sensor public static final int FID_PANEL_FORCE_ON_OFF_SENSOR = 0x0035; // Force On/Off sensor @@ -83,6 +92,7 @@ public class FidTranslationUtils { public static final int FID_ADDITIONAL_HEATING_ACTUATOR = 0x003D; // Add. stage for heating mode public static final int FID_RADIATOR_ACTUATOR_MASTER = 0x003E; // Radiator thermostate public static final int FID_RADIATOR_ACTUATOR_SLAVE = 0x003F; // Room temperature controller extension unit + public static final int FID_COLORTEMPERATURE_ACTUATOR = 0x0040; // Color temperature public static final int FID_BRIGHTNESS_SENSOR = 0x0041; // Brightness sensor public static final int FID_RAIN_SENSOR = 0x0042; // Rain sensor public static final int FID_TEMPERATURE_SENSOR = 0x0043; // Temperature sensor @@ -93,13 +103,15 @@ public class FidTranslationUtils { public static final int FID_FCA_2_PIPE_HEATING_COOLING = 0x0049; // Auto. heating/cooling mode public static final int FID_FCA_4_PIPE_HEATING_AND_COOLING = 0x004A; // Two valves for heating and cooling public static final int FID_WINDOW_DOOR_ACTUATOR = 0x004B; // Window/Door - public static final int FID_INVERTER_INFO = 0x004E; // ABC - public static final int FID_METER_INFO = 0x004F; // ABD - public static final int FID_BATTERY_INFO = 0x0050; // ACD + public static final int FID_INVERTER_INFO = 0x004E; // Inverter information + public static final int FID_METER_INFO = 0x004F; // Meter information + public static final int FID_BATTERY_INFO = 0x0050; // Battery information public static final int FID_PANEL_TIMER_PROGRAM_SWITCH_SENSOR = 0x0051; // Timer program switch sensor + public static final int FID_SAFETY_SENSOR = 0x0053; // Safety sensor public static final int FID_DOMUSTECH_ZONE = 0x0055; // Zone public static final int FID_CENTRAL_HEATING_ACTUATOR = 0x0056; // Central heating actuator public static final int FID_CENTRAL_COOLING_ACTUATOR = 0x0057; // Central cooling actuator + public static final int FID_LINK_ACTUATOR = 0x0058; // Link actuator public static final int FID_HOUSE_KEEPING = 0x0059; // Housekeeping public static final int FID_MEDIA_PLAYER = 0x005A; // Media Player public static final int FID_PANEL_ROOM_TEMPERATURE_CONTROLLER_SLAVE_FOR_BATTERY_DEVICE = 0x005B; // Panel Room @@ -107,6 +119,10 @@ public class FidTranslationUtils { // Controller Slave // For Battery // Device + public static final int FID_WELCOME_IP_MUTE_SENSOR = 0x005C; // Welcome-IP mute sensor + public static final int FID_WELCOME_IP_MUTE_ACTUATOR = 0x005D; // Welcome-IP mute + public static final int FID_WELCOME_IP_DOOR_OPEN_SENSOR = 0x005E; // Welcome-IP doors open sensor + public static final int FID_WELCOME_IP_SWITCH_SENSOR = 0x005F; // Welcome-IP switch public static final int FID_PANEL_MEDIA_PLAYER_SENSOR = 0x0060; // Media Player Sensor public static final int FID_BLIND_ACTUATOR = 0x0061; // Roller blind actuator public static final int FID_ATTIC_WINDOW_ACTUATOR = 0x0062; // Attic window actuator @@ -125,6 +141,7 @@ public class FidTranslationUtils { public static final int FID_COFFEE_MACHINE = 0x006F; // Coffee machine public static final int FID_FRIDGE_FREEZER = 0x0070; // Fridge/Freezer public static final int FID_TIMER_PROGRAM_OR_ALERT_SWITCH_SENSOR = 0x0071; // Timer program switch sensor + public static final int FID_WELCOME_IP_BELL_INDICATOR_SENSOR = 0x0072; // Welcome-IP bell indicator public static final int FID_CEILING_FAN_ACTUATOR = 0x0073; // Ceiling fan actuator public static final int FID_CEILING_FAN_SENSOR = 0x0074; // Ceiling fan sensor public static final int FID_SPLIT_UNIT_GATEWAY = 0x0075; // Room temperature controller with fan speed level @@ -148,6 +165,10 @@ public class FidTranslationUtils { public static final int FID_ADDITIONAL_COOLING_ACTUATOR = 0x0087; // Add. stage for cooling mode public static final int FID_TWO_LEVEL_HEATING_ACTUATOR = 0x0088; // Two Level Heating Actuator public static final int FID_TWO_LEVEL_COOLING_ACTUATOR = 0x0089; // Two Level Cooling Actuator + public static final int FID_DOOR_LOCK_SENSOR = 0x008A; // Door lock sensor + public static final int FID_DOOR_LOCK_ACTUATOR = 0x008B; // Door lock actuator + public static final int FID_AC_ROUTING = 0x008C; // AC routing + public static final int FID_EXTERNAL_SIREN = 0x008D; // External siren public static final int FID_GLOBAL_ZONE = 0x008E; // Zone public static final int FID_VOLUME_UP_SENSOR = 0x008F; // Volume up public static final int FID_VOLUME_DOWN_SENSOR = 0x0090; // Volume down @@ -168,6 +189,7 @@ public class FidTranslationUtils { public static final int FID_DOMUSCURTAINDETECTOR = 0x009F; // External IR Sensor Curtain public static final int FID_DOMUSSMOKEDETECTOR = 0x00A0; // Smoke Detector public static final int FID_DOMUSFLOODDETECTOR = 0x00A1; // Flood Detection + public static final int FID_HOB = 0x00A2; // HOB public static final int FID_PANEL_SUG_SENSOR = 0x00A3; // Sensor for air-conditioning unit public static final int FID_TWO_LEVEL_HEATING_COOLING_ACTUATOR = 0x00A4; // Two-point controller for heating or // cooling @@ -175,27 +197,47 @@ public class FidTranslationUtils { public static final int FID_WALLBOX = 0x00A6; // Wallbox public static final int FID_PANEL_WALLBOX = 0x00A7; // Wallbox public static final int FID_DOOR_LOCK_CONTROL = 0x00A8; // Door lock control + public static final int FID_DOOR_LOCK_SETTINGS = 0x00A9; // Door lock settings public static final int FID_VRV_GATEWAY = 0x00AA; // Room temperature controller with fan speed level - + public static final int FID_CO_2 = 0x00BA; // Co2 + public static final int FID_VOC = 0x00BB; // Voc + public static final int FID_AIRQUALITY_SENSOR = 0x00BD; // Airquality sensor + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE0 = 0x1008; // Switch sensor push button + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE1 = 0x1009; // Switch sensor push button + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE2 = 0x100A; // Switch sensor push button + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE3 = 0x100B; // Switch sensor push button + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE4 = 0x100C; // Switch sensor push button + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE5 = 0x100D; // Switch sensor push button + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE6 = 0x100E; // Switch sensor push button + public static final int FID_SWITCH_SENSOR_PUSHBUTTON_TYPE7 = 0x100F; // Switch sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE0 = 0x1018; // Dimming sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE1 = 0x1019; // Dimming sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE2 = 0x101A; // Dimming sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE3 = 0x101B; // Dimming sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE4 = 0x101C; // Dimming sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE5 = 0x101D; // Dimming sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE6 = 0x101E; // Dimming sensor push button + public static final int FID_DIMMING_SENSOR_PUSHBUTTON_TYPE7 = 0x101F; // Dimming sensor push button + public static final int FID_MOVEMENT_DETECTOR_FLEX = 0x1090; // Movement detector flex + public static final int FID_MOVEMENT_DETECTOR_TYPE0 = 0x1090; // Movement Detector + public static final int FID_DIMMING_ACTUATOR_FLEX = 0x1810; // Switch actuator flex + public static final int FID_DIMMING_ACTUATOR_TYPE0 = 0x1810; // Dim actuator + public static final int FID_BLIND_ACTUATOR_WIRELESS = 0x1821; // Wireless blind actuator public static final int FID_SCENE_TRIGGER = 0x4800; // Scene trigger - public static final int FID_RULE_SWITCH = 0x4A00; // Scene trigger - - // FID added based on tests - public static final int FID_AIRQUALITYSENSOR_PRESSURE = 0x0E017; - public static final int FID_AIRQUALITYSENSOR_CO2 = 0x0E018; - public static final int FID_AIRQUALITYSENSOR_CO = 0x0E019; - public static final int FID_AIRQUALITYSENSOR_NO2 = 0x0E01A; - public static final int FID_AIRQUALITYSENSOR_O3 = 0x0E01B; - public static final int FID_AIRQUALITYSENSOR_PM10 = 0x0E01C; - public static final int FID_AIRQUALITYSENSOR_PM25 = 0x0E01D; - public static final int FID_AIRQUALITYSENSOR_VOC = 0x0E01E; - public static final int FID_AIRQUALITYSENSOR_HUMIDITY = 0x0B03F; - - public static final int FID_MOVEMENT_DETECTOR_FLEX = 0x1090; - public static final int FID_DIMMING_ACTUATOR_FLEX = 0x1810; + public static final int FID_RULE_SWITCH = 0x4A00; // Rule Switch + public static final int FID_AIRQUALITYSENSOR_HUMIDITY = 0xB03F; // Air quality sensor humidity + public static final int FID_AIRQUALITYSENSOR_PRESSURE = 0xE017; // Air quality sensor Pressure + public static final int FID_AIRQUALITYSENSOR_CO2 = 0xE018; // Air quality sensor CO2 + public static final int FID_AIRQUALITYSENSOR_CO = 0xE019; // Air quality sensor CO + public static final int FID_AIRQUALITYSENSOR_NO2 = 0xE01A; // Air quality sensor NO2 + public static final int FID_AIRQUALITYSENSOR_O3 = 0xE01B; // Air quality sensor O3 + public static final int FID_AIRQUALITYSENSOR_PM10 = 0xE01C; // Air quality sensor PM10 + public static final int FID_AIRQUALITYSENSOR_PM25 = 0xE01D; // Air quality sensor PM25 + public static final int FID_AIRQUALITYSENSOR_VOC = 0xE01E; // Air quality sensor VOC private static final Map MAP_FUNCTION_ID = Map.ofEntries(Map.entry("0x0000", "fid-control-element"), // FID_SWITCH_SENSOR Map.entry("0x0001", "fid-dimming-sensor"), // FID_DIMMING_SENSOR + Map.entry("0x0002", "fid-shutter-sensor"), // FID_SHUTTER_SENSOR Map.entry("0x0003", "fid-blind-sensor"), // FID_BLIND_SENSOR Map.entry("0x0004", "fid-stairwell-light-sensor"), // FID_STAIRCASE_LIGHT_SENSOR Map.entry("0x0005", "fid-force-on/off-sensor"), // FID_FORCE_ON_OFF_SENSOR @@ -214,6 +256,8 @@ public class FidTranslationUtils { Map.entry("0x0015", "fid-underfloor-heating"), // FID_UNDERFLOOR_HEATING Map.entry("0x0016", "fid-fan-coil"), // FID_FAN_COIL Map.entry("0x0017", "fid-two-level-controller"), // FID_TWO_LEVEL_CONTROLLER + Map.entry("0x0018", "fid-push-button"), // FID_PUSH_BUTTON_SENSOR + Map.entry("0x0019", "fid-ring-indicator"), // FID_RING_INDICATION_SENSOR Map.entry("0x001A", "fid-door-opener"), // FID_DES_DOOR_OPENER_ACTUATOR Map.entry("0x001B", "fid-proxy"), // FID_PROXY Map.entry("0x001D", "fid-door-map.entry-system-call-level-actuator"), // FID_DES_LEVEL_CALL_ACTUATOR @@ -223,15 +267,19 @@ public class FidTranslationUtils { Map.entry("0x0021", "fid-corridor-light"), // FID_DES_LIGHT_SWITCH_ACTUATOR Map.entry("0x0023", "fid-room-temperature-controller"), // FID_ROOM_TEMPERATURE_CONTROLLER_MASTER_WITHOUT_FAN Map.entry("0x0024", "fid-cooling-mode"), // FID_COOLING_ACTUATOR + Map.entry("0x0025", "fid-day/night-sensor"), // FID_DAY_NIGHT_SENSOR Map.entry("0x0027", "fid-heating-mode"), // FID_HEATING_ACTUATOR Map.entry("0x0028", "fid-force-position-blind"), // FID_FORCE_UP_DOWN_SENSOR Map.entry("0x0029", "fid-auto.-heating/cooling-mode"), // FID_HEATING_COOLING_ACTUATOR Map.entry("0x002A", "fid-switchover-heating/cooling"), // FID_HEATING_COOLING_SENSOR Map.entry("0x002B", "fid-device-settings"), // FID_DES_DEVICE_SETTINGS + Map.entry("0x002C", "fid-space-blind"), // FID_SACE_BLIND_ACTUATOR + Map.entry("0x002D", "fid-rgb-sensor"), // FID_RGB_SENSOR Map.entry("0x002E", "fid-dim-actuator"), // FID_RGB_W_ACTUATOR Map.entry("0x002F", "fid-dim-actuator"), // FID_RGB_ACTUATOR Map.entry("0x0030", "fid-control-element"), // FID_PANEL_SWITCH_SENSOR Map.entry("0x0031", "fid-dimming-sensor"), // FID_PANEL_DIMMING_SENSOR + Map.entry("0x0032", "fid-shutter"), // FID_PANEL_SHUTTER_SENSOR Map.entry("0x0033", "fid-blind-sensor"), // FID_PANEL_BLIND_SENSOR Map.entry("0x0034", "fid-stairwell-light-sensor"), // FID_PANEL_STAIRCASE_LIGHT_SENSOR Map.entry("0x0035", "fid-force-on/off-sensor"), // FID_PANEL_FORCE_ON_OFF_SENSOR @@ -245,6 +293,7 @@ public class FidTranslationUtils { Map.entry("0x003D", "fid-add.-stage-for-heating-mode"), // FID_ADDITIONAL_HEATING_ACTUATOR Map.entry("0x003E", "fid-radiator-thermostate"), // FID_RADIATOR_ACTUATOR_MASTER Map.entry("0x003F", "fid-room-temperature-controller-extension-unit"), // FID_RADIATOR_ACTUATOR_SLAVE + Map.entry("0x0040", "fid-color-temperature"), // FID_COLORTEMPERATURE_ACTUATOR Map.entry("0x0041", "fid-brightness-sensor"), // FID_BRIGHTNESS_SENSOR Map.entry("0x0042", "fid-rain-sensor"), // FID_RAIN_SENSOR Map.entry("0x0043", "fid-temperature-sensor"), // FID_TEMPERATURE_SENSOR @@ -255,16 +304,22 @@ public class FidTranslationUtils { Map.entry("0x0049", "fid-auto.-heating/cooling-mode"), // FID_FCA_2_PIPE_HEATING_COOLING Map.entry("0x004A", "fid-two-valves-for-heating-and-cooling"), // FID_FCA_4_PIPE_HEATING_AND_COOLING Map.entry("0x004B", "fid-window/door"), // FID_WINDOW_DOOR_ACTUATOR - Map.entry("0x004E", "fid-abc"), // FID_INVERTER_INFO - Map.entry("0x004F", "fid-abd"), // FID_METER_INFO - Map.entry("0x0050", "fid-acd"), // FID_BATTERY_INFO + Map.entry("0x004E", "fid-inverter-information"), // FID_INVERTER_INFO + Map.entry("0x004F", "fid-meter-information"), // FID_METER_INFO + Map.entry("0x0050", "fid-battery-information"), // FID_BATTERY_INFO Map.entry("0x0051", "fid-timer-program-switch-sensor"), // FID_PANEL_TIMER_PROGRAM_SWITCH_SENSOR + Map.entry("0x0053", "fid-safety-sensor"), // FID_SAFETY_SENSOR Map.entry("0x0055", "fid-zone"), // FID_DOMUSTECH_ZONE Map.entry("0x0056", "fid-central-heating-actuator"), // FID_CENTRAL_HEATING_ACTUATOR Map.entry("0x0057", "fid-central-cooling-actuator"), // FID_CENTRAL_COOLING_ACTUATOR + Map.entry("0x0058", "fid-link-actuator"), // FID_LINK_ACTUATOR Map.entry("0x0059", "fid-housekeeping"), // FID_HOUSE_KEEPING Map.entry("0x005A", "fid-media-player"), // FID_MEDIA_PLAYER Map.entry("0x005B", "fid-panel-room-temperature-controller-slave-for-battery-device"), // FID_PANEL_ROOM_TEMPERATURE_CONTROLLER_SLAVE_FOR_BATTERY_DEVICE + Map.entry("0x005C", "fid-welcome-ip-mute-sensor"), // FID_WELCOME_IP_MUTE_SENSOR + Map.entry("0x005D", "fid-welcome-ip-mute"), // FID_WELCOME_IP_MUTE_ACTUATOR + Map.entry("0x005E", "fid-welcome-ip-doors-open-sensor"), // FID_WELCOME_IP_DOOR_OPEN_SENSOR + Map.entry("0x005F", "fid-welcome-ip-switch"), // FID_WELCOME_IP_SWITCH_SENSOR Map.entry("0x0060", "fid-media-player-sensor"), // FID_PANEL_MEDIA_PLAYER_SENSOR Map.entry("0x0061", "fid-roller-blind-actuator"), // FID_BLIND_ACTUATOR Map.entry("0x0062", "fid-attic-window-actuator"), // FID_ATTIC_WINDOW_ACTUATOR @@ -283,6 +338,7 @@ public class FidTranslationUtils { Map.entry("0x006F", "fid-coffee-machine"), // FID_COFFEE_MACHINE Map.entry("0x0070", "fid-fridge/freezer"), // FID_FRIDGE_FREEZER Map.entry("0x0071", "fid-timer-program-switch-sensor"), // FID_TIMER_PROGRAM_OR_ALERT_SWITCH_SENSOR + Map.entry("0x0072", "fid-welcome-ip-bell-indicator"), // FID_WELCOME_IP_BELL_INDICATOR_SENSOR Map.entry("0x0073", "fid-ceiling-fan-actuator"), // FID_CEILING_FAN_ACTUATOR Map.entry("0x0074", "fid-ceiling-fan-sensor"), // FID_CEILING_FAN_SENSOR Map.entry("0x0075", "fid-room-temperature-controller-with-fan-speed-level"), // FID_SPLIT_UNIT_GATEWAY @@ -306,6 +362,10 @@ public class FidTranslationUtils { Map.entry("0x0087", "fid-add.-stage-for-cooling-mode"), // FID_ADDITIONAL_COOLING_ACTUATOR Map.entry("0x0088", "fid-two-level-heating-actuator"), // FID_TWO_LEVEL_HEATING_ACTUATOR Map.entry("0x0089", "fid-two-level-cooling-actuator"), // FID_TWO_LEVEL_COOLING_ACTUATOR + Map.entry("0x008A", "fid-door-lock-sensor"), // FID_DOOR_LOCK_SENSOR + Map.entry("0x008B", "fid-door-lock-actuator"), // FID_DOOR_LOCK_ACTUATOR + Map.entry("0x008C", "fid-ac-routing"), // FID_AC_ROUTING + Map.entry("0x008D", "fid-external-siren"), // FID_EXTERNAL_SIREN Map.entry("0x008E", "fid-zone"), // FID_GLOBAL_ZONE Map.entry("0x008F", "fid-volume-up"), // FID_VOLUME_UP_SENSOR Map.entry("0x0090", "fid-volume-down"), // FID_VOLUME_DOWN_SENSOR @@ -326,15 +386,42 @@ public class FidTranslationUtils { Map.entry("0x009F", "fid-external-ir-sensor-curtain"), // FID_DOMUSCURTAINDETECTOR Map.entry("0x00A0", "fid-smoke-detector"), // FID_DOMUSSMOKEDETECTOR Map.entry("0x00A1", "fid-flood-detection"), // FID_DOMUSFLOODDETECTOR + Map.entry("0x00A2", "fid-hob"), // FID_HOB Map.entry("0x00A3", "fid-sensor-for-air-conditioning-unit"), // FID_PANEL_SUG_SENSOR Map.entry("0x00A4", "fid-two-point-controller-for-heating-or-cooling"), // FID_TWO_LEVEL_HEATING_COOLING_ACTUATOR Map.entry("0x00A5", "fid-slave-thermostat"), // FID_PANEL_THERMOSTAT_CONTROLLER_SLAVE Map.entry("0x00A6", "fid-wallbox"), // FID_WALLBOX Map.entry("0x00A7", "fid-wallbox"), // FID_PANEL_WALLBOX Map.entry("0x00A8", "fid-door-lock-control"), // FID_DOOR_LOCK_CONTROL + Map.entry("0x00A9", "fid-door-lock-settings"), // FID_DOOR_LOCK_SETTINGS Map.entry("0x00AA", "fid-room-temperature-controller-with-fan-speed-level"), // FID_VRV_GATEWAY + Map.entry("0x00BA", "fid-co2"), // FID_CO_2 + Map.entry("0x00BB", "fid-voc"), // FID_VOC + Map.entry("0x00BD", "fid-airquality-sensor"), // FID_AIRQUALITY_SENSOR + Map.entry("0x1008", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE0 + Map.entry("0x1009", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE1 + Map.entry("0x100A", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE2 + Map.entry("0x100B", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE3 + Map.entry("0x100C", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE4 + Map.entry("0x100D", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE5 + Map.entry("0x100E", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE6 + Map.entry("0x100F", "fid-switch-sensor-push-button"), // FID_SWITCH_SENSOR_PUSHBUTTON_TYPE7 + Map.entry("0x1018", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE0 + Map.entry("0x1019", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE1 + Map.entry("0x101A", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE2 + Map.entry("0x101B", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE3 + Map.entry("0x101C", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE4 + Map.entry("0x101D", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE5 + Map.entry("0x101E", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE6 + Map.entry("0x101F", "fid-dimming-sensor-push-button"), // FID_DIMMING_SENSOR_PUSHBUTTON_TYPE7 + Map.entry("0x1090", "fid-movement-detector-flex"), // FID_MOVEMENT_DETECTOR_FLEX + Map.entry("0x1090", "fid-movement-detector"), // FID_MOVEMENT_DETECTOR_TYPE0 + Map.entry("0x1810", "fid-switch-actuator-flex"), // FID_DIMMING_ACTUATOR_FLEX + Map.entry("0x1810", "fid-dim-actuator"), // FID_DIMMING_ACTUATOR_TYPE0 + Map.entry("0x1821", "fid-wireless-blind-actuator"), // FID_BLIND_ACTUATOR_WIRELESS Map.entry("0x4800", "fid-scene-trigger"), // FID_SCENE_TRIGGER Map.entry("0x4A00", "fid-rule-switch"), // FID_RULE_SWITCH + Map.entry("0xB03F", "fid-air-quality-sensor-humidity"), // FID_AIRQUALITYSENSOR_HUMIDITY Map.entry("0xE017", "fid-air-quality-sensor-pressure"), // FID_AIRQUALITYSENSOR_PRESSURE Map.entry("0xE018", "fid-air-quality-sensor-co2"), // FID_AIRQUALITYSENSOR_CO2 Map.entry("0xE019", "fid-air-quality-sensor-co"), // FID_AIRQUALITYSENSOR_CO @@ -342,10 +429,8 @@ public class FidTranslationUtils { Map.entry("0xE01B", "fid-air-quality-sensor-o3"), // FID_AIRQUALITYSENSOR_O3 Map.entry("0xE01C", "fid-air-quality-sensor-pm10"), // FID_AIRQUALITYSENSOR_PM10 Map.entry("0xE01D", "fid-air-quality-sensor-pm25"), // FID_AIRQUALITYSENSOR_PM25 - Map.entry("0xE01E", "fid-air-quality-sensor-voc"), // FID_AIRQUALITYSENSOR_VOC - Map.entry("0xB03F", "fid-air-quality-sensor-humidity"), // FID_AIRQUALITYSENSOR_HUMIDITY - Map.entry("0x1090", "fid-movement-detector-flex"), // FID_MOVEMENT_DETECTOR_FLEX - Map.entry("0x1810", "fid-dim-actuator-flex") // FID_SWITCH_ACTUATOR_FLEX + Map.entry("0xE01E", "fid-air-quality-sensor-voc") // FID_AIRQUALITYSENSOR_VOC + ); @Nullable diff --git a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FreeAtHomeHttpCommunicationException.java b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FreeAtHomeHttpCommunicationException.java index 499feb653a0..afbd8198f4a 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FreeAtHomeHttpCommunicationException.java +++ b/bundles/org.openhab.binding.freeathome/src/main/java/org/openhab/binding/freeathome/internal/util/FreeAtHomeHttpCommunicationException.java @@ -28,7 +28,7 @@ import org.eclipse.jdt.annotation.Nullable; public class FreeAtHomeHttpCommunicationException extends Exception { private static final long serialVersionUID = -817364286035448863L; private String errorMessage = "Unknown_Exception"; - private int errorCode; + private final int errorCode; public FreeAtHomeHttpCommunicationException(int errorCode, String message) { super(message); @@ -37,6 +37,7 @@ public class FreeAtHomeHttpCommunicationException extends Exception { this.errorCode = errorCode; } + @Override public @Nullable String getMessage() { return this.errorMessage; } diff --git a/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/addon/addon.xml index 3733278678a..95de143568f 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/addon/addon.xml @@ -5,6 +5,6 @@ binding FreeAtHome Binding - This is the binding for free@home system. + This is the binding for Busch Jaeger (alternativ Vendor ABB) free@home system. local diff --git a/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/i18n/freeathomesystem.properties b/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/i18n/freeathome.properties similarity index 97% rename from bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/i18n/freeathomesystem.properties rename to bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/i18n/freeathome.properties index fb2ae8d7bc7..e6a547ad437 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/i18n/freeathomesystem.properties +++ b/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/i18n/freeathome.properties @@ -1,7 +1,7 @@ # add-on addon.freeathome.name = FreeAtHome Binding -addon.freeathome.description = This is the binding for free@home system. +addon.freeathome.description = This is the binding for Busch Jaeger (alternativ Vendor ABB) free@home system. # thing types @@ -14,12 +14,16 @@ thing-type.freeathome.gateway.description = This gateway represents the free@hom thing-type.config.freeathome.device.deviceId.label = Device Id thing-type.config.freeathome.device.deviceId.description = This is the unique id of the free@home device (Please do not modify after the Thing is generated) +thing-type.config.freeathome.gateway.group.advanced_com_settings.label = Advanced Communication Settings +thing-type.config.freeathome.gateway.group.advanced_com_settings.description = Detailed settings for SysAp communication behavior thing-type.config.freeathome.gateway.group.identification.label = SysAP Setting thing-type.config.freeathome.gateway.group.identification.description = SysAP network address and user settings thing-type.config.freeathome.gateway.ipAddress.label = Sysap IP Address thing-type.config.freeathome.gateway.ipAddress.description = IP Address of the Busch-Jaeger Gateway thing-type.config.freeathome.gateway.password.label = Password thing-type.config.freeathome.gateway.password.description = Password for gateway +thing-type.config.freeathome.gateway.sendKeepAliveMessage.label = Send keep-alive message to SysAp +thing-type.config.freeathome.gateway.sendKeepAliveMessage.description = Older SysAp might require a keep-alive message over the socket instead of a ping message. thing-type.config.freeathome.gateway.username.label = User Name thing-type.config.freeathome.gateway.username.description = The login name @@ -29,6 +33,7 @@ comm-error.not-able-open-httpconnection = Cannot open http connection, wrong pas comm-error.http-wrongpass-or-ip = Cannot open http connection, wrong password or IP address comm-error.not-able-open-websocketconnection = Cannot open websocket connection comm-error.general-websocket-issue = General issue in websocket connection +comm-error.fetch-version-error = Connected, but failed to fetch SysAP version comm-error.websocket-keep-alive-error = Websocket keep alive error comm-error.wrong-credentials = Wrong credentials for SysAP comm-error.error-in-sysap-com = Error in SysAp communication diff --git a/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/thing/bridge-type.xml b/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/thing/bridge-type.xml index 9c058987424..3974f4b4e29 100644 --- a/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/thing/bridge-type.xml +++ b/bundles/org.openhab.binding.freeathome/src/main/resources/OH-INF/thing/bridge-type.xml @@ -16,6 +16,12 @@ false + + + Detailed settings for SysAp communication behavior + true + + network-address @@ -31,6 +37,14 @@ Password for gateway + + + password + Older SysAp might require a keep-alive message over the socket instead of a ping message. + false + true + +