From 3b0c2ca19fdb7aa36bb4dfe74694e83fd87fa608 Mon Sep 17 00:00:00 2001 From: ranzen84 <53980214+ranzen84@users.noreply.github.com> Date: Sat, 19 Feb 2022 21:35:11 +0100 Subject: [PATCH 01/19] Update README.md (#12322) --- bundles/org.openhab.binding.goecharger/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.goecharger/README.md b/bundles/org.openhab.binding.goecharger/README.md index a2925c164c7f4..1a8317e98a0d5 100644 --- a/bundles/org.openhab.binding.goecharger/README.md +++ b/bundles/org.openhab.binding.goecharger/README.md @@ -88,7 +88,7 @@ when Item availablePVCurrent received update then logInfo("Amps available: ", receivedCommand.state) - MaxAmpere.sendCommand(receivedCommand.state) + GoEChargerMaxCurrent.sendCommand(receivedCommand.state) end ``` You can also define more advanced rules if you have multiple cars that charge with a different amount of phases. From 187937fbd170c80988a32029bc8e342c9de84e15 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Sat, 19 Feb 2022 21:37:23 +0100 Subject: [PATCH 02/19] Catch exception in case of connection to database failed during activation of servive (#12313) Signed-off-by: Christoph Weitkamp --- .../persistence/jdbc/internal/JdbcMapper.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java index 3a39efb32372e..3fcdf1e3d4867 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java @@ -12,6 +12,7 @@ */ package org.openhab.persistence.jdbc.internal; +import java.sql.SQLInvalidAuthorizationSpecException; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; @@ -34,6 +35,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; + /** * Mapper class * @@ -203,13 +206,24 @@ public boolean deleteItemValues(FilterCriteria filter, String table) { /*********************** * DATABASE CONNECTION * ***********************/ + @SuppressWarnings("null") protected boolean openConnection() { logger.debug("JDBC::openConnection isDriverAvailable: {}", conf.isDriverAvailable()); if (conf.isDriverAvailable() && !conf.isDbConnected()) { logger.info("JDBC::openConnection: Driver is available::Yank setupDataSource"); - Yank.setupDefaultConnectionPool(conf.getHikariConfiguration()); - conf.setDbConnected(true); - return true; + try { + Yank.setupDefaultConnectionPool(conf.getHikariConfiguration()); + conf.setDbConnected(true); + return true; + } catch (PoolInitializationException e) { + if (e.getCause() instanceof SQLInvalidAuthorizationSpecException) { + logger.warn("JDBC::openConnection: failed to open connection: {}", e.getCause().getMessage()); + } else { + logger.warn("JDBC::openConnection: failed to open connection: {}", e.getMessage()); + } + initialized = false; + return false; + } } else if (!conf.isDriverAvailable()) { logger.warn("JDBC::openConnection: no driver available!"); initialized = false; From 33b613d2830df3b46bed411c83fe4c8600841f87 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sat, 19 Feb 2022 21:37:51 +0100 Subject: [PATCH 03/19] [hueemulation] Provide non-null context to TriggerHandlerCallback (#12320) This prevents NPEs and a compilation issue when openhab/openhab-core#2763 is merged. Signed-off-by: Wouter Born --- .../internal/automation/AbsoluteDateTimeTriggerHandler.java | 3 ++- .../internal/automation/TimerTriggerHandler.java | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/AbsoluteDateTimeTriggerHandler.java b/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/AbsoluteDateTimeTriggerHandler.java index 8d6e1d22e711f..6d27286fb1d07 100644 --- a/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/AbsoluteDateTimeTriggerHandler.java +++ b/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/AbsoluteDateTimeTriggerHandler.java @@ -19,6 +19,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; +import java.util.Map; import org.openhab.core.automation.ModuleHandlerCallback; import org.openhab.core.automation.Trigger; @@ -106,7 +107,7 @@ public synchronized void dispose() { @Override public void run() { - ((TriggerHandlerCallback) callback).triggered(module, null); + ((TriggerHandlerCallback) callback).triggered(module, Map.of()); schedule = null; } } diff --git a/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/TimerTriggerHandler.java b/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/TimerTriggerHandler.java index dd7e8f129f8c5..3585c15d6f89f 100644 --- a/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/TimerTriggerHandler.java +++ b/bundles/org.openhab.io.hueemulation/src/main/java/org/openhab/io/hueemulation/internal/automation/TimerTriggerHandler.java @@ -13,6 +13,7 @@ package org.openhab.io.hueemulation.internal.automation; import java.time.Duration; +import java.util.Map; import java.util.Random; import java.util.concurrent.Callable; @@ -32,7 +33,7 @@ * As soon as that time has run up, it will trigger. *

* A random factor and repeat times can also be configured. - * + * * @author David Graeff - Initial contribution */ @NonNullByDefault @@ -114,7 +115,7 @@ public synchronized void dispose() { @Override public Duration call() { - ((TriggerHandlerCallback) callback).triggered(module, null); + ((TriggerHandlerCallback) callback).triggered(module, Map.of()); config.repeat -= 1; if (config.repeat == 0) { schedule = null; From 2e72ac78fdbf55b0fb53030388f624e4c10b09e2 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 19 Feb 2022 21:48:38 +0100 Subject: [PATCH 04/19] [hdpowerview] Add support for setting repeater LED color and brightness (#12308) * Add support for setting repeater LED color and brightness. Fixes #12307 Signed-off-by: Jacob Laursen --- .../org.openhab.binding.hdpowerview/README.md | 7 +++ .../internal/HDPowerViewBindingConstants.java | 2 + .../internal/HDPowerViewWebTargets.java | 18 ++++++ .../hdpowerview/internal/api/Color.java | 57 +++++++++++++++++++ .../internal/api/requests/RepeaterColor.java | 40 +++++++++++++ .../internal/api/responses/RepeaterData.java | 2 + .../handler/HDPowerViewRepeaterHandler.java | 50 ++++++++++++++-- .../OH-INF/i18n/hdpowerview.properties | 2 + .../resources/OH-INF/thing/thing-types.xml | 6 ++ 9 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/Color.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterColor.java diff --git a/bundles/org.openhab.binding.hdpowerview/README.md b/bundles/org.openhab.binding.hdpowerview/README.md index 06547b6345af6..d7ef2d2addd7a 100644 --- a/bundles/org.openhab.binding.hdpowerview/README.md +++ b/bundles/org.openhab.binding.hdpowerview/README.md @@ -105,6 +105,8 @@ All of these channels appear in the binding, but only those which have a physica | Channel | Item Type | Description | |-----------------|-----------|-------------------------------| +| color | Color | Controls the color of the LED ring. A switch item can be linked: ON = white, OFF = turn off | +| brightness | Dimmer | Controls the brightness of the LED ring. | | identify | String | Flash repeater to identify. Valid values are: `IDENTIFY` | | blinkingEnabled | Switch | Blink during commands. | @@ -238,6 +240,8 @@ Number Living_Room_Shade_SignalStrength "Living Room Shade Signal Strength" {cha Repeater items: ``` +Color Bedroom_Repeater_Color "Bedroom Repeater Color" {channel="hdpowerview:repeater:home:r16384:color"} +Dimmer Bedroom_Repeater_Brightness "Bedroom Repeater Brightness" {channel="hdpowerview:repeater:home:r16384:brightness"} String Bedroom_Repeater_Identify "Bedroom Repeater Identify" {channel="hdpowerview:repeater:home:r16384:identify"} Switch Bedroom_Repeater_BlinkingEnabled "Bedroom Repeater Blinking Enabled [%s]" {channel="hdpowerview:repeater:home:r16384:blinkingEnabled"} ``` @@ -272,6 +276,9 @@ Frame label="Living Room" { Text item=Living_Room_Shade_Battery_Voltage } Frame label="Bedroom" { + Colorpicker item=PowerViewRepeater_Color + Switch item=PowerViewRepeater_Color + Slider item=PowerViewRepeater_Brightness Switch item=Bedroom_Repeater_Identify mappings=[IDENTIFY="Identify"] Switch item=Bedroom_Repeater_BlinkingEnabled } diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java index b8800f394c159..1b19597dfa770 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java @@ -47,6 +47,8 @@ public class HDPowerViewBindingConstants { public static final String CHANNEL_SHADE_BATTERY_VOLTAGE = "batteryVoltage"; public static final String CHANNEL_SHADE_SIGNAL_STRENGTH = "signalStrength"; + public static final String CHANNEL_REPEATER_COLOR = "color"; + public static final String CHANNEL_REPEATER_BRIGHTNESS = "brightness"; public static final String CHANNEL_REPEATER_IDENTIFY = "identify"; public static final String CHANNEL_REPEATER_BLINKING_ENABLED = "blinkingEnabled"; diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java index aa7dd2f3ccc90..654e2f3c60bcf 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java @@ -26,8 +26,10 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.openhab.binding.hdpowerview.internal.api.Color; import org.openhab.binding.hdpowerview.internal.api.ShadePosition; import org.openhab.binding.hdpowerview.internal.api.requests.RepeaterBlinking; +import org.openhab.binding.hdpowerview.internal.api.requests.RepeaterColor; import org.openhab.binding.hdpowerview.internal.api.requests.ShadeCalibrate; import org.openhab.binding.hdpowerview.internal.api.requests.ShadeJog; import org.openhab.binding.hdpowerview.internal.api.requests.ShadeMove; @@ -510,6 +512,22 @@ public RepeaterData enableRepeaterBlinking(int repeaterId, boolean enable) return repeaterDataFromJson(jsonResponse); } + /** + * Sets color and brightness for a repeater + * + * @param repeaterId id of the repeater for which to set color and brightness + * @return RepeaterData class instance + * @throws HubInvalidResponseException if response is invalid + * @throws HubProcessingException if there is any processing error + * @throws HubMaintenanceException if the hub is down for maintenance + */ + public RepeaterData setRepeaterColor(int repeaterId, Color color) + throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException { + String jsonRequest = gson.toJson(new RepeaterColor(repeaterId, color)); + String jsonResponse = invoke(HttpMethod.PUT, repeaters + repeaterId, null, jsonRequest); + return repeaterDataFromJson(jsonResponse); + } + /** * Invoke a call on the hub server to retrieve information or send a command * diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/Color.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/Color.java new file mode 100644 index 0000000000000..39f1d0f47b24f --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/Color.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2022 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.hdpowerview.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.HSBType; + +/** + * Color and brightness information for HD PowerView repeater + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class Color { + public int brightness; + public int red; + public int green; + public int blue; + + public Color(int brightness, HSBType hsbType) { + this.brightness = brightness; + int rgb = hsbType.getRGB(); + java.awt.Color color = new java.awt.Color(rgb); + red = color.getRed(); + green = color.getGreen(); + blue = color.getBlue(); + } + + public Color(int brightness, java.awt.Color color) { + this.brightness = brightness; + red = color.getRed(); + green = color.getGreen(); + blue = color.getBlue(); + } + + public Color(int brightness, int red, int green, int blue) { + this.brightness = brightness; + this.red = red; + this.green = green; + this.blue = blue; + } + + @Override + public String toString() { + return String.format("%d.%d.%d/%d%%", red, green, blue, brightness); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterColor.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterColor.java new file mode 100644 index 0000000000000..960e95f5878c9 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/RepeaterColor.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2022 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.hdpowerview.internal.api.requests; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.hdpowerview.internal.api.Color; + +/** + * Color state of a single Repeater for being updated by an HD PowerView Hub + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class RepeaterColor { + public Repeater repeater; + + public class Repeater { + public int id; + public Color color; + + public Repeater(int id, Color color) { + this.id = id; + this.color = color; + } + } + + public RepeaterColor(int id, Color color) { + repeater = new Repeater(id, color); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java index 8b0c255309ec3..a083f91a35a76 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/responses/RepeaterData.java @@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hdpowerview.internal.api.Color; import org.openhab.binding.hdpowerview.internal.api.Firmware; /** @@ -31,6 +32,7 @@ public class RepeaterData { public int groupId; public boolean blinkEnabled; public @Nullable Firmware firmware; + public @Nullable Color color; public String getName() { return new String(Base64.getDecoder().decode(name)); diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java index 26b465ed99440..1274ff1faa597 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java @@ -20,13 +20,16 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets; +import org.openhab.binding.hdpowerview.internal.api.Color; import org.openhab.binding.hdpowerview.internal.api.Firmware; import org.openhab.binding.hdpowerview.internal.api.responses.RepeaterData; import org.openhab.binding.hdpowerview.internal.config.HDPowerViewRepeaterConfiguration; import org.openhab.binding.hdpowerview.internal.exceptions.HubException; import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException; import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException; +import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; @@ -112,11 +115,43 @@ public void handleCommand(ChannelUID channelUID, Command command) { RepeaterData repeaterData; switch (channelUID.getId()) { + case CHANNEL_REPEATER_COLOR: + if (command instanceof HSBType) { + Color currentColor = webTargets.getRepeater(repeaterId).color; + if (currentColor != null) { + HSBType hsbCommand = (HSBType) command; + var color = new Color(currentColor.brightness, hsbCommand); + repeaterData = webTargets.setRepeaterColor(repeaterId, color); + scheduler.submit(() -> updatePropertyAndStates(repeaterData)); + } + } else if (command instanceof OnOffType) { + Color currentColor = webTargets.getRepeater(repeaterId).color; + if (currentColor != null) { + var color = command == OnOffType.ON + ? new Color(currentColor.brightness, java.awt.Color.WHITE) + : new Color(currentColor.brightness, java.awt.Color.BLACK); + repeaterData = webTargets.setRepeaterColor(repeaterId, color); + scheduler.submit(() -> updatePropertyAndStates(repeaterData)); + } + } + break; + case CHANNEL_REPEATER_BRIGHTNESS: + if (command instanceof PercentType) { + Color currentColor = webTargets.getRepeater(repeaterId).color; + if (currentColor != null) { + PercentType brightness = (PercentType) command; + var color = new Color(brightness.intValue(), currentColor.red, currentColor.green, + currentColor.blue); + repeaterData = webTargets.setRepeaterColor(repeaterId, color); + scheduler.submit(() -> updatePropertyAndStates(repeaterData)); + } + } + break; case CHANNEL_REPEATER_IDENTIFY: if (command instanceof StringType) { if (COMMAND_IDENTIFY.equals(((StringType) command).toString())) { repeaterData = webTargets.identifyRepeater(repeaterId); - scheduler.submit(() -> updatePropertyAndState(repeaterData)); + scheduler.submit(() -> updatePropertyAndStates(repeaterData)); cancelResetIdentifyStateJob(); resetIdentifyStateFuture = scheduler.schedule(() -> { updateState(CHANNEL_REPEATER_IDENTIFY, UnDefType.UNDEF); @@ -129,7 +164,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_REPEATER_BLINKING_ENABLED: repeaterData = webTargets.enableRepeaterBlinking(repeaterId, OnOffType.ON == command); - scheduler.submit(() -> updatePropertyAndState(repeaterData)); + scheduler.submit(() -> updatePropertyAndStates(repeaterData)); break; } } catch (HubInvalidResponseException e) { @@ -185,7 +220,7 @@ private synchronized void poll() { logger.debug("Polling for status information"); RepeaterData repeaterData = webTargets.getRepeater(repeaterId); - updatePropertyAndState(repeaterData); + updatePropertyAndStates(repeaterData); } catch (HubInvalidResponseException e) { Throwable cause = e.getCause(); @@ -201,7 +236,7 @@ private synchronized void poll() { } } - private void updatePropertyAndState(RepeaterData repeaterData) { + private void updatePropertyAndStates(RepeaterData repeaterData) { updateStatus(ThingStatus.ONLINE); Firmware firmware = repeaterData.firmware; @@ -212,6 +247,13 @@ private void updatePropertyAndState(RepeaterData repeaterData) { logger.warn("Repeater firmware version missing in response"); } + Color color = repeaterData.color; + if (color != null) { + logger.debug("Repeater color data received: {}", color.toString()); + updateState(CHANNEL_REPEATER_COLOR, HSBType.fromRGB(color.red, color.green, color.red)); + updateState(CHANNEL_REPEATER_BRIGHTNESS, new PercentType(color.brightness)); + } + updateState(CHANNEL_REPEATER_BLINKING_ENABLED, repeaterData.blinkEnabled ? OnOffType.ON : OnOffType.OFF); } } diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties index d09d2fd988953..e72a760ba8208 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties @@ -7,6 +7,8 @@ binding.hdpowerview.description = The Hunter Douglas PowerView binding provides thing-type.hdpowerview.hub.label = PowerView Hub thing-type.hdpowerview.hub.description = Hunter Douglas (Luxaflex) PowerView Hub +thing-type.hdpowerview.repeater.channel.brightness.description = Controls the brightness of the LED ring +thing-type.hdpowerview.repeater.channel.color.description = Controls the color of the LED ring thing-type.hdpowerview.repeater.label = PowerView Repeater thing-type.hdpowerview.repeater.description = Hunter Douglas (Luxaflex) PowerView Repeater thing-type.hdpowerview.shade.label = PowerView Shade diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml index a0c845f803a73..95aaca7fc56d7 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml @@ -90,6 +90,12 @@ Hunter Douglas (Luxaflex) PowerView Repeater + + Controls the color of the LED ring + + + Controls the brightness of the LED ring + From 6000bc75fb57fd12752ffd1fd180ed6803eb12e5 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sun, 20 Feb 2022 06:50:12 +1000 Subject: [PATCH 05/19] [daikin] Add the ability to disable background discovery (#12300) Signed-off-by: Jimmy Tanagra --- bundles/org.openhab.binding.daikin/README.md | 8 +++ .../DaikinACUnitDiscoveryService.java | 52 +++++++++---------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.binding.daikin/README.md b/bundles/org.openhab.binding.daikin/README.md index ff9d1f2e61cb7..f2ded25e3664b 100644 --- a/bundles/org.openhab.binding.daikin/README.md +++ b/bundles/org.openhab.binding.daikin/README.md @@ -13,6 +13,14 @@ This may work with the older KRP series of wired adapters, but has not been test This add-on will broadcast messages on your local network looking for Daikin air conditioning units and adding them to the queue of new items discovered. You can also manually add a new item if you know the IP address. +Background discovery polls the network every minute for devices. +Background discovery is **enabled** by default. +To **disable** background discovery, add the following line to the *conf/services/runtime.cfg* file: + +```text +discovery.daikin:background=false +``` + ### BRP072C42 adapter discovery A BRP072C42 adapter requires a registered UUID to authenticate. Upon discovery, a UUID will be generated but the adapter's key must be entered in the Thing configuration to complete the UUID registration. diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java index eb2f89ce2b321..7a58fdb48b05e 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java @@ -53,7 +53,7 @@ * @author Paul Smedley - Modifications to support Airbase Controllers * */ -@Component(service = DiscoveryService.class) +@Component(service = DiscoveryService.class, configurationPid = "discovery.daikin") @NonNullByDefault public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService { private static final String UDP_PACKET_CONTENTS = "DAIKIN_UDP/common/basic_info"; @@ -62,17 +62,15 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService { private Logger logger = LoggerFactory.getLogger(DaikinACUnitDiscoveryService.class); private @Nullable HttpClient httpClient; - private final Runnable scanner; private @Nullable ScheduledFuture backgroundFuture; public DaikinACUnitDiscoveryService() { super(Collections.singleton(DaikinBindingConstants.THING_TYPE_AC_UNIT), 600, true); - scanner = createScanner(); } @Override protected void startScan() { - scheduler.execute(scanner); + scheduler.execute(this::scanner); } @Override @@ -83,7 +81,7 @@ protected void startBackgroundDiscovery() { backgroundFuture.cancel(true); backgroundFuture = null; } - backgroundFuture = scheduler.scheduleWithFixedDelay(scanner, 0, 60, TimeUnit.SECONDS); + backgroundFuture = scheduler.scheduleWithFixedDelay(this::scanner, 0, 60, TimeUnit.SECONDS); } @Override @@ -96,32 +94,30 @@ protected void stopBackgroundDiscovery() { super.stopBackgroundDiscovery(); } - private Runnable createScanner() { - return () -> { - long timestampOfLastScan = getTimestampOfLastScan(); - for (InetAddress broadcastAddress : getBroadcastAddresses()) { - logger.trace("Starting broadcast for {}", broadcastAddress.toString()); - - try (DatagramSocket socket = new DatagramSocket()) { - socket.setBroadcast(true); - socket.setReuseAddress(true); - byte[] packetContents = UDP_PACKET_CONTENTS.getBytes(StandardCharsets.UTF_8); - DatagramPacket packet = new DatagramPacket(packetContents, packetContents.length, broadcastAddress, - REMOTE_UDP_PORT); - - // Send before listening in case the port isn't bound until here. - socket.send(packet); - - // receivePacketAndDiscover will return false if no packet is received after 1 second - while (receivePacketAndDiscover(socket)) { - } - } catch (Exception e) { - // Nothing to do here - the host couldn't be found, likely because it doesn't exist + private void scanner() { + long timestampOfLastScan = getTimestampOfLastScan(); + for (InetAddress broadcastAddress : getBroadcastAddresses()) { + logger.trace("Starting broadcast for {}", broadcastAddress.toString()); + + try (DatagramSocket socket = new DatagramSocket()) { + socket.setBroadcast(true); + socket.setReuseAddress(true); + byte[] packetContents = UDP_PACKET_CONTENTS.getBytes(StandardCharsets.UTF_8); + DatagramPacket packet = new DatagramPacket(packetContents, packetContents.length, broadcastAddress, + REMOTE_UDP_PORT); + + // Send before listening in case the port isn't bound until here. + socket.send(packet); + + // receivePacketAndDiscover will return false if no packet is received after 1 second + while (receivePacketAndDiscover(socket)) { } + } catch (Exception e) { + // Nothing to do here - the host couldn't be found, likely because it doesn't exist } + } - removeOlderResults(timestampOfLastScan); - }; + removeOlderResults(timestampOfLastScan); } private boolean receivePacketAndDiscover(DatagramSocket socket) { From 9bb43461fb646b4c9b90e0f42193f2c2c8098851 Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Sat, 19 Feb 2022 21:52:04 +0100 Subject: [PATCH 06/19] [voskstt] initial contribution (#12249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miguel Álvarez Díez --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.voice.voskstt/NOTICE | 20 ++ bundles/org.openhab.voice.voskstt/README.md | 60 ++++ bundles/org.openhab.voice.voskstt/pom.xml | 31 ++ .../src/main/feature/feature.xml | 9 + .../internal/VoskSTTConfiguration.java | 49 +++ .../voskstt/internal/VoskSTTConstants.java | 42 +++ .../voskstt/internal/VoskSTTService.java | 292 ++++++++++++++++++ .../main/resources/OH-INF/config/config.xml | 50 +++ .../resources/OH-INF/i18n/voskstt.properties | 20 ++ bundles/pom.xml | 1 + 12 files changed, 580 insertions(+) create mode 100644 bundles/org.openhab.voice.voskstt/NOTICE create mode 100644 bundles/org.openhab.voice.voskstt/README.md create mode 100644 bundles/org.openhab.voice.voskstt/pom.xml create mode 100644 bundles/org.openhab.voice.voskstt/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConfiguration.java create mode 100644 bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConstants.java create mode 100644 bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTService.java create mode 100644 bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt.properties diff --git a/CODEOWNERS b/CODEOWNERS index 50a78212abfdc..1e9001fe90f0e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -383,6 +383,7 @@ /bundles/org.openhab.voice.pollytts/ @hillmanr /bundles/org.openhab.voice.porcupineks/ @GiviMAD /bundles/org.openhab.voice.voicerss/ @JochenHiller @lolodomo +/bundles/org.openhab.voice.voskstt/ @GiviMAD /bundles/org.openhab.voice.watsonstt/ @GiviMAD /itests/org.openhab.binding.astro.tests/ @gerrieg /itests/org.openhab.binding.avmfritz.tests/ @cweitkamp diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 930b8d50a74cb..415aecf36118d 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1906,6 +1906,11 @@ org.openhab.voice.voicerss ${project.version} + + org.openhab.addons.bundles + org.openhab.voice.voskstt + ${project.version} + org.openhab.addons.bundles org.openhab.voice.watsonstt diff --git a/bundles/org.openhab.voice.voskstt/NOTICE b/bundles/org.openhab.voice.voskstt/NOTICE new file mode 100644 index 0000000000000..4e69b70319b9b --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/NOTICE @@ -0,0 +1,20 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons + +== Third-party Content + +com.alphacephei: vosk +* License: Apache 2.0 License +* Project: https://github.com/alphacep/vosk-api +* Source: https://github.com/alphacep/vosk-api/tree/master/java diff --git a/bundles/org.openhab.voice.voskstt/README.md b/bundles/org.openhab.voice.voskstt/README.md new file mode 100644 index 0000000000000..224d56f732bcd --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/README.md @@ -0,0 +1,60 @@ +# Vosk Speech-to-Text + +Vosk STT Service uses [vosk-api](https://github.com/alphacep/vosk-api) to perform offline speech-to-text in openHAB. + +[Vosk](https://alphacephei.com/vosk/) is an offline open source speech recognition toolkit. +It enables speech recognition for 20+ languages and dialects - English, Indian English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish, Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino, Ukrainian, Kazakh, Swedish, Japanese, Esperanto. +More to come. + +## Configuring the model + +Before you can use this service you should configure your language model. +You can download it from [here](https://alphacephei.com/vosk/models). +You should unzip the contained folder into '\/vosk/' and rename it to model for the add-on to work. + +## Configuration + +### Speech to Text Configuration + +Use your favorite configuration UI to edit **Settings / Other Services - Vosk Speech-to-Text**: + +* **Preload Model** - Keep language model loaded. +* **Single Utterance Mode** - When enabled recognition stops listening after a single utterance. +* **Max Transcription Seconds** - Max seconds to wait to force stop the transcription. +* **Max Silence Seconds** - Only works when singleUtteranceMode is disabled, max seconds without getting new transcriptions to stop listening. + +### Messages Configuration + +Use your favorite configuration UI to edit **Settings / Other Services - Vosk Speech-to-Text**: + +* **No Results Message** - Message to be told when no results. +* **Error Message** - Message to be told when an error has happened. + +### Configuration via a text file + +In case you would like to setup the service via a text file, create a new file in `$OPENHAB_ROOT/conf/services` named `voskstt.cfg` + +Its contents should look similar to: + +``` +org.openhab.voice.voskstt:preloadModel=true +org.openhab.voice.voskstt:singleUtteranceMode=true +org.openhab.voice.voskstt:maxTranscriptionSeconds=60 +org.openhab.voice.voskstt:maxSilenceSeconds=5 +org.openhab.voice.voskstt:noResultsMessage="Sorry, I didn't understand you" +org.openhab.voice.voskstt:errorMessage="Sorry, something went wrong" +``` + +### Default Speech-to-Text Configuration + +You can setup your preferred default Speech-to-Text in the UI: + +* Go to **Settings**. +* Edit **System Services - Voice**. +* Set **Vosk** as **Speech-to-Text**. + +In case you would like to setup these settings via a text file, you can edit the file `runtime.cfg` in `$OPENHAB_ROOT/conf/services` and set the following entries: + +``` +org.openhab.voice:defaultSTT=voskstt +``` diff --git a/bundles/org.openhab.voice.voskstt/pom.xml b/bundles/org.openhab.voice.voskstt/pom.xml new file mode 100644 index 0000000000000..761328d6247e0 --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/pom.xml @@ -0,0 +1,31 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.3.0-SNAPSHOT + + + org.openhab.voice.voskstt + + openHAB Add-ons :: Bundles :: Voice :: Vosk Speech to Text + + + com.alphacephei + vosk + 0.3.33 + compile + + + + net.java.dev.jna + jna + 5.7.0 + compile + + + diff --git a/bundles/org.openhab.voice.voskstt/src/main/feature/feature.xml b/bundles/org.openhab.voice.voskstt/src/main/feature/feature.xml new file mode 100644 index 0000000000000..0f68035cdd402 --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.voice.voskstt/${project.version} + + diff --git a/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConfiguration.java b/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConfiguration.java new file mode 100644 index 0000000000000..1f09cf98ddc9c --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConfiguration.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2022 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.voice.voskstt.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link VoskSTTConfiguration} class contains Vosk STT Service configuration. + * + * @author Miguel Álvarez - Initial contribution + */ +@NonNullByDefault +public class VoskSTTConfiguration { + /** + * Single phrase mode. + */ + public boolean singleUtteranceMode = true; + /** + * Max seconds to wait to force stop the transcription. + */ + public int maxTranscriptionSeconds = 60; + /** + * Only works when singleUtteranceMode is disabled, max seconds without getting new transcriptions to stop + * listening. + */ + public int maxSilenceSeconds = 5; + /** + * Message to be told when no results. + */ + public String noResultsMessage = "Sorry, I didn't understand you"; + /** + * Message to be told when an error has happened. + */ + public String errorMessage = "Sorry, something went wrong"; + /** + * Keep language model loaded + */ + public boolean preloadModel = true; +} diff --git a/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConstants.java b/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConstants.java new file mode 100644 index 0000000000000..b08776a9e62a8 --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTConstants.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 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.voice.voskstt.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link VoskSTTConstants} class defines common constants. + * + * @author Miguel Álvarez - Initial contribution + */ +@NonNullByDefault +public class VoskSTTConstants { + /** + * Service name + */ + public static final String SERVICE_NAME = "Vosk"; + /** + * Service id + */ + public static final String SERVICE_ID = "voskstt"; + + /** + * Service category + */ + public static final String SERVICE_CATEGORY = "voice"; + + /** + * Service pid + */ + public static final String SERVICE_PID = "org.openhab." + SERVICE_CATEGORY + "." + SERVICE_ID; +} diff --git a/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTService.java b/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTService.java new file mode 100644 index 0000000000000..532ffbb22c3ce --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/src/main/java/org/openhab/voice/voskstt/internal/VoskSTTService.java @@ -0,0 +1,292 @@ +/** + * Copyright (c) 2010-2022 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.voice.voskstt.internal; + +import static org.openhab.voice.voskstt.internal.VoskSTTConstants.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.OpenHAB; +import org.openhab.core.audio.AudioFormat; +import org.openhab.core.audio.AudioStream; +import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.config.core.ConfigurableService; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.io.rest.LocaleService; +import org.openhab.core.voice.RecognitionStartEvent; +import org.openhab.core.voice.RecognitionStopEvent; +import org.openhab.core.voice.STTException; +import org.openhab.core.voice.STTListener; +import org.openhab.core.voice.STTService; +import org.openhab.core.voice.STTServiceHandle; +import org.openhab.core.voice.SpeechRecognitionErrorEvent; +import org.openhab.core.voice.SpeechRecognitionEvent; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.vosk.Model; +import org.vosk.Recognizer; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link VoskSTTService} class is a service implementation to use Vosk-API for Speech-to-Text. + * + * @author Miguel Álvarez - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = SERVICE_PID, property = Constants.SERVICE_PID + "=" + SERVICE_PID) +@ConfigurableService(category = SERVICE_CATEGORY, label = SERVICE_NAME + + " Speech-to-Text", description_uri = SERVICE_CATEGORY + ":" + SERVICE_ID) +public class VoskSTTService implements STTService { + private static final String VOSK_FOLDER = Path.of(OpenHAB.getUserDataFolder(), "vosk").toString(); + private static final String MODEL_PATH = Path.of(VOSK_FOLDER, "model").toString(); + static { + Logger logger = LoggerFactory.getLogger(VoskSTTService.class); + File directory = new File(VOSK_FOLDER); + if (!directory.exists()) { + if (directory.mkdir()) { + logger.info("vosk dir created {}", VOSK_FOLDER); + } + } + } + private final Logger logger = LoggerFactory.getLogger(VoskSTTService.class); + private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("OH-voice-voskstt"); + private final LocaleService localeService; + private VoskSTTConfiguration config = new VoskSTTConfiguration(); + private @Nullable Model model; + + @Activate + public VoskSTTService(@Reference LocaleService localeService) { + this.localeService = localeService; + } + + @Activate + protected void activate(Map config) { + configChange(config); + } + + @Modified + protected void modified(Map config) { + configChange(config); + } + + @Deactivate + protected void deactivate(Map config) { + try { + unloadModel(); + } catch (IOException e) { + logger.warn("IOException unloading model: {}", e.getMessage()); + } + } + + private void configChange(Map config) { + this.config = new Configuration(config).as(VoskSTTConfiguration.class); + if (this.config.preloadModel) { + try { + loadModel(); + } catch (IOException e) { + logger.warn("IOException loading model: {}", e.getMessage()); + } + } else { + try { + unloadModel(); + } catch (IOException e) { + logger.warn("IOException unloading model: {}", e.getMessage()); + } + } + } + + @Override + public String getId() { + return SERVICE_ID; + } + + @Override + public String getLabel(@Nullable Locale locale) { + return SERVICE_NAME; + } + + @Override + public Set getSupportedLocales() { + // as it is not possible to determine the language of the model that was downloaded and setup by the user, it is + // assumed the language of the model is matching the locale of the openHAB server + return Set.of(localeService.getLocale(null)); + } + + @Override + public Set getSupportedFormats() { + return Set.of( + new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, null, null, 16000L)); + } + + @Override + public STTServiceHandle recognize(STTListener sttListener, AudioStream audioStream, Locale locale, Set set) + throws STTException { + AtomicBoolean aborted = new AtomicBoolean(false); + try { + var frequency = audioStream.getFormat().getFrequency(); + if (frequency == null) { + throw new IOException("missing audio stream frequency"); + } + backgroundRecognize(sttListener, audioStream, frequency, aborted); + } catch (IOException e) { + throw new STTException(e); + } + return () -> { + aborted.set(true); + }; + } + + private Model getModel() throws IOException { + var model = this.model; + if (model != null) { + return model; + } + return loadModel(); + } + + private Model loadModel() throws IOException { + unloadModel(); + var modelFile = new File(MODEL_PATH); + if (!modelFile.exists() || !modelFile.isDirectory()) { + throw new IOException("missing model dir: " + MODEL_PATH); + } + logger.debug("loading model"); + var model = new Model(MODEL_PATH); + if (config.preloadModel) { + this.model = model; + } + return model; + } + + private void unloadModel() throws IOException { + var model = this.model; + if (model != null) { + logger.debug("unloading model"); + model.close(); + this.model = null; + } + } + + private Future backgroundRecognize(STTListener sttListener, InputStream audioStream, long frequency, + AtomicBoolean aborted) { + StringBuilder transcriptBuilder = new StringBuilder(); + long maxTranscriptionMillis = (config.maxTranscriptionSeconds * 1000L); + long maxSilenceMillis = (config.maxSilenceSeconds * 1000L); + long startTime = System.currentTimeMillis(); + return executor.submit(() -> { + Recognizer recognizer = null; + Model model = null; + try { + model = getModel(); + recognizer = new Recognizer(model, frequency); + long lastInputTime = System.currentTimeMillis(); + int nbytes; + byte[] b = new byte[4096]; + sttListener.sttEventReceived(new RecognitionStartEvent()); + while (!aborted.get()) { + nbytes = audioStream.read(b); + if (aborted.get()) { + break; + } + if (isExpiredInterval(maxTranscriptionMillis, startTime)) { + logger.debug("Stops listening, max transcription time reached"); + break; + } + if (!config.singleUtteranceMode && isExpiredInterval(maxSilenceMillis, lastInputTime)) { + logger.debug("Stops listening, max silence time reached"); + break; + } + if (nbytes == 0) { + trySleep(100); + continue; + } + if (recognizer.acceptWaveForm(b, nbytes)) { + lastInputTime = System.currentTimeMillis(); + var result = recognizer.getResult(); + logger.debug("Result: {}", result); + ObjectMapper mapper = new ObjectMapper(); + var json = mapper.readTree(result); + transcriptBuilder.append(json.get("text").asText()).append(" "); + if (config.singleUtteranceMode) { + break; + } + } else { + logger.debug("Partial: {}", recognizer.getPartialResult()); + } + } + if (!aborted.get()) { + sttListener.sttEventReceived(new RecognitionStopEvent()); + var transcript = transcriptBuilder.toString().trim(); + logger.debug("Final: {}", transcript); + if (!transcript.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionEvent(transcript, 1F)); + } else { + if (!config.noResultsMessage.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.noResultsMessage)); + } else { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent("No results")); + } + } + } + } catch (IOException e) { + logger.warn("Error running speech to text: {}", e.getMessage()); + if (config.errorMessage.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent("Error")); + } else { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.errorMessage)); + } + } finally { + if (recognizer != null) { + recognizer.close(); + } + if (!config.preloadModel && model != null) { + model.close(); + } + } + try { + audioStream.close(); + } catch (IOException e) { + logger.warn("IOException on close: {}", e.getMessage()); + } + }); + } + + private void trySleep(long ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException ignored) { + } + } + + private boolean isExpiredInterval(long interval, long referenceTime) { + return System.currentTimeMillis() - referenceTime > interval; + } +} diff --git a/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 0000000000000..c0266cb910992 --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,50 @@ + + + + + + Configure Speech to Text. + + + + Configure service information messages. + + + + When enabled recognition stops listening after a single utterance. + true + + + + Max seconds to wait to force stop the transcription. + 60 + + + + Only works when singleUtteranceMode is disabled, max seconds without getting new transcriptions to stop + listening. + 5 + + + + Keep language model loaded. If true model is just reload the model on config updates, if not model will + be loaded and offloaded on each execution. It will fallback to try to load the model when executed if it was not + able to load it before. + true + + + + Message to be told when no results. + Sorry, I didn't understand you + + + + Message to be told when an error has happened. (Empty for disabled) + Sorry, something went wrong + + + diff --git a/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt.properties b/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt.properties new file mode 100644 index 0000000000000..952f07837405a --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt.properties @@ -0,0 +1,20 @@ +voice.config.voskstt.errorMessage.label = Error Message +voice.config.voskstt.errorMessage.description = Message to be told when an error has happened. (Empty for disabled) +voice.config.voskstt.group.messages.label = Info Messages +voice.config.voskstt.group.messages.description = Configure service information messages. +voice.config.voskstt.group.stt.label = STT Configuration +voice.config.voskstt.group.stt.description = Configure Speech to Text. +voice.config.voskstt.maxSilenceSeconds.label = Max Silence Seconds +voice.config.voskstt.maxSilenceSeconds.description = Only works when singleUtteranceMode is disabled, max seconds without getting new transcriptions to stop listening. +voice.config.voskstt.maxTranscriptionSeconds.label = Max Transcription Seconds +voice.config.voskstt.maxTranscriptionSeconds.description = Max seconds to wait to force stop the transcription. +voice.config.voskstt.noResultsMessage.label = No Results Message +voice.config.voskstt.noResultsMessage.description = Message to be told when no results. +voice.config.voskstt.preloadModel.label = Preload Model +voice.config.voskstt.preloadModel.description = Keep language model loaded. If true model is just reload the model on config updates, if not model will be loaded and offloaded on each execution. It will fallback to try to load the model when executed if it was not able to load it before. +voice.config.voskstt.singleUtteranceMode.label = Single Utterance Mode +voice.config.voskstt.singleUtteranceMode.description = When enabled recognition stops listening after a single utterance. + +# service + +service.voice.voskstt.label = Vosk Speech-to-Text diff --git a/bundles/pom.xml b/bundles/pom.xml index e1ab1accb9c8d..191b76beade4d 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -401,6 +401,7 @@ org.openhab.voice.pollytts org.openhab.voice.porcupineks org.openhab.voice.voicerss + org.openhab.voice.voskstt org.openhab.voice.watsonstt From a517e6e768df787b6ccb4848c495b81db1159e99 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Sun, 20 Feb 2022 12:35:51 +0100 Subject: [PATCH 07/19] [openweathermap] Call Thread.currentThread().interrupt() on InterruptedException (#12314) * Call Thread.currentThread().interrupt() on InterruptedException Signed-off-by: Christoph Weitkamp --- .../internal/connection/OpenWeatherMapConnection.java | 10 ++++++++-- .../handler/AbstractOpenWeatherMapHandler.java | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/connection/OpenWeatherMapConnection.java b/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/connection/OpenWeatherMapConnection.java index 70e2967f2b2c7..06c547f159f78 100644 --- a/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/connection/OpenWeatherMapConnection.java +++ b/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/connection/OpenWeatherMapConnection.java @@ -428,9 +428,15 @@ private String getResponse(String url) { throw new CommunicationException( errorMessage == null ? "@text/offline.communication-error" : errorMessage, e.getCause()); } - } catch (InterruptedException | TimeoutException e) { + } catch (TimeoutException e) { String errorMessage = e.getMessage(); - logger.debug("InterruptedException or TimeoutException occurred during execution: {}", errorMessage, e); + logger.debug("TimeoutException occurred during execution: {}", errorMessage, e); + throw new CommunicationException(errorMessage == null ? "@text/offline.communication-error" : errorMessage, + e.getCause()); + } catch (InterruptedException e) { + String errorMessage = e.getMessage(); + logger.debug("InterruptedException occurred during execution: {}", errorMessage, e); + Thread.currentThread().interrupt(); throw new CommunicationException(errorMessage == null ? "@text/offline.communication-error" : errorMessage, e.getCause()); } diff --git a/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/handler/AbstractOpenWeatherMapHandler.java b/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/handler/AbstractOpenWeatherMapHandler.java index 308d04b59308d..ac4ed5dfdee0d 100644 --- a/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/handler/AbstractOpenWeatherMapHandler.java +++ b/bundles/org.openhab.binding.openweathermap/src/main/java/org/openhab/binding/openweathermap/internal/handler/AbstractOpenWeatherMapHandler.java @@ -138,9 +138,9 @@ public void updateData(OpenWeatherMapConnection connection) { updateStatus(ThingStatus.ONLINE); } } catch (CommunicationException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getRawMessage()); } catch (ConfigurationException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage()); } } From 4d77608da1d758e1932dc0616e943aaab5820022 Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Sun, 20 Feb 2022 12:45:31 +0100 Subject: [PATCH 08/19] [googlestt] lazy abort (#12317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miguel Álvarez Díez --- .../googlestt/internal/GoogleSTTService.java | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/bundles/org.openhab.voice.googlestt/src/main/java/org/openhab/voice/googlestt/internal/GoogleSTTService.java b/bundles/org.openhab.voice.googlestt/src/main/java/org/openhab/voice/googlestt/internal/GoogleSTTService.java index 5f82829cf1477..a0f0bfa0f81a9 100644 --- a/bundles/org.openhab.voice.googlestt/src/main/java/org/openhab/voice/googlestt/internal/GoogleSTTService.java +++ b/bundles/org.openhab.voice.googlestt/src/main/java/org/openhab/voice/googlestt/internal/GoogleSTTService.java @@ -24,7 +24,6 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -147,17 +146,12 @@ public Set getSupportedFormats() { @Override public STTServiceHandle recognize(STTListener sttListener, AudioStream audioStream, Locale locale, Set set) { - AtomicBoolean keepStreaming = new AtomicBoolean(true); - Future scheduledTask = backgroundRecognize(sttListener, audioStream, keepStreaming, locale, set); + AtomicBoolean aborted = new AtomicBoolean(false); + backgroundRecognize(sttListener, audioStream, aborted, locale, set); return new STTServiceHandle() { @Override public void abort() { - keepStreaming.set(false); - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - scheduledTask.cancel(true); + aborted.set(true); } }; } @@ -206,7 +200,7 @@ private void deleteAuthCode() { } } - private Future backgroundRecognize(STTListener sttListener, AudioStream audioStream, AtomicBoolean keepStreaming, + private Future backgroundRecognize(STTListener sttListener, AudioStream audioStream, AtomicBoolean aborted, Locale locale, Set set) { Credentials credentials = getCredentials(); return executor.submit(() -> { @@ -214,10 +208,9 @@ private Future backgroundRecognize(STTListener sttListener, AudioStream audio ClientStream clientStream = null; try (SpeechClient client = SpeechClient .create(SpeechSettings.newBuilder().setCredentialsProvider(() -> credentials).build())) { - TranscriptionListener responseObserver = new TranscriptionListener(sttListener, config, - (t) -> keepStreaming.set(false)); + TranscriptionListener responseObserver = new TranscriptionListener(sttListener, config, aborted); clientStream = client.streamingRecognizeCallable().splitCall(responseObserver); - streamAudio(clientStream, audioStream, responseObserver, keepStreaming, locale); + streamAudio(clientStream, audioStream, responseObserver, aborted, locale); clientStream.closeSend(); logger.debug("Background recognize done"); } catch (IOException e) { @@ -232,7 +225,7 @@ private Future backgroundRecognize(STTListener sttListener, AudioStream audio } private void streamAudio(ClientStream clientStream, AudioStream audioStream, - TranscriptionListener responseObserver, AtomicBoolean keepStreaming, Locale locale) throws IOException { + TranscriptionListener responseObserver, AtomicBoolean aborted, Locale locale) throws IOException { // Gather stream info and send config AudioFormat streamFormat = audioStream.getFormat(); RecognitionConfig.AudioEncoding streamEncoding; @@ -259,10 +252,14 @@ private void streamAudio(ClientStream clientStream, A long maxTranscriptionMillis = (config.maxTranscriptionSeconds * 1000L); long maxSilenceMillis = (config.maxSilenceSeconds * 1000L); int readBytes = 6400; - while (keepStreaming.get()) { + while (!aborted.get()) { byte[] data = new byte[readBytes]; int dataN = audioStream.read(data); - if (!keepStreaming.get() || isExpiredInterval(maxTranscriptionMillis, startTime)) { + if (aborted.get()) { + logger.debug("Stops listening, aborted"); + break; + } + if (isExpiredInterval(maxTranscriptionMillis, startTime)) { logger.debug("Stops listening, max transcription time reached"); break; } @@ -328,16 +325,15 @@ private static class TranscriptionListener implements ResponseObserver completeListener; + private final AtomicBoolean aborted; private float confidenceSum = 0; private int responseCount = 0; private long lastInputTime = 0; - public TranscriptionListener(STTListener sttListener, GoogleSTTConfiguration config, - Consumer<@Nullable Throwable> completeListener) { + public TranscriptionListener(STTListener sttListener, GoogleSTTConfiguration config, AtomicBoolean aborted) { this.sttListener = sttListener; this.config = config; - this.completeListener = completeListener; + this.aborted = aborted; } @Override @@ -372,7 +368,7 @@ public void onResponse(StreamingRecognizeResponse response) { responseCount++; // when in single utterance mode we can just get one final result so complete if (config.singleUtteranceMode) { - completeListener.accept(null); + onComplete(); } } }); @@ -380,16 +376,18 @@ public void onResponse(StreamingRecognizeResponse response) { @Override public void onComplete() { - sttListener.sttEventReceived(new RecognitionStopEvent()); - float averageConfidence = confidenceSum / responseCount; - String transcript = transcriptBuilder.toString(); - if (!transcript.isBlank()) { - sttListener.sttEventReceived(new SpeechRecognitionEvent(transcript, averageConfidence)); - } else { - if (!config.noResultsMessage.isBlank()) { - sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.noResultsMessage)); + if (!aborted.getAndSet(true)) { + sttListener.sttEventReceived(new RecognitionStopEvent()); + float averageConfidence = confidenceSum / responseCount; + String transcript = transcriptBuilder.toString(); + if (!transcript.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionEvent(transcript, averageConfidence)); } else { - sttListener.sttEventReceived(new SpeechRecognitionErrorEvent("No results")); + if (!config.noResultsMessage.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.noResultsMessage)); + } else { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent("No results")); + } } } } @@ -397,14 +395,15 @@ public void onComplete() { @Override public void onError(@Nullable Throwable t) { logger.warn("Recognition error: ", t); - completeListener.accept(t); - sttListener.sttEventReceived(new RecognitionStopEvent()); - if (!config.errorMessage.isBlank()) { - sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.errorMessage)); - } else { - String errorMessage = t.getMessage(); - sttListener.sttEventReceived( - new SpeechRecognitionErrorEvent(errorMessage != null ? errorMessage : "Unknown error")); + if (!aborted.getAndSet(true)) { + sttListener.sttEventReceived(new RecognitionStopEvent()); + if (!config.errorMessage.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.errorMessage)); + } else { + String errorMessage = t.getMessage(); + sttListener.sttEventReceived( + new SpeechRecognitionErrorEvent(errorMessage != null ? errorMessage : "Unknown error")); + } } } From c03dbfd5202e862859daa3715e67eabf584bcc54 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sun, 20 Feb 2022 14:22:40 +0100 Subject: [PATCH 09/19] Interrupt current thread on InterruptedException (#12323) Signed-off-by: Jacob Laursen --- .../binding/hdpowerview/internal/HDPowerViewWebTargets.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java index 654e2f3c60bcf..0c43befa9e06d 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java @@ -562,6 +562,9 @@ private synchronized String invoke(HttpMethod method, String url, @Nullable Quer try { response = request.send(); } catch (InterruptedException | TimeoutException | ExecutionException e) { + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } if (Instant.now().isBefore(maintenanceScheduledEnd)) { // throw "softer" exception during maintenance window logger.debug("Hub still undergoing maintenance"); From 27538fa87f5e8a4023dc72d5a5fbfd7efde95953 Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Sun, 20 Feb 2022 14:25:47 +0100 Subject: [PATCH 10/19] [watsonstt] lazy abort (#12318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [watsonstt] lazy abort Signed-off-by: Miguel Álvarez Díez --- .../watsonstt/internal/WatsonSTTService.java | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/bundles/org.openhab.voice.watsonstt/src/main/java/org/openhab/voice/watsonstt/internal/WatsonSTTService.java b/bundles/org.openhab.voice.watsonstt/src/main/java/org/openhab/voice/watsonstt/internal/WatsonSTTService.java index a3dc80d0d7eb5..ebd5c0759a069 100644 --- a/bundles/org.openhab.voice.watsonstt/src/main/java/org/openhab/voice/watsonstt/internal/WatsonSTTService.java +++ b/bundles/org.openhab.voice.watsonstt/src/main/java/org/openhab/voice/watsonstt/internal/WatsonSTTService.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -132,12 +133,13 @@ public STTServiceHandle recognize(STTListener sttListener, AudioStream audioStre .speechDetectorSensitivity(config.speechDetectorSensitivity).inactivityTimeout(config.inactivityTimeout) .build(); final AtomicReference<@Nullable WebSocket> socketRef = new AtomicReference<>(); - var task = executor.submit(() -> { + final AtomicBoolean aborted = new AtomicBoolean(false); + executor.submit(() -> { int retries = 2; while (retries > 0) { try { socketRef.set(speechToText.recognizeUsingWebSocket(wsOptions, - new TranscriptionListener(sttListener, config))); + new TranscriptionListener(sttListener, config, aborted))); break; } catch (RuntimeException e) { var cause = e.getCause(); @@ -157,16 +159,17 @@ public STTServiceHandle recognize(STTListener sttListener, AudioStream audioStre return new STTServiceHandle() { @Override public void abort() { - var socket = socketRef.get(); - if (socket != null) { - socket.close(1000, null); - socket.cancel(); - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { + if (!aborted.getAndSet(true)) { + var socket = socketRef.get(); + if (socket != null) { + socket.close(1000, null); + socket.cancel(); + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } } } - task.cancel(true); } }; } @@ -226,13 +229,15 @@ private static class TranscriptionListener implements RecognizeCallback { private final StringBuilder transcriptBuilder = new StringBuilder(); private final STTListener sttListener; private final WatsonSTTConfiguration config; + private final AtomicBoolean aborted; private float confidenceSum = 0f; private int responseCount = 0; private boolean disconnected = false; - public TranscriptionListener(STTListener sttListener, WatsonSTTConfiguration config) { + public TranscriptionListener(STTListener sttListener, WatsonSTTConfiguration config, AtomicBoolean aborted) { this.sttListener = sttListener; this.config = config; + this.aborted = aborted; } @Override @@ -267,24 +272,28 @@ public void onError(@Nullable Exception e) { return; } logger.warn("TranscriptionError: {}", errorMessage); - sttListener.sttEventReceived( - new SpeechRecognitionErrorEvent(errorMessage != null ? errorMessage : "Unknown error")); + if (!aborted.get()) { + sttListener.sttEventReceived( + new SpeechRecognitionErrorEvent(errorMessage != null ? errorMessage : "Unknown error")); + } } @Override public void onDisconnected() { logger.debug("onDisconnected"); disconnected = true; - sttListener.sttEventReceived(new RecognitionStopEvent()); - float averageConfidence = confidenceSum / (float) responseCount; - String transcript = transcriptBuilder.toString(); - if (!transcript.isBlank()) { - sttListener.sttEventReceived(new SpeechRecognitionEvent(transcript, averageConfidence)); - } else { - if (!config.noResultsMessage.isBlank()) { - sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.noResultsMessage)); + if (!aborted.getAndSet(true)) { + sttListener.sttEventReceived(new RecognitionStopEvent()); + float averageConfidence = confidenceSum / (float) responseCount; + String transcript = transcriptBuilder.toString(); + if (!transcript.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionEvent(transcript, averageConfidence)); } else { - sttListener.sttEventReceived(new SpeechRecognitionErrorEvent("No results")); + if (!config.noResultsMessage.isBlank()) { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent(config.noResultsMessage)); + } else { + sttListener.sttEventReceived(new SpeechRecognitionErrorEvent("No results")); + } } } } From 85596511669f00fd8d3b91389a4305aa34bb6481 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sun, 20 Feb 2022 20:12:31 +0100 Subject: [PATCH 11/19] [mqtt] Add default translations (#12328) This will allow for translating the MQTT Binding in Crowdin. Signed-off-by: Wouter Born --- .../resources/OH-INF/i18n/mqtt.properties | 55 +++++ .../resources/OH-INF/i18n/mqtt.properties | 210 ++++++++++++++++++ .../OH-INF/i18n/mqttbroker_de.properties | 2 - .../resources/OH-INF/i18n/mqtt.properties | 14 ++ .../resources/OH-INF/i18n/mqtt.properties | 16 ++ .../resources/OH-INF/i18n/mqtt.properties | 5 + 6 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties delete mode 100644 bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqttbroker_de.properties diff --git a/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt.properties b/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt.properties index 9736943568d0d..555b7126cb2ef 100644 --- a/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt.properties +++ b/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt.properties @@ -1,3 +1,20 @@ +# thing types + +thing-type.mqtt.cct.label = Milight CCT +thing-type.mqtt.cct.description = Led globe with both cool and warm white controls +thing-type.mqtt.fut089.label = Milight FUT089 +thing-type.mqtt.fut089.description = Use this when your remote is the newer 8 group type called FUT089 and your globes are rgb_cct +thing-type.mqtt.fut091.label = Milight FUT091 +thing-type.mqtt.fut091.description = Use this when your remote is the newer fut091 and your globes are cct +thing-type.mqtt.rgb.label = Milight RGB +thing-type.mqtt.rgb.description = RGB Globe with no white +thing-type.mqtt.rgb_cct.label = Milight RGBCCT +thing-type.mqtt.rgb_cct.description = Led globe with full Colour, and both cool and warm whites. +thing-type.mqtt.rgbw.label = Milight RGBW +thing-type.mqtt.rgbw.description = RGB Globe with a fixed white + +# thing types config + thing-type.config.mqtt.cct.dimmedCT.label = Dimmed Colour Temp thing-type.config.mqtt.cct.dimmedCT.description = Traditional globes grow warmer the more they are dimmed. Set this to 370, or leave blank to disable. thing-type.config.mqtt.cct.oneTriggersNightMode.label = 1% Triggers Night Mode @@ -30,3 +47,41 @@ thing-type.config.mqtt.rgbw.whiteSat.label = White Saturation thing-type.config.mqtt.rgbw.whiteSat.description = When both the whiteHue and whiteSat values are seen by the binding it will trigger the white LEDS. thing-type.config.mqtt.rgbw.whiteThreshold.label = White Threshold thing-type.config.mqtt.rgbw.whiteThreshold.description = Saturation values at or below this value on a RGBW color control will trigger the white mode. -1 will disable this feature. + +# channel types + +channel-type.mqtt.bulbMode.label = Bulb Mode +channel-type.mqtt.bulbMode.description = Displays the mode the bulb is currently in. +channel-type.mqtt.bulbMode.state.option.white = white +channel-type.mqtt.bulbMode.state.option.color = color +channel-type.mqtt.bulbMode.state.option.scene = scene +channel-type.mqtt.bulbMode.state.option.night = night +channel-type.mqtt.colour.label = Colour +channel-type.mqtt.colour.description = Allows you to change the colour, brightness and saturation of the globe. +channel-type.mqtt.colourTemperature.label = Colour Temperature +channel-type.mqtt.colourTemperature.description = Change from cool to warm white with this control. +channel-type.mqtt.command.label = Command +channel-type.mqtt.command.description = Send a raw command to the globe/s. +channel-type.mqtt.command.state.option.next_mode = Next Mode +channel-type.mqtt.command.state.option.previous_mode = Previous Mode +channel-type.mqtt.command.state.option.mode_speed_up = Mode Speed Up +channel-type.mqtt.command.state.option.mode_speed_down = Mode Speed Down +channel-type.mqtt.command.state.option.set_white = Set White +channel-type.mqtt.command.state.option.level_down = Level Down +channel-type.mqtt.command.state.option.level_up = Level Up +channel-type.mqtt.command.state.option.temperature_down = Temperature Down +channel-type.mqtt.command.state.option.temperature_up = Temperature Up +channel-type.mqtt.command.state.option.night_mode = Night Mode +channel-type.mqtt.discoMode.label = Disco Mode +channel-type.mqtt.discoMode.description = Switch to a Disco mode directly. +channel-type.mqtt.discoMode.state.option.0 = Disco 0 +channel-type.mqtt.discoMode.state.option.1 = Disco 1 +channel-type.mqtt.discoMode.state.option.2 = Disco 2 +channel-type.mqtt.discoMode.state.option.3 = Disco 3 +channel-type.mqtt.discoMode.state.option.4 = Disco 4 +channel-type.mqtt.discoMode.state.option.5 = Disco 5 +channel-type.mqtt.discoMode.state.option.6 = Disco 6 +channel-type.mqtt.discoMode.state.option.7 = Disco 7 +channel-type.mqtt.discoMode.state.option.8 = Disco 8 +channel-type.mqtt.level.label = Level +channel-type.mqtt.level.description = Level changes the brightness of the globe. diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties new file mode 100644 index 0000000000000..e3e20d452cfdd --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqtt.properties @@ -0,0 +1,210 @@ +# thing types + +thing-type.mqtt.topic.label = Generic MQTT Thing +thing-type.mqtt.topic.description = You need a configured Broker first. Dynamically add channels of various types to this Thing. Link different MQTT topics to each channel. + +# thing types config + +thing-type.config.mqtt.topic.availabilityTopic.label = Availability Topic +thing-type.config.mqtt.topic.availabilityTopic.description = Topic of the LWT of the device +thing-type.config.mqtt.topic.payloadAvailable.label = Device Available Payload +thing-type.config.mqtt.topic.payloadAvailable.description = Payload of the 'Availability Topic', when the device is available. Default: 'ON' +thing-type.config.mqtt.topic.payloadNotAvailable.label = Device Unavailable Payload +thing-type.config.mqtt.topic.payloadNotAvailable.description = Payload of the 'Availability Topic', when the device is *not* available. Default: 'OFF' +thing-type.config.mqtt.topic.transformationPattern.label = Availability Payload Transformations +thing-type.config.mqtt.topic.transformationPattern.description = Applies transformations to the incoming availability payload. A transformation example for a received JSON would be "JSONPATH:$.status" for a json {status: "Online"}. You can chain transformations by separating them with the intersection character ∩. + +# channel types + +channel-type.mqtt.color.label = Color Value (HSB, RGB or CIE xyY) +channel-type.mqtt.colorHSB.label = Color Value (Hue,Saturation,Brightness) +channel-type.mqtt.colorRGB.label = Color Value (Red,Green,Blue) +channel-type.mqtt.contact.label = Open/Close Contact +channel-type.mqtt.datetime.label = Date/Time Value +channel-type.mqtt.datetime.description = Current date and/or time +channel-type.mqtt.dimmer.label = Dimmer +channel-type.mqtt.image.label = Image +channel-type.mqtt.image.description = An image to display. Send a binary bmp, jpg, png or any other supported format to this channel. +channel-type.mqtt.location.label = Location +channel-type.mqtt.location.description = GPS coordinates as Latitude,Longitude,Altitude +channel-type.mqtt.number.label = Number Value +channel-type.mqtt.rollershutter.label = Rollershutter +channel-type.mqtt.string.label = Text Value +channel-type.mqtt.switch.label = On/Off Switch +channel-type.mqtt.trigger.label = Trigger + +# channel types config + +thing-type.config.mqtt.color_channel.colorMode.label = Color Mode +thing-type.config.mqtt.color_channel.colorMode.description = Defines the color representation format of the payload. "HSB" by default. +thing-type.config.mqtt.color_channel.colorMode.option.HSB = HSB (Hue, Saturation, Brightness) +thing-type.config.mqtt.color_channel.colorMode.option.RGB = RGB (Red, Green, Blue) +thing-type.config.mqtt.color_channel.colorMode.option.XYY = CIE xyY (x, y, Brightness) +thing-type.config.mqtt.color_channel.commandTopic.label = MQTT Command Topic +thing-type.config.mqtt.color_channel.commandTopic.description = An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. +thing-type.config.mqtt.color_channel.formatBeforePublish.label = Outgoing Value Format +thing-type.config.mqtt.color_channel.formatBeforePublish.description = Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". +thing-type.config.mqtt.color_channel.group.transformations.label = Transform Values +thing-type.config.mqtt.color_channel.group.transformations.description = These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item. +thing-type.config.mqtt.color_channel.off.label = Off/Closed Value +thing-type.config.mqtt.color_channel.off.description = A number (like 0, -10) or a string (like "disabled") that is recognised as off/closed state. You can use this parameter for a second keyword, next to OFF (CLOSED respectively on a Contact). +thing-type.config.mqtt.color_channel.on.label = On/Open Value +thing-type.config.mqtt.color_channel.on.description = A number (like 1, 10) or a string (like "enabled") that is recognised as on/open state. You can use this parameter for a second keyword, next to ON (OPEN respectively on a Contact). +thing-type.config.mqtt.color_channel.onBrightness.label = Initial Brightness +thing-type.config.mqtt.color_channel.onBrightness.description = If you connect this channel to a Switch item and turn it on, color and saturation are preserved from the last state, but the brightness will be set to this configured initial brightness percentage. +thing-type.config.mqtt.color_channel.postCommand.label = Is Command +thing-type.config.mqtt.color_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option. +thing-type.config.mqtt.color_channel.qos.label = QoS +thing-type.config.mqtt.color_channel.qos.description = MQTT QoS of this channel (0, 1, 2). Default is QoS of the broker connection. +thing-type.config.mqtt.color_channel.qos.option.0 = At most once (best effort delivery "fire and forget") +thing-type.config.mqtt.color_channel.qos.option.1 = At least once (guaranteed that a message will be delivered at least once) +thing-type.config.mqtt.color_channel.qos.option.2 = Exactly once (guarantees that each message is received only once by the counterpart) +thing-type.config.mqtt.color_channel.retained.label = Retained +thing-type.config.mqtt.color_channel.retained.description = The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. +thing-type.config.mqtt.color_channel.stateTopic.label = MQTT State Topic +thing-type.config.mqtt.color_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. +thing-type.config.mqtt.color_channel.transformationPattern.label = Incoming Value Transformations +thing-type.config.mqtt.color_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. You can chain transformations by separating them with the intersection character ∩. +thing-type.config.mqtt.color_channel.transformationPatternOut.label = Outgoing Value Transformation +thing-type.config.mqtt.color_channel.transformationPatternOut.description = Applies a transformation before publishing a MQTT topic value. Transformations are specialised in extracting a value, but some transformations like the MAP one could be useful. +thing-type.config.mqtt.dimmer_channel.commandTopic.label = MQTT Command Topic +thing-type.config.mqtt.dimmer_channel.commandTopic.description = An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. +thing-type.config.mqtt.dimmer_channel.formatBeforePublish.label = Outgoing Value Format +thing-type.config.mqtt.dimmer_channel.formatBeforePublish.description = Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". +thing-type.config.mqtt.dimmer_channel.group.transformations.label = Transform Values +thing-type.config.mqtt.dimmer_channel.group.transformations.description = These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item. +thing-type.config.mqtt.dimmer_channel.max.label = Absolute Maximum +thing-type.config.mqtt.dimmer_channel.max.description = This configuration represents the maximum of the allowed range. For a percentage channel that equals one-hundred percent. +thing-type.config.mqtt.dimmer_channel.min.label = Absolute Minimum +thing-type.config.mqtt.dimmer_channel.min.description = This configuration represents the minimum of the allowed range. For a percentage channel that equals zero percent. +thing-type.config.mqtt.dimmer_channel.off.label = Custom Off/Closed Value +thing-type.config.mqtt.dimmer_channel.off.description = A number (like 0, -10) or a string (like "disabled") that is additionally recognised as off/closed state. You can use this parameter for a second keyword, next to OFF (CLOSED respectively on a Contact). +thing-type.config.mqtt.dimmer_channel.on.label = Custom On/Open Value +thing-type.config.mqtt.dimmer_channel.on.description = A number (like 1, 10) or a string (like "enabled") that is additionally recognised as on/open state. You can use this parameter for a second keyword, next to ON (OPEN respectively on a Contact). +thing-type.config.mqtt.dimmer_channel.postCommand.label = Is Command +thing-type.config.mqtt.dimmer_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option. +thing-type.config.mqtt.dimmer_channel.qos.label = QoS +thing-type.config.mqtt.dimmer_channel.qos.description = MQTT QoS of this channel (0, 1, 2). Default is QoS of the broker connection. +thing-type.config.mqtt.dimmer_channel.qos.option.0 = At most once (best effort delivery "fire and forget") +thing-type.config.mqtt.dimmer_channel.qos.option.1 = At least once (guaranteed that a message will be delivered at least once) +thing-type.config.mqtt.dimmer_channel.qos.option.2 = Exactly once (guarantees that each message is received only once by the counterpart) +thing-type.config.mqtt.dimmer_channel.retained.label = Retained +thing-type.config.mqtt.dimmer_channel.retained.description = The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. +thing-type.config.mqtt.dimmer_channel.stateTopic.label = MQTT State Topic +thing-type.config.mqtt.dimmer_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. +thing-type.config.mqtt.dimmer_channel.step.label = Delta Value +thing-type.config.mqtt.dimmer_channel.step.description = A number/dimmer channel can receive INCREASE/DECREASE commands and computes the target number by adding or subtracting this delta value. +thing-type.config.mqtt.dimmer_channel.transformationPattern.label = Incoming Value Transformations +thing-type.config.mqtt.dimmer_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. You can chain transformations by separating them with the intersection character ∩. +thing-type.config.mqtt.dimmer_channel.transformationPatternOut.label = Outgoing Value Transformation +thing-type.config.mqtt.dimmer_channel.transformationPatternOut.description = Applies a transformation before publishing a MQTT topic value. Transformations are specialised in extracting a value, but some transformations like the MAP one could be useful. +thing-type.config.mqtt.number_channel.commandTopic.label = MQTT Command Topic +thing-type.config.mqtt.number_channel.commandTopic.description = An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. +thing-type.config.mqtt.number_channel.formatBeforePublish.label = Outgoing Value Format +thing-type.config.mqtt.number_channel.formatBeforePublish.description = Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". +thing-type.config.mqtt.number_channel.group.transformations.label = Transform Values +thing-type.config.mqtt.number_channel.group.transformations.description = These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item. +thing-type.config.mqtt.number_channel.max.label = Absolute Maximum +thing-type.config.mqtt.number_channel.max.description = This configuration represents the maximum of the allowed range. For a percentage channel that equals one-hundred percent. +thing-type.config.mqtt.number_channel.min.label = Absolute Minimum +thing-type.config.mqtt.number_channel.min.description = This configuration represents the minimum of the allowed range. For a percentage channel that equals zero percent. +thing-type.config.mqtt.number_channel.postCommand.label = Is Command +thing-type.config.mqtt.number_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option. +thing-type.config.mqtt.number_channel.qos.label = QoS +thing-type.config.mqtt.number_channel.qos.description = MQTT QoS of this channel (0, 1, 2). Default is QoS of the broker connection. +thing-type.config.mqtt.number_channel.qos.option.0 = At most once (best effort delivery "fire and forget") +thing-type.config.mqtt.number_channel.qos.option.1 = At least once (guaranteed that a message will be delivered at least once) +thing-type.config.mqtt.number_channel.qos.option.2 = Exactly once (guarantees that each message is received only once by the counterpart) +thing-type.config.mqtt.number_channel.retained.label = Retained +thing-type.config.mqtt.number_channel.retained.description = The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. +thing-type.config.mqtt.number_channel.stateTopic.label = MQTT State Topic +thing-type.config.mqtt.number_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. +thing-type.config.mqtt.number_channel.step.label = Delta Value +thing-type.config.mqtt.number_channel.step.description = A number/dimmer channel can receive INCREASE/DECREASE commands and computes the target number by adding or subtracting this delta value. +thing-type.config.mqtt.number_channel.transformationPattern.label = Incoming Value Transformations +thing-type.config.mqtt.number_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. You can chain transformations by separating them with the intersection character ∩. +thing-type.config.mqtt.number_channel.transformationPatternOut.label = Outgoing Value Transformation +thing-type.config.mqtt.number_channel.transformationPatternOut.description = Applies a transformation before publishing a MQTT topic value. Transformations are specialised in extracting a value, but some transformations like the MAP one could be useful. +thing-type.config.mqtt.number_channel.unit.label = Unit Of Measurement +thing-type.config.mqtt.number_channel.unit.description = Unit of measurement (optional). The unit is used for representing the value in the GUI as well as for converting incoming values (like from '°F' to '°C'). Examples: "°C", "°F" +thing-type.config.mqtt.rollershutter_channel.commandTopic.label = MQTT Command Topic +thing-type.config.mqtt.rollershutter_channel.commandTopic.description = An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. +thing-type.config.mqtt.rollershutter_channel.formatBeforePublish.label = Outgoing Value Format +thing-type.config.mqtt.rollershutter_channel.formatBeforePublish.description = Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". +thing-type.config.mqtt.rollershutter_channel.group.transformations.label = Transform Values +thing-type.config.mqtt.rollershutter_channel.group.transformations.description = These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item. +thing-type.config.mqtt.rollershutter_channel.off.label = Down Value +thing-type.config.mqtt.rollershutter_channel.off.description = A string (like "CLOSE") that is recognised as DOWN state. You can use this parameter for a second keyword, next to DOWN. +thing-type.config.mqtt.rollershutter_channel.on.label = Up Value +thing-type.config.mqtt.rollershutter_channel.on.description = A string (like "OPEN") that is recognised as UP state. You can use this parameter for a second keyword, next to UP. +thing-type.config.mqtt.rollershutter_channel.postCommand.label = Is Command +thing-type.config.mqtt.rollershutter_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option. +thing-type.config.mqtt.rollershutter_channel.qos.label = QoS +thing-type.config.mqtt.rollershutter_channel.qos.description = MQTT QoS of this channel (0, 1, 2). Default is QoS of the broker connection. +thing-type.config.mqtt.rollershutter_channel.qos.option.0 = At most once (best effort delivery "fire and forget") +thing-type.config.mqtt.rollershutter_channel.qos.option.1 = At least once (guaranteed that a message will be delivered at least once) +thing-type.config.mqtt.rollershutter_channel.qos.option.2 = Exactly once (guarantees that each message is received only once by the counterpart) +thing-type.config.mqtt.rollershutter_channel.retained.label = Retained +thing-type.config.mqtt.rollershutter_channel.retained.description = The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. +thing-type.config.mqtt.rollershutter_channel.stateTopic.label = MQTT State Topic +thing-type.config.mqtt.rollershutter_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. +thing-type.config.mqtt.rollershutter_channel.stop.label = Stop Value +thing-type.config.mqtt.rollershutter_channel.stop.description = A string (like "STOP") that is recognised as stop state. Will set the rollershutter state to undefined, because the current position is unknown at that point. +thing-type.config.mqtt.rollershutter_channel.transformationPattern.label = Incoming Value Transformations +thing-type.config.mqtt.rollershutter_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. You can chain transformations by separating them with the intersection character ∩. +thing-type.config.mqtt.rollershutter_channel.transformationPatternOut.label = Outgoing Value Transformation +thing-type.config.mqtt.rollershutter_channel.transformationPatternOut.description = Applies a transformation before publishing a MQTT topic value. Transformations are specialised in extracting a value, but some transformations like the MAP one could be useful. +thing-type.config.mqtt.string_channel.allowedStates.label = Allowed States +thing-type.config.mqtt.string_channel.allowedStates.description = If your MQTT topic is limited to a set of one or more specific commands or specific states, define those states here. Separate multiple states with commas. An example for a light bulb state set: ON,DIMMED,OFF +thing-type.config.mqtt.string_channel.commandTopic.label = MQTT Command Topic +thing-type.config.mqtt.string_channel.commandTopic.description = An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. +thing-type.config.mqtt.string_channel.formatBeforePublish.label = Outgoing Value Format +thing-type.config.mqtt.string_channel.formatBeforePublish.description = Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". +thing-type.config.mqtt.string_channel.group.transformations.label = Transform Values +thing-type.config.mqtt.string_channel.group.transformations.description = These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item. +thing-type.config.mqtt.string_channel.postCommand.label = Is Command +thing-type.config.mqtt.string_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option. +thing-type.config.mqtt.string_channel.qos.label = QoS +thing-type.config.mqtt.string_channel.qos.description = MQTT QoS of this channel (0, 1, 2). Default is QoS of the broker connection. +thing-type.config.mqtt.string_channel.qos.option.0 = At most once (best effort delivery "fire and forget") +thing-type.config.mqtt.string_channel.qos.option.1 = At least once (guaranteed that a message will be delivered at least once) +thing-type.config.mqtt.string_channel.qos.option.2 = Exactly once (guarantees that each message is received only once by the counterpart) +thing-type.config.mqtt.string_channel.retained.label = Retained +thing-type.config.mqtt.string_channel.retained.description = The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. +thing-type.config.mqtt.string_channel.stateTopic.label = MQTT State Topic +thing-type.config.mqtt.string_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. +thing-type.config.mqtt.string_channel.transformationPattern.label = Incoming Value Transformations +thing-type.config.mqtt.string_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. You can chain transformations by separating them with the intersection character ∩. +thing-type.config.mqtt.string_channel.transformationPatternOut.label = Outgoing Value Transformation +thing-type.config.mqtt.string_channel.transformationPatternOut.description = Applies a transformation before publishing a MQTT topic value. Transformations are specialised in extracting a value, but some transformations like the MAP one could be useful. +thing-type.config.mqtt.switch_channel.commandTopic.label = MQTT Command Topic +thing-type.config.mqtt.switch_channel.commandTopic.description = An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. +thing-type.config.mqtt.switch_channel.formatBeforePublish.label = Outgoing Value Format +thing-type.config.mqtt.switch_channel.formatBeforePublish.description = Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". +thing-type.config.mqtt.switch_channel.group.transformations.label = Transform Values +thing-type.config.mqtt.switch_channel.group.transformations.description = These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item. +thing-type.config.mqtt.switch_channel.off.label = Custom Off/Closed Value +thing-type.config.mqtt.switch_channel.off.description = A number (like 0, -10) or a string (like "disabled") that is additionally recognised as off/closed state. You can use this parameter for a second keyword, next to OFF (CLOSED respectively on a Contact). +thing-type.config.mqtt.switch_channel.on.label = Custom On/Open Value +thing-type.config.mqtt.switch_channel.on.description = A number (like 1, 10) or a string (like "enabled") that is additionally recognised as on/open state. You can use this parameter for a second keyword, next to ON (OPEN respectively on a Contact). +thing-type.config.mqtt.switch_channel.postCommand.label = Is Command +thing-type.config.mqtt.switch_channel.postCommand.description = If the received MQTT value should not only update the state of linked items, but command them, enable this option. +thing-type.config.mqtt.switch_channel.qos.label = QoS +thing-type.config.mqtt.switch_channel.qos.description = MQTT QoS of this channel (0, 1, 2). Default is QoS of the broker connection. +thing-type.config.mqtt.switch_channel.qos.option.0 = At most once (best effort delivery "fire and forget") +thing-type.config.mqtt.switch_channel.qos.option.1 = At least once (guaranteed that a message will be delivered at least once) +thing-type.config.mqtt.switch_channel.qos.option.2 = Exactly once (guarantees that each message is received only once by the counterpart) +thing-type.config.mqtt.switch_channel.retained.label = Retained +thing-type.config.mqtt.switch_channel.retained.description = The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. +thing-type.config.mqtt.switch_channel.stateTopic.label = MQTT State Topic +thing-type.config.mqtt.switch_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. +thing-type.config.mqtt.switch_channel.transformationPattern.label = Incoming Value Transformations +thing-type.config.mqtt.switch_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. You can chain transformations by separating them with the intersection character ∩. +thing-type.config.mqtt.switch_channel.transformationPatternOut.label = Outgoing Value Transformation +thing-type.config.mqtt.switch_channel.transformationPatternOut.description = Applies a transformation before publishing a MQTT topic value. Transformations are specialised in extracting a value, but some transformations like the MAP one could be useful. +thing-type.config.mqtt.trigger_channel.group.transformations.label = Transform Values +thing-type.config.mqtt.trigger_channel.group.transformations.description = These configuration parameters allow you to alter before a received value is used in the trigger. +thing-type.config.mqtt.trigger_channel.stateTopic.label = MQTT Trigger Topic +thing-type.config.mqtt.trigger_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the trigger +thing-type.config.mqtt.trigger_channel.transformationPattern.label = Incoming Value Transformations +thing-type.config.mqtt.trigger_channel.transformationPattern.description = Applies transformations to an incoming MQTT topic value. This can be used to map the events sent by the device to common values for all devices using, e.g. the MAP transformation. You can chain transformations by separating them with the intersection character ∩. diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqttbroker_de.properties b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqttbroker_de.properties deleted file mode 100644 index 70d7c9dca7987..0000000000000 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/i18n/mqttbroker_de.properties +++ /dev/null @@ -1,2 +0,0 @@ -binding.mqttgeneric.name = Allgemeines MQTT Binding -binding.mqttgeneric.description = Verknüpfung von MQTT Topics mit Things diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties index 7ab2c165eccfb..2095eecad3be8 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/i18n/mqtt.properties @@ -1,3 +1,17 @@ +# thing types + +thing-type.mqtt.homeassistant.label = HomeAssistant MQTT Component +thing-type.mqtt.homeassistant.description = You need a configured Broker first. This Thing represents a device, that follows the "HomeAssistant MQTT Component" specification. + +# thing types config + +thing-type.config.mqtt.homeassistant.basetopic.label = MQTT Base Prefix +thing-type.config.mqtt.homeassistant.basetopic.description = MQTT base prefix +thing-type.config.mqtt.homeassistant.topics.label = MQTT Config Topic +thing-type.config.mqtt.homeassistant.topics.description = List of HomeAssistant configuration topics (e.g. /homeassistant/switch/4711/config) + +# channel types config + channel-type.config.mqtt.ha-channel.component.label = Component channel-type.config.mqtt.ha-channel.component.description = HomeAssistant component type (e.g. binary_sensor, switch, light) channel-type.config.mqtt.ha-channel.config.label = Json Configuration diff --git a/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/i18n/mqtt.properties b/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/i18n/mqtt.properties index 295e5a9f4662a..2d554c6efafc0 100644 --- a/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/i18n/mqtt.properties +++ b/bundles/org.openhab.binding.mqtt.homie/src/main/resources/OH-INF/i18n/mqtt.properties @@ -1,3 +1,19 @@ +# thing types + +thing-type.mqtt.homie300.label = Homie MQTT Device +thing-type.mqtt.homie300.description = You need a configured Broker first. This thing represents a device, that follows the "MQTT Homie Convention" (Version 3.x). + +# thing types config + +thing-type.config.mqtt.homie300.basetopic.label = MQTT Base Prefix +thing-type.config.mqtt.homie300.basetopic.description = MQTT base prefix +thing-type.config.mqtt.homie300.deviceid.label = Device ID +thing-type.config.mqtt.homie300.deviceid.description = Homie Device ID. This is part of the MQTT topic, e.g. "homie/deviceid/$homie". +thing-type.config.mqtt.homie300.removetopics.label = Remove Retained Topics +thing-type.config.mqtt.homie300.removetopics.description = Remove retained topics when thing is deleted + +# channel types config + channel-type.config.mqtt.homie-channel.datatype.label = Data Type channel-type.config.mqtt.homie-channel.datatype.description = The data type of this channel. channel-type.config.mqtt.homie-channel.datatype.option.integer_ = Integer diff --git a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt.properties b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt.properties index 3c131e9f1b6ae..029ae60fa8f31 100644 --- a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt.properties +++ b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt.properties @@ -78,6 +78,8 @@ channel-type.config.mqtt.publishTrigger.separator.description = The trigger chan channel-type.config.mqtt.publishTrigger.stateTopic.label = MQTT Topic channel-type.config.mqtt.publishTrigger.stateTopic.description = This channel will trigger on this MQTT topic. This topic can contain wildcards like + and # for example "all/in/#" or "sensors/+/config". +# thing actions + actionInputTopicLabel = MQTT Topic actionInputTopicDesc = The topic to publish a value to. actionInputValueLabel = Value @@ -86,5 +88,8 @@ actionInputRetainLabel = Retain actionInputRetainDesc = Retain message actionLabel = publish an MQTT message actionDesc = Publishes a value to the given MQTT topic. + +# thing status + offline.notextualconfig = The system connection with the name {0} doesn't exist anymore. offline.sharedremoved = Another binding unexpectedly removed the internal broker connection. From 51bbd34cd7159ef1dd92b0a6aad3f48736b7a601 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sun, 20 Feb 2022 20:26:10 +0100 Subject: [PATCH 12/19] [bluetooth] Add missing default translations (#12329) This will allow for translating the Bluetooth Binding in Crowdin. Signed-off-by: Wouter Born --- .../OH-INF/i18n/bluetooth.properties | 40 +++++++++++++++ .../OH-INF/i18n/bluetooth.properties | 40 +++++++++++++++ .../OH-INF/i18n/bluetooth.properties | 33 ++++++++++++ .../OH-INF/i18n/bluetooth.properties | 15 ++++++ .../OH-INF/i18n/bluetooth.properties | 19 +++++++ .../OH-INF/i18n/bluetooth.properties | 47 +++++++++++++++++ .../OH-INF/i18n/bluetooth.properties | 9 ++++ .../OH-INF/i18n/bluetooth.properties | 18 +++++++ .../OH-INF/i18n/bluetooth.properties | 51 +++++++++++++++++++ .../OH-INF/i18n/bluetooth.properties | 11 ++++ .../OH-INF/i18n/bluetooth.properties | 23 +++++++++ 11 files changed, 306 insertions(+) create mode 100644 bundles/org.openhab.binding.bluetooth.airthings/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.am43/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.blukii/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.enoceanble/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.generic/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.govee/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.roaming/src/main/resources/OH-INF/i18n/bluetooth.properties create mode 100644 bundles/org.openhab.binding.bluetooth.ruuvitag/src/main/resources/OH-INF/i18n/bluetooth.properties diff --git a/bundles/org.openhab.binding.bluetooth.airthings/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.airthings/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..41bc1cd4eb3a8 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.airthings/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,40 @@ +# thing types + +thing-type.bluetooth.airthings_wave_gen1.label = Airthings Wave Gen 1 +thing-type.bluetooth.airthings_wave_gen1.description = Smart Radon Monitor +thing-type.bluetooth.airthings_wave_mini.label = Airthings Wave Mini +thing-type.bluetooth.airthings_wave_mini.description = Indoor air quality monitor +thing-type.bluetooth.airthings_wave_plus.label = Airthings Wave Plus +thing-type.bluetooth.airthings_wave_plus.description = Indoor air quality monitor with radon detection + +# thing types config + +thing-type.config.bluetooth.airthings_wave_gen1.address.label = Address +thing-type.config.bluetooth.airthings_wave_gen1.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.airthings_wave_gen1.refreshInterval.label = Refresh Interval +thing-type.config.bluetooth.airthings_wave_gen1.refreshInterval.description = States how often a refresh shall occur in seconds. This could have impact to battery lifetime +thing-type.config.bluetooth.airthings_wave_mini.address.label = Address +thing-type.config.bluetooth.airthings_wave_mini.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.airthings_wave_mini.refreshInterval.label = Refresh Interval +thing-type.config.bluetooth.airthings_wave_mini.refreshInterval.description = States how often a refresh shall occur in seconds. This could have impact to battery lifetime +thing-type.config.bluetooth.airthings_wave_plus.address.label = Address +thing-type.config.bluetooth.airthings_wave_plus.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.airthings_wave_plus.refreshInterval.label = Refresh Interval +thing-type.config.bluetooth.airthings_wave_plus.refreshInterval.description = States how often a refresh shall occur in seconds. This could have impact to battery lifetime + +# channel types + +channel-type.bluetooth.airthings_co2.label = CO₂ Level +channel-type.bluetooth.airthings_co2.description = Carbon dioxide level +channel-type.bluetooth.airthings_humidity.label = Humidity +channel-type.bluetooth.airthings_humidity.description = Humidity level +channel-type.bluetooth.airthings_pressure.label = Pressure +channel-type.bluetooth.airthings_pressure.description = Pressure +channel-type.bluetooth.airthings_radon_lt_avg.label = Radon Long Term Average Level +channel-type.bluetooth.airthings_radon_lt_avg.description = Radon gas level +channel-type.bluetooth.airthings_radon_st_avg.label = Radon Short Term Average Level +channel-type.bluetooth.airthings_radon_st_avg.description = Radon gas level +channel-type.bluetooth.airthings_temperature.label = Temperature +channel-type.bluetooth.airthings_temperature.description = Temperature +channel-type.bluetooth.airthings_tvoc.label = TVOC Level +channel-type.bluetooth.airthings_tvoc.description = Total volatile organic compounds diff --git a/bundles/org.openhab.binding.bluetooth.am43/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.am43/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..0f98074865a1b --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.am43/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,40 @@ +# thing types + +thing-type.bluetooth.am43.label = AM43 Blind Drive Motor +thing-type.bluetooth.am43.description = A AM43 Blind Drive Motor + +# thing types config + +thing-type.config.bluetooth.am43.address.label = Address +thing-type.config.bluetooth.am43.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.am43.commandTimeout.label = Command Timeout +thing-type.config.bluetooth.am43.commandTimeout.description = The amount of time, in milliseconds, a command should take before it times out. +thing-type.config.bluetooth.am43.invertPosition.label = Invert Position +thing-type.config.bluetooth.am43.invertPosition.description = Inverts the blinds percentages such that 0 becomes 100 and 100 becomes 0 +thing-type.config.bluetooth.am43.refreshInterval.label = Refresh Interval +thing-type.config.bluetooth.am43.refreshInterval.description = Refresh interval for battery and light sensor data (in seconds). This could impact battery lifetime + +# channel types + +channel-type.bluetooth.am43_bottomLimitSet.label = Bottom Limit is Set +channel-type.bluetooth.am43_diameter.label = Diameter +channel-type.bluetooth.am43_diameter.description = The diameter of the pulley of this motor (not really important) +channel-type.bluetooth.am43_direction.label = Direction +channel-type.bluetooth.am43_direction.state.option.Forward = Forward +channel-type.bluetooth.am43_direction.state.option.Reverse = Reverse +channel-type.bluetooth.am43_hasLightSensor.label = Light Sensor Present +channel-type.bluetooth.am43_hasLightSensor.description = Whether or not a light sensor is attached to the motor +channel-type.bluetooth.am43_length.label = Length +channel-type.bluetooth.am43_length.description = The distance the blinds travel from full open and close (not really important) +channel-type.bluetooth.am43_lightLevel.label = Light Level +channel-type.bluetooth.am43_lightLevel.description = Light level detected by the solar sensor. Will range from 0-10 +channel-type.bluetooth.am43_operationMode.label = Operation Mode +channel-type.bluetooth.am43_operationMode.state.option.Inching = Inching +channel-type.bluetooth.am43_operationMode.state.option.Continuous = Continuous +channel-type.bluetooth.am43_position.label = Position +channel-type.bluetooth.am43_position.description = The percent value of the blind position +channel-type.bluetooth.am43_speed.label = Speed +channel-type.bluetooth.am43_speed.description = The speed value in RPMs set for this motor +channel-type.bluetooth.am43_topLimitSet.label = Top Limit is Set +channel-type.bluetooth.am43_type.label = Device Type +channel-type.bluetooth.am43_type.description = The type of blinds this motor is operating. (not really important) diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..b475615dc3bf7 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,33 @@ +# thing types + +thing-type.bluetooth.bluegiga.label = BlueGiga Bluetooth Dongle +thing-type.bluetooth.bluegiga.description = Serial interface to the BlueGiga dongle + +# thing types config + +thing-type.config.bluetooth.bluegiga.activeScanInterval.label = Active Scan Interval +thing-type.config.bluetooth.bluegiga.activeScanInterval.description = Active scan interval defines the interval when scanning is re-started in units of 625us. Default is 6400 units (4000ms). +thing-type.config.bluetooth.bluegiga.activeScanWindow.label = Active Scan Window +thing-type.config.bluetooth.bluegiga.activeScanWindow.description = Active scan Window defines how long time the scanner will listen on a certain frequency and try to pick up advertisement packets. Default is 6400 units (4000ms). +thing-type.config.bluetooth.bluegiga.backgroundDiscovery.label = Background Discovery +thing-type.config.bluetooth.bluegiga.backgroundDiscovery.description = Whether this adapter performs background discovery of Bluetooth devices +thing-type.config.bluetooth.bluegiga.connIntervalMax.label = Maximum Connection Interval +thing-type.config.bluetooth.bluegiga.connIntervalMax.description = Maximum connection interval in units of 1.25ms. Default is 800 units (1000ms). +thing-type.config.bluetooth.bluegiga.connIntervalMin.label = Minimum Connection Interval +thing-type.config.bluetooth.bluegiga.connIntervalMin.description = Minimum connection interval in units of 1.25ms. Default is 80 units (100ms). +thing-type.config.bluetooth.bluegiga.connLatency.label = Connection Latency +thing-type.config.bluetooth.bluegiga.connLatency.description = Connection latency defines how many connection intervals a slave device can skip. Default is 3. +thing-type.config.bluetooth.bluegiga.connTimeout.label = Connection Supervision Timeout +thing-type.config.bluetooth.bluegiga.connTimeout.description = Connection supervision timeout in units of 10ms defines how long the devices can be out of range before the connection is closed. Default is 1000 units (10000ms). +thing-type.config.bluetooth.bluegiga.inactiveDeviceCleanupInterval.label = Device Cleanup Interval +thing-type.config.bluetooth.bluegiga.inactiveDeviceCleanupInterval.description = How often device cleanup is performed +thing-type.config.bluetooth.bluegiga.inactiveDeviceCleanupThreshold.label = Device Cleanup Threshold +thing-type.config.bluetooth.bluegiga.inactiveDeviceCleanupThreshold.description = Timespan a device can remain radio silent before it is eligible for cleanup +thing-type.config.bluetooth.bluegiga.passiveScanIdleTime.label = Passive Scan Idle Time +thing-type.config.bluetooth.bluegiga.passiveScanIdleTime.description = Passive scan idle time defines the time how long to wait in milliseconds before start passive scan. +thing-type.config.bluetooth.bluegiga.passiveScanInterval.label = Passive Scan Interval +thing-type.config.bluetooth.bluegiga.passiveScanInterval.description = Passive scan interval defines the interval when scanning is re-started in units of 625us. Default is 6400 units (4000ms). +thing-type.config.bluetooth.bluegiga.passiveScanWindow.label = Passive Scan Window +thing-type.config.bluetooth.bluegiga.passiveScanWindow.description = Passive scan Window defines how long time the scanner will listen on a certain frequency and try to pick up advertisement packets. Default is 6400 units (4000ms). +thing-type.config.bluetooth.bluegiga.port.label = Port +thing-type.config.bluetooth.bluegiga.port.description = Serial Port diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..3b3f9bf4928b8 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,15 @@ +# thing types + +thing-type.bluetooth.bluez.label = Bluetooth BlueZ Adapter +thing-type.bluetooth.bluez.description = Linux built-in Bluetooth support + +# thing types config + +thing-type.config.bluetooth.bluez.address.label = Address +thing-type.config.bluetooth.bluez.address.description = The Bluetooth address of the adapter in format XX:XX:XX:XX:XX:XX +thing-type.config.bluetooth.bluez.backgroundDiscovery.label = Background Discovery +thing-type.config.bluetooth.bluez.backgroundDiscovery.description = Whether this adapter performs background discovery of Bluetooth devices +thing-type.config.bluetooth.bluez.inactiveDeviceCleanupInterval.label = Device Cleanup Interval +thing-type.config.bluetooth.bluez.inactiveDeviceCleanupInterval.description = How often device cleanup is performed +thing-type.config.bluetooth.bluez.inactiveDeviceCleanupThreshold.label = Device Cleanup Threshold +thing-type.config.bluetooth.bluez.inactiveDeviceCleanupThreshold.description = Timespan a device can remain radio silent before it is eligible for cleanup diff --git a/bundles/org.openhab.binding.bluetooth.blukii/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.blukii/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..45c9508187e2f --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.blukii/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,19 @@ +# thing types + +thing-type.bluetooth.blukii_beacon.label = Blukii SmartBeacon +thing-type.bluetooth.blukii_beacon.description = A Blukii SmartBeacon + +# thing types config + +thing-type.config.bluetooth.blukii_beacon.address.label = Address +thing-type.config.bluetooth.blukii_beacon.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format + +# channel types + +channel-type.bluetooth.blukii_humidity.label = Humidity +channel-type.bluetooth.blukii_luminance.label = Luminance +channel-type.bluetooth.blukii_pressure.label = Pressure +channel-type.bluetooth.blukii_temperature.label = Temperature +channel-type.bluetooth.blukii_tiltx.label = Tilt X +channel-type.bluetooth.blukii_tilty.label = Tilt Y +channel-type.bluetooth.blukii_tiltz.label = Tilt Z diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..a241fcdba7c52 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,47 @@ +# thing types + +thing-type.bluetooth.brc1h.label = Daikin BRC1H Thermostat +thing-type.bluetooth.brc1h.description = A Daikin Madoka BRC1H Thermostat (BLE) + +# thing types config + +thing-type.config.bluetooth.brc1h.address.label = Address +thing-type.config.bluetooth.brc1h.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.brc1h.commandTimeout.label = Command Timeout +thing-type.config.bluetooth.brc1h.commandTimeout.description = The amount of time, in milliseconds, a command should take before it times out. +thing-type.config.bluetooth.brc1h.refreshInterval.label = Refresh Interval +thing-type.config.bluetooth.brc1h.refreshInterval.description = Refresh interval for battery and light sensor data (in seconds). This could impact battery lifetime + +# channel types + +channel-type.bluetooth.brc1h_cleanFilter.label = Clean Filter Indicator +channel-type.bluetooth.brc1h_commCtrlVersion.label = Communication Controller Version +channel-type.bluetooth.brc1h_eyeBrightness.label = Eye Illumination Brightness +channel-type.bluetooth.brc1h_fanSpeed.label = Fan Speed +channel-type.bluetooth.brc1h_homebridgeMode.label = Mode from Homebridge +channel-type.bluetooth.brc1h_homebridgeMode.command.option.0 = Off +channel-type.bluetooth.brc1h_homebridgeMode.command.option.1 = Heating +channel-type.bluetooth.brc1h_homebridgeMode.command.option.2 = Cooling +channel-type.bluetooth.brc1h_homebridgeMode.command.option.3 = Auto +channel-type.bluetooth.brc1h_homekitCurrentHeatingCoolingMode.label = HomeKit CurrentMode +channel-type.bluetooth.brc1h_homekitCurrentHeatingCoolingMode.description = Readonly value. Off, Heating, Cooling, Auto +channel-type.bluetooth.brc1h_homekitTargetHeatingCoolingMode.label = HomeKit Target Mode +channel-type.bluetooth.brc1h_homekitTargetHeatingCoolingMode.command.option.Off = Off +channel-type.bluetooth.brc1h_homekitTargetHeatingCoolingMode.command.option.CoolOn = Cool +channel-type.bluetooth.brc1h_homekitTargetHeatingCoolingMode.command.option.HeatOn = Heat +channel-type.bluetooth.brc1h_homekitTargetHeatingCoolingMode.command.option.Auto = Auto +channel-type.bluetooth.brc1h_indoorFanHours.label = Number of hours fan has been operating +channel-type.bluetooth.brc1h_indoorOperationHours.label = Number of hours system has been operating +channel-type.bluetooth.brc1h_indoorPowerHours.label = Number of hours system has been powered up +channel-type.bluetooth.brc1h_indoorTemperature.label = Indoor Temperature +channel-type.bluetooth.brc1h_onOffStatus.label = Unit Power Status +channel-type.bluetooth.brc1h_operationMode.label = Operation Mode +channel-type.bluetooth.brc1h_operationMode.command.option.FAN = Fan +channel-type.bluetooth.brc1h_operationMode.command.option.DRY = Dry +channel-type.bluetooth.brc1h_operationMode.command.option.AUTO = Auto +channel-type.bluetooth.brc1h_operationMode.command.option.COOL = Cool +channel-type.bluetooth.brc1h_operationMode.command.option.HEAT = Heat +channel-type.bluetooth.brc1h_operationMode.command.option.VENTILATION = Ventilation +channel-type.bluetooth.brc1h_outdoorTemperature.label = Outdoor Temperature +channel-type.bluetooth.brc1h_remoteCtrlVersion.label = Remote Controller Version +channel-type.bluetooth.brc1h_setpoint.label = Setpoint diff --git a/bundles/org.openhab.binding.bluetooth.enoceanble/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.enoceanble/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..16eb22ddd8f0c --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.enoceanble/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,9 @@ +# thing types + +thing-type.bluetooth.ptm215b.label = Enocean PTM 215B Rocker +thing-type.bluetooth.ptm215b.description = An Enocean BLE Rocker (PTM 215B) + +# thing types config + +thing-type.config.bluetooth.ptm215b.address.label = Address +thing-type.config.bluetooth.ptm215b.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format diff --git a/bundles/org.openhab.binding.bluetooth.generic/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.generic/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..af35826047632 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.generic/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,18 @@ +# thing types + +thing-type.bluetooth.generic.label = Generic Bluetooth Device +thing-type.bluetooth.generic.description = A generic bluetooth device that supports GATT characteristics + +# thing types config + +thing-type.config.bluetooth.generic.address.label = Address +thing-type.config.bluetooth.generic.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.generic.alwaysConnected.label = Connect Automatically +thing-type.config.bluetooth.generic.alwaysConnected.description = If enabled, will automatically connect to the device and reconnect if connection is lost. +thing-type.config.bluetooth.generic.pollingInterval.label = Polling Interval +thing-type.config.bluetooth.generic.pollingInterval.description = The frequency at which readable characteristics refreshed + +# channel types + +channel-type.bluetooth.char-unknown.label = Unknown Bluetooth Characteristic +channel-type.bluetooth.char-unknown.description = The raw value of unknown characteristics are represented with hexadecimal diff --git a/bundles/org.openhab.binding.bluetooth.govee/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.govee/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..527fa31332c5b --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.govee/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,51 @@ +# thing types + +thing-type.bluetooth.goveeHygrometer.label = Govee Hygrometer +thing-type.bluetooth.goveeHygrometer.description = Govee Thermo-Hygrometer +thing-type.bluetooth.goveeHygrometerMonitor.label = Govee Monitoring Hygrometer +thing-type.bluetooth.goveeHygrometerMonitor.description = Govee Thermo-Hygrometer w/ Warning Alarms + +# thing types config + +thing-type.config.bluetooth.goveeHygrometer.address.label = Address +thing-type.config.bluetooth.goveeHygrometer.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.goveeHygrometer.group.calibration.label = Calibration +thing-type.config.bluetooth.goveeHygrometer.group.calibration.description = Sensor calibration settings. +thing-type.config.bluetooth.goveeHygrometer.humidityCalibration.label = Humidity Calibration +thing-type.config.bluetooth.goveeHygrometer.humidityCalibration.description = Adds offset to reported humidity +thing-type.config.bluetooth.goveeHygrometer.refreshInterval.label = Refresh Interval +thing-type.config.bluetooth.goveeHygrometer.refreshInterval.description = The frequency at which battery, temperature, and humidity data will refresh +thing-type.config.bluetooth.goveeHygrometer.temperatureCalibration.label = Temperature Calibration +thing-type.config.bluetooth.goveeHygrometer.temperatureCalibration.description = Adds offset to reported temperature +thing-type.config.bluetooth.goveeHygrometerMonitor.address.label = Address +thing-type.config.bluetooth.goveeHygrometerMonitor.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.bluetooth.goveeHygrometerMonitor.group.alarms.label = Alarm +thing-type.config.bluetooth.goveeHygrometerMonitor.group.alarms.description = Alarm settings. +thing-type.config.bluetooth.goveeHygrometerMonitor.group.calibration.label = Calibration +thing-type.config.bluetooth.goveeHygrometerMonitor.group.calibration.description = Sensor calibration settings. +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityCalibration.label = Humidity Calibration +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityCalibration.description = Adds offset to reported humidity +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityWarningAlarm.label = Broadcast Humidity Warning +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityWarningAlarm.description = If enabled, the Govee device will notify openHAB if humidity is out of the specified range +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityWarningMax.label = Max Warning Humidity +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityWarningMax.description = Sets the highest acceptable humidity value before a warning should be issued +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityWarningMin.label = Min Warning Humidity +thing-type.config.bluetooth.goveeHygrometerMonitor.humidityWarningMin.description = Sets the lowest acceptable humidity value before a warning should be issued +thing-type.config.bluetooth.goveeHygrometerMonitor.refreshInterval.label = Refresh Interval +thing-type.config.bluetooth.goveeHygrometerMonitor.refreshInterval.description = The frequency at which battery, temperature, and humidity data will refresh +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureCalibration.label = Temperature Calibration +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureCalibration.description = Adds offset to reported temperature +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureWarningAlarm.label = Broadcast Temperature Warning +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureWarningAlarm.description = If enabled, the Govee device will notify openHAB if temperature is out of the specified range +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureWarningMax.label = Max Warning Temperature +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureWarningMax.description = Sets the highest acceptable temperature value before a warning should be issued +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureWarningMin.label = Min Warning Temperature +thing-type.config.bluetooth.goveeHygrometerMonitor.temperatureWarningMin.description = Sets the lowest acceptable temperature value before a warning should be issued + +# channel types + +channel-type.bluetooth.govee-humidity-alarm.label = Humidity Warning Alarm +channel-type.bluetooth.govee-humidity-alarm.description = If humidity warnings are enabled, then this alarm indicates whether the current humidity is out of range. +channel-type.bluetooth.govee-temperature-alarm.label = Temperature Warning Alarm +channel-type.bluetooth.govee-temperature-alarm.description = If temperature warnings are enabled, then this alarm indicates whether the current temperature is out of range. +channel-type.bluetooth.govee-temperature.label = Current Measured Temperature diff --git a/bundles/org.openhab.binding.bluetooth.roaming/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.roaming/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..95a28252b78d7 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.roaming/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,11 @@ +# thing types + +thing-type.bluetooth.roaming.label = Roaming Bluetooth Controller +thing-type.bluetooth.roaming.description = A virtual Bluetooth adapter that handles roaming between other adapters + +# thing types config + +thing-type.config.bluetooth.roaming.backgroundDiscovery.label = Device Discovery +thing-type.config.bluetooth.roaming.backgroundDiscovery.description = Whether this adapter participates in Bluetooth device discovery +thing-type.config.bluetooth.roaming.groupUIDs.label = Adapter UIDs +thing-type.config.bluetooth.roaming.groupUIDs.description = Specifies which Bluetooth adapters that roaming devices can interact through.
Should be formatted as a comma separated list of thing UIDs.
If not specified, roaming devices can interact through any other Bluetooth adapter thing. diff --git a/bundles/org.openhab.binding.bluetooth.ruuvitag/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.ruuvitag/src/main/resources/OH-INF/i18n/bluetooth.properties new file mode 100644 index 0000000000000..96166acb70469 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.ruuvitag/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -0,0 +1,23 @@ +# thing types + +thing-type.bluetooth.ruuvitag_beacon.label = RuuviTag SmartBeacon +thing-type.bluetooth.ruuvitag_beacon.description = A RuuviTag SmartBeacon + +# thing types config + +thing-type.config.bluetooth.ruuvitag_beacon.address.label = Address +thing-type.config.bluetooth.ruuvitag_beacon.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format + +# channel types + +channel-type.bluetooth.ruuvitag_accelerationx.label = Acceleration X +channel-type.bluetooth.ruuvitag_accelerationy.label = Acceleration Y +channel-type.bluetooth.ruuvitag_accelerationz.label = Acceleration Z +channel-type.bluetooth.ruuvitag_batteryVoltage.label = Battery Voltage +channel-type.bluetooth.ruuvitag_dataFormat.label = Data Format Version +channel-type.bluetooth.ruuvitag_humidity.label = Humidity +channel-type.bluetooth.ruuvitag_measurementSequenceNumber.label = Measurement Sequence Number +channel-type.bluetooth.ruuvitag_movementCounter.label = Measurement Counter +channel-type.bluetooth.ruuvitag_pressure.label = Pressure +channel-type.bluetooth.ruuvitag_temperature.label = Temperature +channel-type.bluetooth.ruuvitag_txPower.label = TX Power From 6c104e241a0b91f8a2db9c17bd6f610bde60aab9 Mon Sep 17 00:00:00 2001 From: Patrick Fink Date: Sun, 20 Feb 2022 21:53:30 +0100 Subject: [PATCH 13/19] [flicbutton] Initial contribution FlicButton Binding (#9234) * [flicbutton] Initial contribution FlicButton Binding Signed-off-by: Patrick Fink * [flicbutton] Add config parameter address for FlicButton thing Signed-off-by: Patrick Fink * [flicbutton] Run spotless Signed-off-by: Patrick Fink * [flicbutton] Code cleanup & docs improvement Signed-off-by: Patrick Fink * Apply suggestions from code review Co-authored-by: Fabian Wolter * [flicbutton] Update LICENSE Signed-off-by: Patrick Fink * [flicbutton] Apply suggestions from code review (2) & update to 3.1-SNAPSHOT Signed-off-by: Patrick Fink * [flicbutton] Apply suggestions from code review (3) & fix offline status Signed-off-by: Patrick Fink * [flicbutton] Fix 3rd party source for proper IDE integration Signed-off-by: Patrick Fink * [flicbutton] Simplify config parsing Signed-off-by: Patrick Fink * [flicbutton] Move everything to internal package Signed-off-by: Patrick Fink * [flicbutton] Remove hyphens from port parameter docs example Signed-off-by: Patrick Fink * [flicbutton] Change maintainer to openHAB project Signed-off-by: Patrick Fink * Apply docs suggestions + update to 3.2.0-SNAPSHOT Signed-off-by: Patrick Fink Co-authored-by: Matthew Skinner * [flicbutton] Fix bridge offline & reconnect handling Signed-off-by: Patrick Fink * [flicbutton] Close open socket on dispose Signed-off-by: Patrick Fink * [flicbutton] Improve exception error message in ThingStatus Signed-off-by: Patrick Fink * [flicbutton] Fix README title Signed-off-by: Patrick Fink * [flicbutton] Improve exception error message in ThingStatus Signed-off-by: Patrick Fink * [flicbutton] Style fixes Signed-off-by: Patrick Fink * [flicbutton] Use trace log level for button clicks & status changes Signed-off-by: Patrick Fink * Apply doc improvements from code review Signed-off-by: Patrick Fink Co-authored-by: Matthew Skinner * [flicbutton] Add binding to bom/openhab-addons Signed-off-by: Patrick Fink * [flicbutton] Cleanup / remove guava leftover Signed-off-by: Patrick Fink * [flicbutton] Remove online status description Signed-off-by: Patrick Fink * [flicbutton] Improve flicd hostname label Signed-off-by: Patrick Fink Co-authored-by: Fabian Wolter * [flicbutton] Do not catch IllegalArgumentException anymore as its not neeed Signed-off-by: Patrick Fink * [flicbutton] Use debug log level instead of info Signed-off-by: Patrick Fink * [flicbutton] Update version and license Signed-off-by: Patrick Fink * [flicbutton] Fix SAT warnings, e.g. add null handling annotations Signed-off-by: Patrick Fink * [flicbutton] Fix SAT warnings (2) Signed-off-by: Patrick Fink * [flicbutton] Concurrency refactoring & fixes Signed-off-by: Patrick Fink * [flicbutton] Cancel initialization task also when already running Signed-off-by: Patrick Fink * [flicbutton] Add javadoc and move FLIC_OPENHAB_EVENT_TRIGGER_MAP constant to constants class Signed-off-by: Patrick Fink * [flicbutton] Use ThingStatusDetail.OFFLINE.GONE when Flic button was removed from bridge Signed-off-by: Patrick Fink * [flicbutton] Fix FlicSimpleclientDiscoveryServiceImpl javadoc Signed-off-by: Patrick Fink * [flicbutton] Fix required definition of thing types Signed-off-by: Patrick Fink Co-authored-by: Fabian Wolter Co-authored-by: Matthew Skinner --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.flicbutton/NOTICE | 21 + .../org.openhab.binding.flicbutton/README.md | 130 ++++ .../org.openhab.binding.flicbutton/pom.xml | 38 ++ .../src/3rdparty/LICENSE | 121 ++++ .../javaclient/BatteryStatusListener.java | 45 ++ .../io/flic/fliclib/javaclient/Bdaddr.java | 68 ++ .../javaclient/ButtonConnectionChannel.java | 188 ++++++ .../fliclib/javaclient/ButtonScanner.java | 28 + .../flic/fliclib/javaclient/FlicClient.java | 630 ++++++++++++++++++ .../fliclib/javaclient/GeneralCallbacks.java | 28 + .../GetButtonInfoResponseCallback.java | 18 + .../javaclient/GetInfoResponseCallback.java | 19 + .../io/flic/fliclib/javaclient/Packets.java | 455 +++++++++++++ .../flic/fliclib/javaclient/ScanWizard.java | 64 ++ .../flic/fliclib/javaclient/StreamUtils.java | 81 +++ .../io/flic/fliclib/javaclient/TimerTask.java | 14 + .../fliclib/javaclient/enums/BdAddrType.java | 9 + .../enums/BluetoothControllerState.java | 10 + .../fliclib/javaclient/enums/ClickType.java | 13 + .../javaclient/enums/ConnectionStatus.java | 10 + .../enums/CreateConnectionChannelError.java | 9 + .../javaclient/enums/DisconnectReason.java | 11 + .../fliclib/javaclient/enums/LatencyMode.java | 10 + .../javaclient/enums/RemovedReason.java | 22 + .../javaclient/enums/ScanWizardResult.java | 13 + .../src/main/feature/feature.xml | 9 + .../internal/FlicButtonBindingConstants.java | 65 ++ .../internal/FlicButtonHandlerFactory.java | 104 +++ .../discovery/FlicButtonDiscoveryService.java | 41 ++ .../FlicSimpleclientDiscoveryServiceImpl.java | 138 ++++ .../internal/handler/ChildThingHandler.java | 86 +++ .../FlicButtonBatteryLevelListener.java | 43 ++ .../handler/FlicButtonEventListener.java | 104 +++ .../internal/handler/FlicButtonHandler.java | 213 ++++++ .../FlicDaemonBridgeConfiguration.java | 41 ++ .../handler/FlicDaemonBridgeHandler.java | 152 +++++ .../internal/util/FlicButtonUtils.java | 38 ++ .../main/resources/OH-INF/binding/binding.xml | 9 + .../resources/OH-INF/thing/thing-types.xml | 44 ++ bundles/pom.xml | 1 + 42 files changed, 3149 insertions(+) create mode 100644 bundles/org.openhab.binding.flicbutton/NOTICE create mode 100644 bundles/org.openhab.binding.flicbutton/README.md create mode 100644 bundles/org.openhab.binding.flicbutton/pom.xml create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/LICENSE create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/BatteryStatusListener.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Bdaddr.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonConnectionChannel.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonScanner.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/FlicClient.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GeneralCallbacks.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetButtonInfoResponseCallback.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetInfoResponseCallback.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Packets.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ScanWizard.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/StreamUtils.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/TimerTask.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BdAddrType.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BluetoothControllerState.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ClickType.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ConnectionStatus.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/CreateConnectionChannelError.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/DisconnectReason.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/LatencyMode.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/RemovedReason.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ScanWizardResult.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonBindingConstants.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonHandlerFactory.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicButtonDiscoveryService.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/ChildThingHandler.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonBatteryLevelListener.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonHandler.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeConfiguration.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/util/FlicButtonUtils.java create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 1e9001fe90f0e..63317c9bafabe 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -93,6 +93,7 @@ /bundles/org.openhab.binding.exec/ @kgoderis /bundles/org.openhab.binding.feed/ @svilenvul /bundles/org.openhab.binding.feican/ @Hilbrand +/bundles/org.openhab.binding.flicbutton/ @pfink /bundles/org.openhab.binding.fmiweather/ @ssalonen /bundles/org.openhab.binding.folderwatcher/ @goopilot /bundles/org.openhab.binding.folding/ @fa2k diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 415aecf36118d..bb4e528761e3b 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -456,6 +456,11 @@ org.openhab.binding.feican ${project.version}
+ + org.openhab.addons.bundles + org.openhab.binding.flicbutton + ${project.version} + org.openhab.addons.bundles org.openhab.binding.fmiweather diff --git a/bundles/org.openhab.binding.flicbutton/NOTICE b/bundles/org.openhab.binding.flicbutton/NOTICE new file mode 100644 index 0000000000000..ca1e0d671e175 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/NOTICE @@ -0,0 +1,21 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons + +== Third-party Content + +fliclib-javaclient (files under src/3rdparty) + +* License: CC0 1.0 +* Project: https://github.com/50ButtonsEach/fliclib-linux-hci +* Source: https://github.com/50ButtonsEach/fliclib-linux-hci/tree/master/clientlib/java/lib/src/main/java/io/flic/fliclib/javaclient diff --git a/bundles/org.openhab.binding.flicbutton/README.md b/bundles/org.openhab.binding.flicbutton/README.md new file mode 100644 index 0000000000000..87e81dfdf2d6d --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/README.md @@ -0,0 +1,130 @@ +# Flic Button Binding + +openHAB binding for using [Flic Buttons](https://flic.io/) +with a [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci) bridge. + +Currently, although Flic Buttons are BLE devices, this binding only supports fliclib-linux-hci (flicd) as a bridge. +The openHAB Bluetooth Bindings are not supported. +Flicd requires a seperate Bluetooth adapter to work, so if you use this binding together with e.g. the +[Bluez Binding](https://www.openhab.org/addons/bindings/bluetooth.bluez/), +two physical Bluetooth adapters are required (one for Bluez and one for flicd). +Be aware that flicd requires an initial internet connection for the verification of the buttons. +After buttons are initially added to flicd, an internet connection is not required anymore. + +## Supported Things + +| Thing Type ID | Description | +| --------------- | ------------------------- | +| flicd-bridge | The bridge representing a running instance of [fliclib-linux-hci (flicd)](https://github.com/50ButtonsEach/fliclib-linux-hci) on the server. | +| button | The Flic button (supports Flic 1 buttons as well as Flic 2 buttons) | + +## Discovery + +* There is no automatic discovery for flicd-bridge available. +* After flicd-bridge is (manually) configured, buttons will be automatically discovered via background discovery as soon + as they're added with [simpleclient](https://github.com/50ButtonsEach/fliclib-linux-hci). + +If they're already attached to the flicd-bridge before configuring this binding, they can be discovered by triggering an +active scan. + +## Thing Configuration + +### flicd-bridge + +Example for textual configuration: + +``` +Bridge flicbutton:flicd-bridge:mybridge +``` + +The default host is localhost:5551 (this should be sufficient if flicd is running with default settings on the same server as openHAB). +If your flicd service is running somewhere else, specify it like this: + +``` +Bridge flicbutton:flicd-bridge:mybridge [ hostname="", port=] +``` + +If flicd is running on a remote host, please do not forget to start it with the parameter `-s `, otherwise it won't be accessible for openHAB (more details on [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci)). + +### button + +For the button, the only config parameter is the MAC address. +Normally, no textual configuration is necessary as buttons are auto discovered as soon as the bridge is configured. +If you want to use textual configuration anyway, you can do it like this: + +``` +Bridge flicbutton:flicd-bridge:mybridge [ hostname="", port=] { + Thing button myflic1 "" [address =""] + Thing button myflic2 "" [address =""] + ... +} +``` + +You can lookup the MAC addresses of your buttons within the inbox of the UI. +You're free to choose any label you like for your button. + +## Channels + +| Channel ID | Channel Type | Item Type | Description | +| ------------------------- | ------------------------ | --------------------------| ------------------------------ | +| rawbutton | [System Trigger Channel](https://www.openhab.org/docs/developer/bindings/thing-xml.html#system-trigger-channel-types) `system.rawbutton` | Depends on the [Trigger Profile](https://www.openhab.org/docs/configuration/items.html#profiles) used | Raw Button channel that triggers `PRESSED` and `RELEASED` events, allows to use openHAB profiles or own implementations via rules to detect e.g. double clicks, long presses etc. | +| button | [System Trigger Channel](https://www.openhab.org/docs/developer/bindings/thing-xml.html#system-trigger-channel-types) `system.button` | Depends on the [Trigger Profile](https://www.openhab.org/docs/configuration/items.html#profiles) used | Button channel that is using Flic's implementation for detecting long, short or double clicks. Triggers `SHORT_PRESSED`,`DOUBLE_PRESSED` and `LONG_PRESSED` events. | +| battery-level | [System State Channel](https://www.openhab.org/docs/developer/bindings/thing-xml.html#system-state-channel-types) `system.battery-level` | Number | Represents the battery level as a percentage (0-100%). +## Example + +### Initial setup + +1. Setup and run flicd as described in [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci). + Please consider that you need a separate Bluetooth adapter. Shared usage with other Bluetooth services (e.g. Bluez) + is not possible. +1. Connect your buttons to flicd using the simpleclient as described in + [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci). Flicd has to run in background the whole + time, simpleclient can be killed after you successfully test the button connects. +1. Add a flicd-bridge via the UI or textual configuration. Please consider that flicd only accepts connections from + localhost by default, to enable remote connections from openHAB you have to use the `--server-addr` parameter as + described in [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci). +1. When the bridge is online, buttons newly added via simpleclient will automatically get discovered via background + discovery. To discover buttons that were set up before the binding was setup, please run an active scan. + +### Configuration Example using Profiles + +[Profiles](https://www.openhab.org/docs/configuration/items.html#profiles) are the recommended way to use this binding. + +demo.things: + +``` +Bridge flicbutton:flicd-bridge:local-flicd { + Thing button flic_livingroom "Yellow Button Living Room" [address = "60:13:B3:02:18:BD"] + Thing button flic_kitchen "Black Button Kitchen" [address = "B5:7E:59:78:86:9F"] +} +``` + +demo.items: + +``` +Dimmer Light_LivingRoom { channel="milight:rgbLed:milight2:4:ledbrightness", channel="flicbutton:button:local-flicd:flic_livingroom:rawbutton" [profile="rawbutton-toggle-switch"], channel="flicbutton:button:local-flicd:flic_kitchen:rawbutton" [profile="rawbutton-toggle-switch"] } // We have a combined kitchen / livingroom, so we control the living room lights with switches from the living room and from the kitchen +Switch Light_Kitchen { channel="hue:group:1:kitchen-bulbs:switch", channel="flicbutton:button:local-flicd:flic_kitchen:rawbutton" [profile="rawbutton-toggle-switch"] } +``` + +### Configuration Example using Rules + +It's also possible to setup [Rules](https://www.openhab.org/docs/configuration/rules-dsl.html). +The following rules help to initially test your setup as they'll trigger log messages on incoming events. + +``` +rule "Button rule using the button channel" + +when + Channel "flicbutton:button:local-flicd:flic_livingroom:button" triggered SHORT_PRESSED +then + logInfo("Flic", "Flic 'short pressed' triggered") +end + +rule "Button rule directly using the rawbutton channel" + +when + Channel "flicbutton:button:local-flicd:flic_livingroom:rawbutton" triggered +then + logInfo("Flic", "Flic pressed: " + receivedEvent.event) +end +``` diff --git a/bundles/org.openhab.binding.flicbutton/pom.xml b/bundles/org.openhab.binding.flicbutton/pom.xml new file mode 100644 index 0000000000000..9163b246e9d4b --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.3.0-SNAPSHOT + + + org.openhab.binding.flicbutton + + openHAB Add-ons :: Bundles :: FlicButton Binding + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + add-source + + generate-sources + + + src/3rdparty/java + + + + + + + + diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/LICENSE b/bundles/org.openhab.binding.flicbutton/src/3rdparty/LICENSE new file mode 100644 index 0000000000000..1625c17936079 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. \ No newline at end of file diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/BatteryStatusListener.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/BatteryStatusListener.java new file mode 100644 index 0000000000000..bb562f191500b --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/BatteryStatusListener.java @@ -0,0 +1,45 @@ +package io.flic.fliclib.javaclient; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Battery status listener. + * + * Add this listener to a {@link FlicClient} by executing {@link FlicClient#addBatteryStatusListener(BatteryStatusListener)}. + */ +public class BatteryStatusListener { + private static AtomicInteger nextId = new AtomicInteger(); + int listenerId = nextId.getAndIncrement(); + + private Bdaddr bdaddr; + Callbacks callbacks; + + public BatteryStatusListener(Bdaddr bdaddr, Callbacks callbacks) { + if (bdaddr == null) { + throw new IllegalArgumentException("bdaddr is null"); + } + if (callbacks == null) { + throw new IllegalArgumentException("callbacks is null"); + } + this.bdaddr = bdaddr; + this.callbacks = callbacks; + } + + public Bdaddr getBdaddr() { + return bdaddr; + } + + public abstract static class Callbacks { + /** + * This will be called when the battery status has been updated. + * It will also be called immediately after the battery status listener has been created. + * If the button stays connected, this method will be called approximately every three hours. + * + * @param bdaddr Bluetooth device address + * @param batteryPercentage A number between 0 and 100 for the battery level. Will be -1 if unknown. + * @param timestamp Standard UNIX timestamp, in seconds, for the event. + */ + public abstract void onBatteryStatus(Bdaddr bdaddr, int batteryPercentage, long timestamp) throws IOException; + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Bdaddr.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Bdaddr.java new file mode 100644 index 0000000000000..16a7b3989d3ff --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Bdaddr.java @@ -0,0 +1,68 @@ +package io.flic.fliclib.javaclient; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +/** + * Bluetooth address. + */ +public class Bdaddr { + private byte[] bytes; + + /** + * Creates a Bdaddr given the bluetooth address in string format. + * + * @param addr address of the format xx:xx:xx:xx:xx:xx + */ + public Bdaddr(String addr) { + bytes = new byte[6]; + bytes[5] = (byte)Integer.parseInt(addr.substring(0, 2), 16); + bytes[4] = (byte)Integer.parseInt(addr.substring(3, 5), 16); + bytes[3] = (byte)Integer.parseInt(addr.substring(6, 8), 16); + bytes[2] = (byte)Integer.parseInt(addr.substring(9, 11), 16); + bytes[1] = (byte)Integer.parseInt(addr.substring(12, 14), 16); + bytes[0] = (byte)Integer.parseInt(addr.substring(15, 17), 16); + } + + Bdaddr(InputStream stream) throws IOException { + bytes = new byte[6]; + for (int i = 0; i < 6; i++) { + bytes[i] = (byte)stream.read(); + } + } + + byte[] getBytes() { + return bytes.clone(); + } + + /** + * Create a string representing the bluetooth address. + * + * @return A string of the bdaddr + */ + @Override + public String toString() { + return String.format("%02x:%02x:%02x:%02x:%02x:%02x", bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]); + } + + @Override + public int hashCode() { + return (bytes[0] & 0xff) ^ ((bytes[1] & 0xff) << 8) ^ ((bytes[2] & 0xff) << 16) ^ ((bytes[3] & 0xff) << 24) ^ (bytes[4] & 0xff) ^ ((bytes[5] & 0xff) << 8); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Bdaddr)) { + return false; + } + Bdaddr other = (Bdaddr)obj; + return Arrays.equals(bytes, other.bytes); + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonConnectionChannel.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonConnectionChannel.java new file mode 100644 index 0000000000000..a006aea8b2eea --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonConnectionChannel.java @@ -0,0 +1,188 @@ +package io.flic.fliclib.javaclient; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import io.flic.fliclib.javaclient.enums.*; + +/** + * Button connection channel. + * + * Add this button connection channel to a {@link FlicClient} by executing {@link FlicClient#addConnectionChannel(ButtonConnectionChannel)}. + * You may only have this connection channel added to one {@link FlicClient} at a time. + */ +public class ButtonConnectionChannel { + private static AtomicInteger nextId = new AtomicInteger(); + int connId = nextId.getAndIncrement(); + + FlicClient client; + + private Bdaddr bdaddr; + private LatencyMode latencyMode; + private short autoDisconnectTime; + Callbacks callbacks; + + final Object lock = new Object(); + + /** + * Create a connection channel using the specified parameters. + * + * Add this button connection channel to a {@link FlicClient} by executing {@link FlicClient#addConnectionChannel(ButtonConnectionChannel)}. + * + * @param bdaddr + * @param latencyMode + * @param autoDisconnectTime Number of seconds (0 - 511) until disconnect if no button event happens. 512 disables this feature. + * @param callbacks + */ + public ButtonConnectionChannel(Bdaddr bdaddr, LatencyMode latencyMode, short autoDisconnectTime, Callbacks callbacks) { + if (bdaddr == null) { + throw new IllegalArgumentException("bdaddr is null"); + } + if (latencyMode == null) { + throw new IllegalArgumentException("latencyMode is null"); + } + if (callbacks == null) { + throw new IllegalArgumentException("callbacks is null"); + } + this.bdaddr = bdaddr; + this.latencyMode = latencyMode; + this.autoDisconnectTime = autoDisconnectTime; + this.callbacks = callbacks; + } + + /** + * Create a connection channel using the specified parameters. + * + * Add this button connection channel to a {@link FlicClient} by executing {@link FlicClient#addConnectionChannel(ButtonConnectionChannel)}. + * + * @param bdaddr + * @param callbacks + */ + public ButtonConnectionChannel(Bdaddr bdaddr, Callbacks callbacks) { + this(bdaddr, LatencyMode.NormalLatency, (short)0x1ff, callbacks); + } + + /** + * Get the {@link FlicClient} for this {@link ButtonConnectionChannel}. + * + * @return + */ + public FlicClient getFlicClient() { + return client; + } + + public Bdaddr getBdaddr() { + return bdaddr; + } + + public LatencyMode getLatencyMode() { + return latencyMode; + } + + public short getAutoDisconnectTime() { + return autoDisconnectTime; + } + + /** + * Applies new latency mode parameter. + * + * @param latencyMode + */ + public void setLatencyMode(LatencyMode latencyMode) throws IOException { + if (latencyMode == null) { + throw new IllegalArgumentException("latencyMode is null"); + } + synchronized (lock) { + this.latencyMode = latencyMode; + + FlicClient cl = client; + if (cl != null) { + CmdChangeModeParameters pkt = new CmdChangeModeParameters(); + pkt.connId = connId; + pkt.latencyMode = latencyMode; + pkt.autoDisconnectTime = autoDisconnectTime; + cl.sendPacket(pkt); + } + } + } + + /** + * Applies new auto disconnect time parameter. + * + * @param autoDisconnectTime Number of seconds (0 - 511) until disconnect if no button event happens. 512 disables this feature. + */ + public void setAutoDisconnectTime(short autoDisconnectTime) throws IOException { + if (latencyMode == null) { + throw new IllegalArgumentException("latencyMode is null"); + } + synchronized (lock) { + this.autoDisconnectTime = autoDisconnectTime; + + FlicClient cl = client; + if (cl != null) { + CmdChangeModeParameters pkt = new CmdChangeModeParameters(); + pkt.connId = connId; + pkt.latencyMode = latencyMode; + pkt.autoDisconnectTime = autoDisconnectTime; + cl.sendPacket(pkt); + } + } + } + + /** + * User callbacks for incoming events. + * + * See the protocol specification for further details. + */ + public abstract static class Callbacks { + + /** + * Called when the server has received the create connection channel command. + * + * If createConnectionChannelError is {@link CreateConnectionChannelError#NoError}, other events will arrive until {@link #onRemoved} is received. + * There will be no {@link #onRemoved} if an error occurred. + * + * @param channel + * @param createConnectionChannelError + * @param connectionStatus + * @throws IOException + */ + public void onCreateConnectionChannelResponse(ButtonConnectionChannel channel, CreateConnectionChannelError createConnectionChannelError, ConnectionStatus connectionStatus) throws IOException { + } + + /** + * Called when the connection channel has been removed. + * + * Check the removedReason to find out why. From this point, the connection channel can be re-added again if you wish. + * + * @param channel + * @param removedReason + * @throws IOException + */ + public void onRemoved(ButtonConnectionChannel channel, RemovedReason removedReason) throws IOException { + } + + /** + * Called when the connection status changes. + * + * @param channel + * @param connectionStatus + * @param disconnectReason Only valid if connectionStatus is {@link ConnectionStatus#Disconnected} + * @throws IOException + */ + public void onConnectionStatusChanged(ButtonConnectionChannel channel, ConnectionStatus connectionStatus, DisconnectReason disconnectReason) throws IOException { + } + + public void onButtonUpOrDown(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException { + } + + public void onButtonClickOrHold(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException { + } + + public void onButtonSingleOrDoubleClick(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException { + } + + public void onButtonSingleOrDoubleClickOrHold(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException { + } + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonScanner.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonScanner.java new file mode 100644 index 0000000000000..41ca1487e8cf9 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ButtonScanner.java @@ -0,0 +1,28 @@ +package io.flic.fliclib.javaclient; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Button scanner class. + * + * Inherit this class and override the {@link #onAdvertisementPacket(Bdaddr, String, int, boolean, boolean)} method. + * Then add this button scanner to a {@link FlicClient} using {@link FlicClient#addScanner(ButtonScanner)} to start it. + */ +public abstract class ButtonScanner { + private static AtomicInteger nextId = new AtomicInteger(); + int scanId = nextId.getAndIncrement(); + + /** + * This will be called for every received advertisement packet from a Flic button. + * + * @param bdaddr Bluetooth address + * @param name Advertising name + * @param rssi RSSI value in dBm + * @param isPrivate The button is private and won't accept new connections from non-bonded clients + * @param alreadyVerified The server has already verified this button, which means you can connect to it even if it's private + * @param alreadyConnectedToThisDevice The button is already connected to this device + * @param alreadyConnectedToOtherDevice The button is already connected to another device + */ + public abstract void onAdvertisementPacket(Bdaddr bdaddr, String name, int rssi, boolean isPrivate, boolean alreadyVerified, boolean alreadyConnectedToThisDevice, boolean alreadyConnectedToOtherDevice) throws IOException; +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/FlicClient.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/FlicClient.java new file mode 100644 index 0000000000000..07f0544bbf4e0 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/FlicClient.java @@ -0,0 +1,630 @@ +package io.flic.fliclib.javaclient; + +import io.flic.fliclib.javaclient.enums.CreateConnectionChannelError; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.ArrayDeque; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Implements a FlicClient over a TCP Socket. + * + * When this class is constructed, a socket connection is established. + * + * You may then send commands to the server and set timers. + * + * Once you are ready with the initialization you must call the {@link #handleEvents()} method which is a main loop that never exits, unless the socket is closed. + * + * For a more detailed description of all commands, events and enums, check the protocol specification. + */ +public class FlicClient { + private Socket socket; + private InputStream socketInputStream; + private OutputStream socketOutputStream; + + private ConcurrentHashMap scanners = new ConcurrentHashMap<>(); + private ConcurrentHashMap connectionChannels = new ConcurrentHashMap<>(); + private ConcurrentHashMap scanWizards = new ConcurrentHashMap<>(); + private ConcurrentHashMap batteryStatusListeners = new ConcurrentHashMap<>(); + private ConcurrentLinkedQueue getInfoResponseCallbackQueue = new ConcurrentLinkedQueue<>(); + private ArrayDeque getButtonInfoResponseCallbackQueue = new ArrayDeque<>(); + + private volatile GeneralCallbacks generalCallbacks = new GeneralCallbacks(); + + private ConcurrentSkipListMap timers = new ConcurrentSkipListMap<>(); + + private Thread handleEventsThread; + + /** + * Create a FlicClient and connect to the specified hostName and TCP port + * + * @param hostName + * @param port + * @throws UnknownHostException + * @throws IOException + */ + public FlicClient(String hostName, int port) throws UnknownHostException, IOException { + socket = new Socket(hostName, port); + socket.setKeepAlive(true); + socketInputStream = socket.getInputStream(); + socketOutputStream = socket.getOutputStream(); + } + + /** + * Create a FlicClient and connect to the specified hostName using the default TCP port + * + * @param hostName + * @throws UnknownHostException + * @throws IOException + */ + public FlicClient(String hostName) throws UnknownHostException, IOException { + this(hostName, 5551); + } + + /** + * Close the socket. + * + * From this point any use of this FlicClient is illegal. + * The {@link #handleEvents()} will return as soon as the closing is done. + * + * @throws IOException + */ + public void close() throws IOException { + runOnHandleEventsThread(new TimerTask() { + @Override + public void run() throws IOException { + socket.close(); + } + }); + } + + /** + * Set general callbacks to be called upon receiving some specific events. + * + * @param callbacks + */ + public void setGeneralCallbacks(GeneralCallbacks callbacks) { + if (callbacks == null) { + callbacks = new GeneralCallbacks(); + } + generalCallbacks = callbacks; + } + + /** + * Get info about the current state of the server. + * + * The server will send back its information directly and the callback will be called once the response arrives. + * + * @param callback + * @throws IOException + */ + public void getInfo(GetInfoResponseCallback callback) throws IOException { + if (callback == null) { + throw new IllegalArgumentException("callback is null"); + } + getInfoResponseCallbackQueue.add(callback); + + CmdGetInfo pkt = new CmdGetInfo(); + sendPacket(pkt); + } + + /** + * Get button info for a verified button. + * + * The server will send back its information directly and the callback will be called once the response arrives. + * Responses will arrive in the same order as requested. + * + * If the button isn't verified, the data sent to callback will be null. + * + * @param bdaddr The bluetooth address. + * @param callback Callback for the response. + * @throws IOException + */ + public void getButtonInfo(final Bdaddr bdaddr, final GetButtonInfoResponseCallback callback) throws IOException { + if (callback == null) { + throw new IllegalArgumentException("callback is null"); + } + // Run on events thread to ensure ordering if multiple requests are issued at the same time + runOnHandleEventsThread(new TimerTask() { + @Override + public void run() throws IOException { + getButtonInfoResponseCallbackQueue.add(callback); + + CmdGetButtonInfo pkt = new CmdGetButtonInfo(); + pkt.bdaddr = bdaddr; + sendPacket(pkt); + } + }); + } + + /** + * Add a scanner. + * + * The scan will start directly once the scanner is added. + * + * @param buttonScanner + * @throws IOException + */ + public void addScanner(ButtonScanner buttonScanner) throws IOException { + if (buttonScanner == null) { + throw new IllegalArgumentException("buttonScanner is null"); + } + if (scanners.putIfAbsent(buttonScanner.scanId, buttonScanner) != null) { + throw new IllegalArgumentException("Button scanner already added"); + } + + CmdCreateScanner pkt = new CmdCreateScanner(); + pkt.scanId = buttonScanner.scanId; + sendPacket(pkt); + } + + /** + * Remove a scanner. + * + * @param buttonScanner The same scanner that was used in {@link #addScanner(ButtonScanner)} + * @throws IOException + */ + public void removeScanner(ButtonScanner buttonScanner) throws IOException { + if (buttonScanner == null) { + throw new IllegalArgumentException("buttonScanner is null"); + } + if (scanners.remove(buttonScanner.scanId) == null) { + throw new IllegalArgumentException("Button scanner was never added"); + } + + CmdRemoveScanner pkt = new CmdRemoveScanner(); + pkt.scanId = buttonScanner.scanId; + sendPacket(pkt); + } + + /** + * Add a scan wizard. + * + * The scan wizard will start directly once the scan wizard is added. + * + * @param scanWizard + * @throws IOException + */ + public void addScanWizard(ScanWizard scanWizard) throws IOException { + if (scanWizard == null) { + throw new IllegalArgumentException("scanWizard is null"); + } + if (scanWizards.putIfAbsent(scanWizard.scanWizardId, scanWizard) != null) { + throw new IllegalArgumentException("Scan wizard already added"); + } + + CmdCreateScanWizard pkt = new CmdCreateScanWizard(); + pkt.scanWizardId = scanWizard.scanWizardId; + sendPacket(pkt); + } + + /** + * Cancel a scan wizard. + * + * This will cancel an ongoing scan wizard. + * + * If cancelled due to this request, the result of the scan wizard will be WizardCancelledByUser. + * + * @param scanWizard The same scan wizard that was used in {@link #addScanWizard(ScanWizard)} + * @throws IOException + */ + public void cancelScanWizard(ScanWizard scanWizard) throws IOException { + if (scanWizard == null) { + throw new IllegalArgumentException("scanWizard is null"); + } + + CmdCancelScanWizard pkt = new CmdCancelScanWizard(); + pkt.scanWizardId = scanWizard.scanWizardId; + sendPacket(pkt); + } + + /** + * Adds a connection channel to a specific Flic button. + * + * This will start listening for a specific Flic button's connection and button events. + * Make sure the Flic is either in public mode (by holding it down for 7 seconds) or already verified before calling this method. + * + * The {@link ButtonConnectionChannel.Callbacks#onCreateConnectionChannelResponse} + * method will be called after this command has been received by the server. + * + * You may have as many connection channels as you wish for a specific Flic Button. + * + * @param channel + * @throws IOException + */ + public void addConnectionChannel(ButtonConnectionChannel channel) throws IOException { + if (channel == null) { + throw new IllegalArgumentException("channel is null"); + } + if (connectionChannels.putIfAbsent(channel.connId, channel) != null) { + throw new IllegalArgumentException("Connection channel already added"); + } + + synchronized (channel.lock) { + channel.client = this; + + CmdCreateConnectionChannel pkt = new CmdCreateConnectionChannel(); + pkt.connId = channel.connId; + pkt.bdaddr = channel.getBdaddr(); + pkt.latencyMode = channel.getLatencyMode(); + pkt.autoDisconnectTime = channel.getAutoDisconnectTime(); + sendPacket(pkt); + } + } + + /** + * Remove a connection channel. + * + * This will stop listening for new events for a specific connection channel that has previously been added. + * Note: The effect of this command will take place at the time the {@link ButtonConnectionChannel.Callbacks#onRemoved} event arrives. + * + * @param channel + * @throws IOException + */ + public void removeConnectionChannel(ButtonConnectionChannel channel) throws IOException { + if (channel == null) { + throw new IllegalArgumentException("channel is null"); + } + + CmdRemoveConnectionChannel pkt = new CmdRemoveConnectionChannel(); + pkt.connId = channel.connId; + sendPacket(pkt); + } + + /** + * Force disconnection or cancel pending connection of a specific Flic button. + * + * This removes all connection channels for all clients connected to the server for this specific Flic button. + * + * @param bdaddr + * @throws IOException + */ + public void forceDisconnect(Bdaddr bdaddr) throws IOException { + if (bdaddr == null) { + throw new IllegalArgumentException("bdaddr is null"); + } + + CmdForceDisconnect pkt = new CmdForceDisconnect(); + pkt.bdaddr = bdaddr; + sendPacket(pkt); + } + + /** + * Delete a button. + * + * @param bdaddr + * @throws IOException + */ + public void deleteButton(Bdaddr bdaddr) throws IOException { + if (bdaddr == null) { + throw new IllegalArgumentException("bdaddr is null"); + } + + CmdDeleteButton pkt = new CmdDeleteButton(); + pkt.bdaddr = bdaddr; + sendPacket(pkt); + } + + /** + * Add a battery status listener. + * + * @param listener + * @throws IOException + */ + public void addBatteryStatusListener(BatteryStatusListener listener) throws IOException { + if (listener == null) { + throw new IllegalArgumentException("listener is null"); + } + if (batteryStatusListeners.putIfAbsent(listener.listenerId, listener) != null) { + throw new IllegalArgumentException("Battery status listener already added"); + } + + CmdCreateBatteryStatusListener pkt = new CmdCreateBatteryStatusListener(); + pkt.listenerId = listener.listenerId; + pkt.bdaddr = listener.getBdaddr(); + sendPacket(pkt); + } + + /** + * Remove a battery status listener + * + * @param listener + * @throws IOException + */ + public void removeBatteryStatusListener(BatteryStatusListener listener) throws IOException { + if (listener == null) { + throw new IllegalArgumentException("buttonScanner is null"); + } + if (batteryStatusListeners.remove(listener.listenerId) == null) { + throw new IllegalArgumentException("Battery status listener was never added"); + } + + CmdRemoveBatteryStatusListener pkt = new CmdRemoveBatteryStatusListener(); + pkt.listenerId = listener.listenerId; + sendPacket(pkt); + } + + void sendPacket(CommandPacket packet) throws IOException { + byte[] bytes = packet.construct(); + synchronized (socketOutputStream) { + socketOutputStream.write(bytes); + } + } + + /** + * Set a timer. + * + * This timer task will run after the specified timeoutMillis on the thread that handles the events. + * + * @param timeoutMillis + * @param timerTask + * @throws IOException + */ + public void setTimer(int timeoutMillis, TimerTask timerTask) throws IOException { + long pointInTime = System.nanoTime() + timeoutMillis * 1000000L; + while (timers.putIfAbsent(pointInTime, timerTask) != null) { + pointInTime++; + } + if (handleEventsThread != Thread.currentThread()) { + CmdPing pkt = new CmdPing(); + pkt.pingId = 0; + sendPacket(pkt); + } + } + + /** + * Run a task on the thread that handles the events. + * + * @param task + * @throws IOException + */ + public void runOnHandleEventsThread(TimerTask task) throws IOException { + if (handleEventsThread == Thread.currentThread()) { + task.run(); + } else { + setTimer(0, task); + } + } + + /** + * Start the main loop for this client. + * + * This method will not return until the socket has been closed. + * Once it has returned, any use of this FlicClient is illegal. + * + * @throws IOException + */ + public void handleEvents() throws IOException { + handleEventsThread = Thread.currentThread(); + while (!Thread.currentThread().isInterrupted()) { + Map.Entry firstTimer = timers.firstEntry(); + long timeout = 0; + if (firstTimer != null) { + timeout = firstTimer.getKey() - System.nanoTime(); + if (timeout <= 0) { + timers.remove(firstTimer.getKey(), firstTimer.getValue()); + firstTimer.getValue().run(); + continue; + } + } + + if (socket.isClosed()) { + break; + } + + int len0; + socket.setSoTimeout((int)(timeout / 1000000)); + try { + len0 = socketInputStream.read(); + } catch (SocketTimeoutException e) { + continue; + } + int len1 = socketInputStream.read(); + int len = len0 | (len1 << 8); + if ((len >> 16) == -1) { + break; + } + if (len == 0) { + continue; + } + byte[] pkt = new byte[len]; + + int pos = 0; + while (pos < len) { + int nbytes = socketInputStream.read(pkt, pos, len - pos); + if (nbytes == -1) { + break; + } + pos += nbytes; + } + if (len == 1) { + continue; + } + dispatchPacket(pkt); + } + socket.close(); + } + + private void dispatchPacket(byte[] packet) throws IOException { + int opcode = packet[0]; + switch (opcode) { + case EventPacket.EVT_ADVERTISEMENT_PACKET_OPCODE: { + EvtAdvertisementPacket pkt = new EvtAdvertisementPacket(); + pkt.parse(packet); + ButtonScanner scanner = scanners.get(pkt.scanId); + if (scanner != null) { + scanner.onAdvertisementPacket(pkt.addr, pkt.name, pkt.rssi, pkt.isPrivate, pkt.alreadyVerified, pkt.alreadyConnectedToThisDevice, pkt.alreadyConnectedToOtherDevice); + } + break; + } + case EventPacket.EVT_CREATE_CONNECTION_CHANNEL_RESPONSE_OPCODE: { + EvtCreateConnectionChannelResponse pkt = new EvtCreateConnectionChannelResponse(); + pkt.parse(packet); + ButtonConnectionChannel channel = connectionChannels.get(pkt.connId); + if (channel != null) { + if (pkt.connectionChannelError != CreateConnectionChannelError.NoError) { + connectionChannels.remove(channel.connId); + } + channel.callbacks.onCreateConnectionChannelResponse(channel, pkt.connectionChannelError, pkt.connectionStatus); + } + break; + } + case EventPacket.EVT_CONNECTION_STATUS_CHANGED_OPCODE: { + EvtConnectionStatusChanged pkt = new EvtConnectionStatusChanged(); + pkt.parse(packet); + ButtonConnectionChannel channel = connectionChannels.get(pkt.connId); + if (channel != null) { + channel.callbacks.onConnectionStatusChanged(channel, pkt.connectionStatus, pkt.disconnectReason); + } + break; + } + case EventPacket.EVT_CONNECTION_CHANNEL_REMOVED_OPCODE: { + EvtConnectionChannelRemoved pkt = new EvtConnectionChannelRemoved(); + pkt.parse(packet); + ButtonConnectionChannel channel = connectionChannels.get(pkt.connId); + if (channel != null) { + connectionChannels.remove(channel.connId); + channel.callbacks.onRemoved(channel, pkt.removedReason); + } + break; + } + case EventPacket.EVT_BUTTON_UP_OR_DOWN_OPCODE: + case EventPacket.EVT_BUTTON_CLICK_OR_HOLD_OPCODE: + case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE: + case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE: { + EvtButtonEvent pkt = new EvtButtonEvent(); + pkt.parse(packet); + ButtonConnectionChannel channel = connectionChannels.get(pkt.connId); + if (channel != null) { + if (opcode == EventPacket.EVT_BUTTON_UP_OR_DOWN_OPCODE) { + channel.callbacks.onButtonUpOrDown(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff); + } else if (opcode == EventPacket.EVT_BUTTON_CLICK_OR_HOLD_OPCODE) { + channel.callbacks.onButtonClickOrHold(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff); + } else if (opcode == EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE) { + channel.callbacks.onButtonSingleOrDoubleClick(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff); + } else if (opcode == EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE) { + channel.callbacks.onButtonSingleOrDoubleClickOrHold(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff); + } + } + break; + } + case EventPacket.EVT_NEW_VERIFIED_BUTTON_OPCODE: { + EvtNewVerifiedButton pkt = new EvtNewVerifiedButton(); + pkt.parse(packet); + GeneralCallbacks gc = generalCallbacks; + if (gc != null) { + gc.onNewVerifiedButton(pkt.bdaddr); + } + break; + } + case EventPacket.EVT_GET_INFO_RESPONSE_OPCODE: { + EvtGetInfoResponse pkt = new EvtGetInfoResponse(); + pkt.parse(packet); + getInfoResponseCallbackQueue.remove().onGetInfoResponse(pkt.bluetoothControllerState, pkt.myBdAddr, pkt.myBdAddrType, pkt.maxPendingConnections, pkt.maxConcurrentlyConnectedButtons, pkt.currentPendingConnections, pkt.currentlyNoSpaceForNewConnections, pkt.bdAddrOfVerifiedButtons); + break; + } + case EventPacket.EVT_NO_SPACE_FOR_NEW_CONNECTION_OPCODE: { + EvtNoSpaceForNewConnection pkt = new EvtNoSpaceForNewConnection(); + pkt.parse(packet); + GeneralCallbacks gc = generalCallbacks; + if (gc != null) { + gc.onNoSpaceForNewConnection(pkt.maxConcurrentlyConnectedButtons); + } + break; + } + case EventPacket.EVT_GOT_SPACE_FOR_NEW_CONNECTION_OPCODE: { + EvtGotSpaceForNewConnection pkt = new EvtGotSpaceForNewConnection(); + pkt.parse(packet); + GeneralCallbacks gc = generalCallbacks; + if (gc != null) { + gc.onGotSpaceForNewConnection(pkt.maxConcurrentlyConnectedButtons); + } + break; + } + case EventPacket.EVT_BLUETOOTH_CONTROLLER_STATE_CHANGE_OPCODE: { + EvtBluetoothControllerStateChange pkt = new EvtBluetoothControllerStateChange(); + pkt.parse(packet); + GeneralCallbacks gc = generalCallbacks; + if (gc != null) { + gc.onBluetoothControllerStateChange(pkt.state); + } + break; + } + case EventPacket.EVT_GET_BUTTON_INFO_RESPONSE_OPCODE: { + EvtGetButtonInfoResponse pkt = new EvtGetButtonInfoResponse(); + pkt.parse(packet); + getButtonInfoResponseCallbackQueue.remove().onGetButtonInfoResponse(pkt.bdaddr, pkt.uuid, pkt.color, pkt.serialNumber); + break; + } + case EventPacket.EVT_SCAN_WIZARD_FOUND_PRIVATE_BUTTON_OPCODE: { + EvtScanWizardFoundPrivateButton pkt = new EvtScanWizardFoundPrivateButton(); + pkt.parse(packet); + ScanWizard wizard = scanWizards.get(pkt.scanWizardId); + if (wizard != null) { + wizard.onFoundPrivateButton(); + } + break; + } + case EventPacket.EVT_SCAN_WIZARD_FOUND_PUBLIC_BUTTON_OPCODE: { + EvtScanWizardFoundPublicButton pkt = new EvtScanWizardFoundPublicButton(); + pkt.parse(packet); + ScanWizard wizard = scanWizards.get(pkt.scanWizardId); + if (wizard != null) { + wizard.bdaddr = pkt.addr; + wizard.name = pkt.name; + wizard.onFoundPublicButton(wizard.bdaddr, wizard.name); + } + break; + } + case EventPacket.EVT_SCAN_WIZARD_BUTTON_CONNECTED_OPCODE: { + EvtScanWizardButtonConnected pkt = new EvtScanWizardButtonConnected(); + pkt.parse(packet); + ScanWizard wizard = scanWizards.get(pkt.scanWizardId); + if (wizard != null) { + wizard.onButtonConnected(wizard.bdaddr, wizard.name); + } + break; + } + case EventPacket.EVT_SCAN_WIZARD_COMPLETED_OPCODE: { + EvtScanWizardCompleted pkt = new EvtScanWizardCompleted(); + pkt.parse(packet); + ScanWizard wizard = scanWizards.get(pkt.scanWizardId); + scanWizards.remove(pkt.scanWizardId); + if (wizard != null) { + Bdaddr bdaddr = wizard.bdaddr; + String name = wizard.name; + wizard.bdaddr = null; + wizard.name = null; + wizard.onCompleted(pkt.result, bdaddr, name); + } + break; + } + case EventPacket.EVT_BUTTON_DELETED_OPCODE: { + EvtButtonDeleted pkt = new EvtButtonDeleted(); + pkt.parse(packet); + GeneralCallbacks gc = generalCallbacks; + if (gc != null) { + gc.onButtonDeleted(pkt.bdaddr, pkt.deletedByThisClient); + } + break; + } + case EventPacket.EVT_BATTERY_STATUS_OPCODE: { + EvtBatteryStatus pkt = new EvtBatteryStatus(); + pkt.parse(packet); + BatteryStatusListener listener = batteryStatusListeners.get(pkt.listenerId); + if (listener != null) { + listener.callbacks.onBatteryStatus(listener.getBdaddr(), pkt.batteryPercentage, pkt.timestamp); + } + break; + } + } + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GeneralCallbacks.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GeneralCallbacks.java new file mode 100644 index 0000000000000..77399d7aa382b --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GeneralCallbacks.java @@ -0,0 +1,28 @@ +package io.flic.fliclib.javaclient; + +import io.flic.fliclib.javaclient.enums.BluetoothControllerState; + +import java.io.IOException; + +/** + * GeneralCallbacks. + * + * See the protocol specification for further details. + */ +public class GeneralCallbacks { + public void onNewVerifiedButton(Bdaddr bdaddr) throws IOException { + + } + public void onNoSpaceForNewConnection(int maxConcurrentlyConnectedButtons) throws IOException { + + } + public void onGotSpaceForNewConnection(int maxConcurrentlyConnectedButtons) throws IOException { + + } + public void onBluetoothControllerStateChange(BluetoothControllerState state) throws IOException { + + } + public void onButtonDeleted(Bdaddr bdaddr, boolean deletedByThisClient) throws IOException { + + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetButtonInfoResponseCallback.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetButtonInfoResponseCallback.java new file mode 100644 index 0000000000000..740f8dd0b4204 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetButtonInfoResponseCallback.java @@ -0,0 +1,18 @@ +package io.flic.fliclib.javaclient; + +/** + * GetButtonInfoResponseCallback. + * + * Used in {@link FlicClient#getButtonInfo(Bdaddr, GetButtonInfoResponseCallback)}. + */ +public abstract class GetButtonInfoResponseCallback { + /** + * Called upon response. + * + * @param bdaddr Bluetooth address + * @param uuid Uuid of button, might be null if unknown + * @param color Color of button, might be null if unknown + * @param serialNumber Serial number of the button, will be null if the button is not found + */ + public abstract void onGetButtonInfoResponse(Bdaddr bdaddr, String uuid, String color, String serialNumber); +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetInfoResponseCallback.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetInfoResponseCallback.java new file mode 100644 index 0000000000000..642a356658346 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/GetInfoResponseCallback.java @@ -0,0 +1,19 @@ +package io.flic.fliclib.javaclient; + +import io.flic.fliclib.javaclient.enums.BdAddrType; +import io.flic.fliclib.javaclient.enums.BluetoothControllerState; + +import java.io.IOException; + +/** + * GetInfoResponseCallback. + * + * Used in {@link FlicClient#getInfo(GetInfoResponseCallback)}. + */ +public abstract class GetInfoResponseCallback { + public abstract void onGetInfoResponse(BluetoothControllerState bluetoothControllerState, Bdaddr myBdAddr, + BdAddrType myBdAddrType, int maxPendingConnections, + int maxConcurrentlyConnectedButtons, int currentPendingConnections, + boolean currentlyNoSpaceForNewConnection, + Bdaddr[] verifiedButtons) throws IOException; +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Packets.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Packets.java new file mode 100644 index 0000000000000..7cdcf011c9d5d --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Packets.java @@ -0,0 +1,455 @@ +package io.flic.fliclib.javaclient; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +import io.flic.fliclib.javaclient.enums.*; + +/** + * Flic Protocol Packets + */ + +abstract class CommandPacket { + protected int opcode; + + public final byte[] construct() { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + write(stream); + } catch (IOException e) { + } + byte[] res = new byte[3 + stream.size()]; + res[0] = (byte)(1 + stream.size()); + res[1] = (byte)((1 + stream.size()) >> 8); + res[2] = (byte)opcode; + System.arraycopy(stream.toByteArray(), 0, res, 3, stream.size()); + return res; + } + + abstract protected void write(OutputStream stream) throws IOException; +} + +class CmdGetInfo extends CommandPacket { + @Override + protected void write(OutputStream stream) { + opcode = 0; + } +} + +class CmdCreateScanner extends CommandPacket { + public int scanId; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 1; + StreamUtils.writeInt32(stream, scanId); + } +} + +class CmdRemoveScanner extends CommandPacket { + public int scanId; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 2; + StreamUtils.writeInt32(stream, scanId); + } +} + +class CmdCreateConnectionChannel extends CommandPacket { + public int connId; + public Bdaddr bdaddr; + public LatencyMode latencyMode; + public short autoDisconnectTime; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 3; + StreamUtils.writeInt32(stream, connId); + StreamUtils.writeBdaddr(stream, bdaddr); + StreamUtils.writeEnum(stream, latencyMode); + StreamUtils.writeInt16(stream, autoDisconnectTime); + } +} + +class CmdRemoveConnectionChannel extends CommandPacket { + public int connId; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 4; + StreamUtils.writeInt32(stream, connId); + } +} + +class CmdForceDisconnect extends CommandPacket { + public Bdaddr bdaddr; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 5; + StreamUtils.writeBdaddr(stream, bdaddr); + } +} + +class CmdChangeModeParameters extends CommandPacket { + public int connId; + public LatencyMode latencyMode; + public short autoDisconnectTime; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 6; + StreamUtils.writeInt32(stream, connId); + StreamUtils.writeEnum(stream, latencyMode); + StreamUtils.writeInt16(stream, autoDisconnectTime); + } +} + +class CmdPing extends CommandPacket { + public int pingId; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 7; + StreamUtils.writeInt32(stream, pingId); + } +} + +class CmdGetButtonInfo extends CommandPacket { + public Bdaddr bdaddr; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 8; + StreamUtils.writeBdaddr(stream, bdaddr); + } +} + +class CmdCreateScanWizard extends CommandPacket { + public int scanWizardId; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 9; + StreamUtils.writeInt32(stream, scanWizardId); + } +} + +class CmdCancelScanWizard extends CommandPacket { + public int scanWizardId; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 10; + StreamUtils.writeInt32(stream, scanWizardId); + } +} + +class CmdDeleteButton extends CommandPacket { + public Bdaddr bdaddr; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 11; + StreamUtils.writeBdaddr(stream, bdaddr); + } +} + +class CmdCreateBatteryStatusListener extends CommandPacket { + public int listenerId; + public Bdaddr bdaddr; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 12; + StreamUtils.writeInt32(stream, listenerId); + StreamUtils.writeBdaddr(stream, bdaddr); + } +} + +class CmdRemoveBatteryStatusListener extends CommandPacket { + public int listenerId; + + @Override + protected void write(OutputStream stream) throws IOException { + opcode = 13; + StreamUtils.writeInt32(stream, listenerId); + } +} + +abstract class EventPacket { + public static final int EVT_ADVERTISEMENT_PACKET_OPCODE = 0; + public static final int EVT_CREATE_CONNECTION_CHANNEL_RESPONSE_OPCODE = 1; + public static final int EVT_CONNECTION_STATUS_CHANGED_OPCODE = 2; + public static final int EVT_CONNECTION_CHANNEL_REMOVED_OPCODE = 3; + public static final int EVT_BUTTON_UP_OR_DOWN_OPCODE = 4; + public static final int EVT_BUTTON_CLICK_OR_HOLD_OPCODE = 5; + public static final int EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE = 6; + public static final int EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE = 7; + public static final int EVT_NEW_VERIFIED_BUTTON_OPCODE = 8; + public static final int EVT_GET_INFO_RESPONSE_OPCODE = 9; + public static final int EVT_NO_SPACE_FOR_NEW_CONNECTION_OPCODE = 10; + public static final int EVT_GOT_SPACE_FOR_NEW_CONNECTION_OPCODE = 11; + public static final int EVT_BLUETOOTH_CONTROLLER_STATE_CHANGE_OPCODE = 12; + public static final int EVT_PING_RESPONSE_OPCODE = 13; + public static final int EVT_GET_BUTTON_INFO_RESPONSE_OPCODE = 14; + public static final int EVT_SCAN_WIZARD_FOUND_PRIVATE_BUTTON_OPCODE = 15; + public static final int EVT_SCAN_WIZARD_FOUND_PUBLIC_BUTTON_OPCODE = 16; + public static final int EVT_SCAN_WIZARD_BUTTON_CONNECTED_OPCODE = 17; + public static final int EVT_SCAN_WIZARD_COMPLETED_OPCODE = 18; + public static final int EVT_BUTTON_DELETED_OPCODE = 19; + public static final int EVT_BATTERY_STATUS_OPCODE = 20; + + public void parse(byte[] arr) { + InputStream stream = new ByteArrayInputStream(arr); + try { + stream.skip(1); + parseInternal(stream); + } catch(IOException e) { + } + } + + abstract protected void parseInternal(InputStream stream) throws IOException; +} + +class EvtAdvertisementPacket extends EventPacket { + public int scanId; + public Bdaddr addr; + public String name; + public int rssi; + public boolean isPrivate; + public boolean alreadyVerified; + public boolean alreadyConnectedToThisDevice; + public boolean alreadyConnectedToOtherDevice; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + scanId = StreamUtils.getInt32(stream); + addr = StreamUtils.getBdaddr(stream); + name = StreamUtils.getString(stream, 16); + rssi = StreamUtils.getInt8(stream); + isPrivate = StreamUtils.getBoolean(stream); + alreadyVerified = StreamUtils.getBoolean(stream); + alreadyConnectedToThisDevice = StreamUtils.getBoolean(stream); + alreadyConnectedToOtherDevice = StreamUtils.getBoolean(stream); + } +} + +class EvtCreateConnectionChannelResponse extends EventPacket { + public int connId; + public CreateConnectionChannelError connectionChannelError; + public ConnectionStatus connectionStatus; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + connId = StreamUtils.getInt32(stream); + connectionChannelError = CreateConnectionChannelError.values()[StreamUtils.getUInt8(stream)]; + connectionStatus = ConnectionStatus.values()[StreamUtils.getUInt8(stream)]; + } +} + +class EvtConnectionStatusChanged extends EventPacket { + public int connId; + public ConnectionStatus connectionStatus; + public DisconnectReason disconnectReason; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + connId = StreamUtils.getInt32(stream); + connectionStatus = ConnectionStatus.values()[StreamUtils.getUInt8(stream)]; + disconnectReason = DisconnectReason.values()[StreamUtils.getUInt8(stream)]; + } +} + +class EvtConnectionChannelRemoved extends EventPacket { + public int connId; + public RemovedReason removedReason; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + connId = StreamUtils.getInt32(stream); + removedReason = RemovedReason.values()[StreamUtils.getUInt8(stream)]; + } +} + +class EvtButtonEvent extends EventPacket { + public int connId; + public ClickType clickType; + public boolean wasQueued; + public int timeDiff; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + connId = StreamUtils.getInt32(stream); + clickType = ClickType.values()[StreamUtils.getUInt8(stream)]; + wasQueued = StreamUtils.getBoolean(stream); + timeDiff = StreamUtils.getInt32(stream); + } +} + +class EvtNewVerifiedButton extends EventPacket { + public Bdaddr bdaddr; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + bdaddr = StreamUtils.getBdaddr(stream); + } +} + +class EvtGetInfoResponse extends EventPacket { + public BluetoothControllerState bluetoothControllerState; + public Bdaddr myBdAddr; + public BdAddrType myBdAddrType; + public int maxPendingConnections; + public int maxConcurrentlyConnectedButtons; + public int currentPendingConnections; + public boolean currentlyNoSpaceForNewConnections; + public Bdaddr[] bdAddrOfVerifiedButtons; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + bluetoothControllerState = BluetoothControllerState.values()[StreamUtils.getUInt8(stream)]; + myBdAddr = StreamUtils.getBdaddr(stream); + myBdAddrType = BdAddrType.values()[StreamUtils.getUInt8(stream)]; + maxPendingConnections = StreamUtils.getUInt8(stream); + maxConcurrentlyConnectedButtons = StreamUtils.getInt16(stream); + currentPendingConnections = StreamUtils.getUInt8(stream); + currentlyNoSpaceForNewConnections = StreamUtils.getBoolean(stream); + int nbVerifiedButtons = StreamUtils.getUInt16(stream); + bdAddrOfVerifiedButtons = new Bdaddr[nbVerifiedButtons]; + for (int i = 0; i < nbVerifiedButtons; i++) { + bdAddrOfVerifiedButtons[i] = StreamUtils.getBdaddr(stream); + } + } +} + +class EvtNoSpaceForNewConnection extends EventPacket { + public int maxConcurrentlyConnectedButtons; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + maxConcurrentlyConnectedButtons = StreamUtils.getUInt8(stream); + } +} + +class EvtGotSpaceForNewConnection extends EventPacket { + public int maxConcurrentlyConnectedButtons; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + maxConcurrentlyConnectedButtons = StreamUtils.getUInt8(stream); + } +} + +class EvtBluetoothControllerStateChange extends EventPacket { + public BluetoothControllerState state; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + state = BluetoothControllerState.values()[StreamUtils.getUInt8(stream)]; + } +} + +class EvtGetButtonInfoResponse extends EventPacket { + public Bdaddr bdaddr; + public String uuid; + public String color; + public String serialNumber; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + bdaddr = StreamUtils.getBdaddr(stream); + byte[] uuidBytes = StreamUtils.getByteArr(stream, 16); + StringBuilder sb = new StringBuilder(32); + for (int i = 0; i < 16; i++) { + sb.append(String.format("%02x", uuidBytes[i])); + } + uuid = sb.toString(); + if (uuid.equals("00000000000000000000000000000000")) { + uuid = null; + } + color = StreamUtils.getString(stream, 16); + if (color.isEmpty()) { + color = null; + } + serialNumber = StreamUtils.getString(stream, 16); + if (serialNumber.isEmpty()) { + serialNumber = null; + } + } +} + +class EvtScanWizardFoundPrivateButton extends EventPacket { + public int scanWizardId; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + scanWizardId = StreamUtils.getInt32(stream); + } +} + +class EvtScanWizardFoundPublicButton extends EventPacket { + public int scanWizardId; + public Bdaddr addr; + public String name; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + scanWizardId = StreamUtils.getInt32(stream); + addr = StreamUtils.getBdaddr(stream); + int nameLen = StreamUtils.getUInt8(stream); + byte[] bytes = new byte[nameLen]; + for (int i = 0; i < nameLen; i++) { + bytes[i] = (byte)stream.read(); + } + for (int i = nameLen; i < 16; i++) { + stream.skip(1); + } + name = new String(bytes, StandardCharsets.UTF_8); + } +} + +class EvtScanWizardButtonConnected extends EventPacket { + public int scanWizardId; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + scanWizardId = StreamUtils.getInt32(stream); + } +} + +class EvtScanWizardCompleted extends EventPacket { + public int scanWizardId; + public ScanWizardResult result; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + scanWizardId = StreamUtils.getInt32(stream); + result = ScanWizardResult.values()[StreamUtils.getUInt8(stream)]; + } +} + +class EvtButtonDeleted extends EventPacket { + public Bdaddr bdaddr; + public boolean deletedByThisClient; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + bdaddr = StreamUtils.getBdaddr(stream); + deletedByThisClient = StreamUtils.getBoolean(stream); + } +} + +class EvtBatteryStatus extends EventPacket { + public int listenerId; + public int batteryPercentage; + public long timestamp; + + @Override + protected void parseInternal(InputStream stream) throws IOException { + listenerId = StreamUtils.getInt32(stream); + batteryPercentage = StreamUtils.getInt8(stream); + timestamp = StreamUtils.getInt64(stream); + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ScanWizard.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ScanWizard.java new file mode 100644 index 0000000000000..3e573f6bbe18d --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/ScanWizard.java @@ -0,0 +1,64 @@ +package io.flic.fliclib.javaclient; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import io.flic.fliclib.javaclient.enums.ScanWizardResult; + +/** + * Scan wizard class. + * + * This class will scan for a new button and pair it automatically. + * There are internal timeouts that make sure operations don't take too long time. + * + * Inherit this class and override the methods. + * Then add this scan wizard to a {@link FlicClient} using {@link FlicClient#addScanWizard(ScanWizard)} to start it. + * You can cancel by calling {@link FlicClient#cancelScanWizard(ScanWizard)}. + */ +public abstract class ScanWizard { + private static AtomicInteger nextId = new AtomicInteger(); + int scanWizardId = nextId.getAndIncrement(); + Bdaddr bdaddr; + String name; + + /** + * This will be called once if a private button is found. + * + * Tell the user to hold down the button for 7 seconds in order to make it public. + * + */ + public abstract void onFoundPrivateButton() throws IOException; + + /** + * This will be called once a public button is found. + * + * Now a connection attempt will be made to the device in order to pair and verify it. + * + * @param bdaddr Bluetooth Device Address + * @param name Advertising name + */ + public abstract void onFoundPublicButton(Bdaddr bdaddr, String name) throws IOException; + + /** + * This will be called once the bluetooth connection has been established. + * + * Now a pair attempt will be made. + * + * @param bdaddr Bluetooth Device Address + * @param name Advertising name + */ + public abstract void onButtonConnected(Bdaddr bdaddr, String name) throws IOException; + + /** + * Scan wizard completed. + * + * If the result is success, you can now create a connection channel to the button. + * + * The ScanWizard is now detached from the FlicClient and can now be recycled. + * + * @param result Result of the scan wizard + * @param bdaddr Bluetooth Device Address or null, depending on if {@link #onFoundPublicButton} has been called or not + * @param name Advertising name or null, depending on if {@link #onFoundPublicButton} has been called or not + */ + public abstract void onCompleted(ScanWizardResult result, Bdaddr bdaddr, String name) throws IOException; +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/StreamUtils.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/StreamUtils.java new file mode 100644 index 0000000000000..0db0ededd8e73 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/StreamUtils.java @@ -0,0 +1,81 @@ +package io.flic.fliclib.javaclient; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +class StreamUtils { + public static boolean getBoolean(InputStream stream) throws IOException { + return stream.read() != 0; + } + public static int getUInt8(InputStream stream) throws IOException { + return stream.read(); + } + + public static int getInt8(InputStream stream) throws IOException { + return (byte)stream.read(); + } + + public static int getUInt16(InputStream stream) throws IOException { + return stream.read() | (stream.read() << 8); + } + + public static int getInt16(InputStream stream) throws IOException { + return (short)getUInt16(stream); + } + + public static int getInt32(InputStream stream) throws IOException { + return stream.read() | (stream.read() << 8) | (stream.read() << 16) | (stream.read() << 24); + } + + public static long getInt64(InputStream stream) throws IOException { + return (getInt32(stream) & 0xffffffffL) | ((long)getInt32(stream) << 32); + } + + public static Bdaddr getBdaddr(InputStream stream) throws IOException { + return new Bdaddr(stream); + } + + public static byte[] getByteArr(InputStream stream, int len) throws IOException { + byte[] arr = new byte[len]; + for (int i = 0; i < len; i++) { + arr[i] = (byte)stream.read(); + } + return arr; + } + + public static String getString(InputStream stream, int maxlen) throws IOException { + int len = getInt8(stream); + byte[] arr = new byte[len]; + for (int i = 0; i < len; i++) { + arr[i] = (byte)stream.read(); + } + for (int i = len; i < maxlen; i++) { + stream.skip(1); + } + return new String(arr, StandardCharsets.UTF_8); + } + + public static void writeEnum(OutputStream stream, Enum enumValue) throws IOException { + stream.write(enumValue.ordinal()); + } + + public static void writeInt8(OutputStream stream, int v) throws IOException { + stream.write(v); + } + + public static void writeInt16(OutputStream stream, int v) throws IOException { + stream.write(v & 0xff); + stream.write(v >> 8); + } + + public static void writeInt32(OutputStream stream, int v) throws IOException { + writeInt16(stream, v); + writeInt16(stream, v >> 16); + } + + public static void writeBdaddr(OutputStream stream, Bdaddr addr) throws IOException { + stream.write(addr.getBytes()); + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/TimerTask.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/TimerTask.java new file mode 100644 index 0000000000000..8ed0919d2edf6 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/TimerTask.java @@ -0,0 +1,14 @@ +package io.flic.fliclib.javaclient; + +import java.io.IOException; + +/** + * TimerTask. + * + * Use this interface instead of {@link Runnable} to avoid having to deal with IOExceptions. + * Invocations of the run method on this interface from the {@link FlicClient} will propagate IOExceptions to the caller of {@link FlicClient#handleEvents()}. + * + */ +public interface TimerTask { + void run() throws IOException; +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BdAddrType.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BdAddrType.java new file mode 100644 index 0000000000000..650e719ce8104 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BdAddrType.java @@ -0,0 +1,9 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum BdAddrType { + PublicBdAddrType, + RandomBdAddrType +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BluetoothControllerState.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BluetoothControllerState.java new file mode 100644 index 0000000000000..24ee268192204 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/BluetoothControllerState.java @@ -0,0 +1,10 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum BluetoothControllerState { + Detached, + Resetting, + Attached +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ClickType.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ClickType.java new file mode 100644 index 0000000000000..d1a767b705b79 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ClickType.java @@ -0,0 +1,13 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum ClickType { + ButtonDown, + ButtonUp, + ButtonClick, + ButtonSingleClick, + ButtonDoubleClick, + ButtonHold +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ConnectionStatus.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ConnectionStatus.java new file mode 100644 index 0000000000000..ad0673ad5fc26 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ConnectionStatus.java @@ -0,0 +1,10 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum ConnectionStatus { + Disconnected, + Connected, + Ready +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/CreateConnectionChannelError.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/CreateConnectionChannelError.java new file mode 100644 index 0000000000000..aa78274ecd547 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/CreateConnectionChannelError.java @@ -0,0 +1,9 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum CreateConnectionChannelError { + NoError, + MaxPendingConnectionsReached +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/DisconnectReason.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/DisconnectReason.java new file mode 100644 index 0000000000000..a875a725ca68b --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/DisconnectReason.java @@ -0,0 +1,11 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum DisconnectReason { + Unspecified, + ConnectionEstablishmentFailed, + TimedOut, + BondingKeysMismatch +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/LatencyMode.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/LatencyMode.java new file mode 100644 index 0000000000000..135693f5d2983 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/LatencyMode.java @@ -0,0 +1,10 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum LatencyMode { + NormalLatency, + LowLatency, + HighLatency +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/RemovedReason.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/RemovedReason.java new file mode 100644 index 0000000000000..48bc410f6bb68 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/RemovedReason.java @@ -0,0 +1,22 @@ +package io.flic.fliclib.javaclient.enums; + +/** + * Created by Emil on 2016-05-03. + */ +public enum RemovedReason { + RemovedByThisClient, + ForceDisconnectedByThisClient, + ForceDisconnectedByOtherClient, + + ButtonIsPrivate, + VerifyTimeout, + InternetBackendError, + InvalidData, + + CouldntLoadDevice, + + DeletedByThisClient, + DeletedByOtherClient, + ButtonBelongsToOtherPartner, + DeletedFromButton +} diff --git a/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ScanWizardResult.java b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ScanWizardResult.java new file mode 100644 index 0000000000000..e31582a98a182 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/enums/ScanWizardResult.java @@ -0,0 +1,13 @@ +package io.flic.fliclib.javaclient.enums; + +public enum ScanWizardResult { + WizardSuccess, + WizardCancelledByUser, + WizardFailedTimeout, + WizardButtonIsPrivate, + WizardBluetoothUnavailable, + WizardInternetBackendError, + WizardInvalidData, + WizardButtonBelongsToOtherPartner, + WizardButtonAlreadyConnectedToOtherDevice +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/feature/feature.xml b/bundles/org.openhab.binding.flicbutton/src/main/feature/feature.xml new file mode 100644 index 0000000000000..45d90679a3968 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.flicbutton/${project.version} + + diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonBindingConstants.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonBindingConstants.java new file mode 100644 index 0000000000000..0aa1773aff3b8 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonBindingConstants.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.CommonTriggerEvents; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link FlicButtonBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Patrick Fink - Initial contribution + */ +@NonNullByDefault +public class FlicButtonBindingConstants { + + public static final String BINDING_ID = "flicbutton"; + + // List of all Thing Type UIDs + public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "flicd-bridge"); + public static final ThingTypeUID FLICBUTTON_THING_TYPE = new ThingTypeUID(BINDING_ID, "button"); + + public static final Set BRIDGE_THING_TYPES_UIDS = Collections.singleton(BRIDGE_THING_TYPE); + public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(FLICBUTTON_THING_TYPE); + + // List of all configuration options + public static final String CONFIG_HOST_NAME = "hostname"; + public static final String CONFIG_PORT = "port"; + public static final String CONFIG_ADDRESS = "address"; + + // List of all Channel ids + public static final String CHANNEL_ID_RAWBUTTON_EVENTS = "rawbutton"; + public static final String CHANNEL_ID_BUTTON_EVENTS = "button"; + public static final String CHANNEL_ID_BATTERY_LEVEL = "battery-level"; + + // Other stuff + public static final int BUTTON_OFFLINE_GRACE_PERIOD_SECONDS = 60; + + public static final Map FLIC_OPENHAB_TRIGGER_EVENT_MAP = Collections + .unmodifiableMap(new HashMap() { + { + put("ButtonSingleClick", CommonTriggerEvents.SHORT_PRESSED); + put("ButtonDoubleClick", CommonTriggerEvents.DOUBLE_PRESSED); + put("ButtonHold", CommonTriggerEvents.LONG_PRESSED); + put("ButtonDown", CommonTriggerEvents.PRESSED); + put("ButtonUp", CommonTriggerEvents.RELEASED); + } + }); +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonHandlerFactory.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonHandlerFactory.java new file mode 100644 index 0000000000000..5e44ab7fcc0a0 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/FlicButtonHandlerFactory.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.flicbutton.internal.discovery.FlicButtonDiscoveryService; +import org.openhab.binding.flicbutton.internal.discovery.FlicSimpleclientDiscoveryServiceImpl; +import org.openhab.binding.flicbutton.internal.handler.FlicButtonHandler; +import org.openhab.binding.flicbutton.internal.handler.FlicDaemonBridgeHandler; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link FlicButtonHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Patrick Fink - Initial contribution + */ +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.flicbutton") +@NonNullByDefault +public class FlicButtonHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Stream + .concat(FlicButtonBindingConstants.BRIDGE_THING_TYPES_UIDS.stream(), + FlicButtonBindingConstants.SUPPORTED_THING_TYPES_UIDS.stream()) + .collect(Collectors.toSet()); + private final Map> discoveryServiceRegs = new HashMap<>(); + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + @Nullable + protected ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(FlicButtonBindingConstants.FLICBUTTON_THING_TYPE)) { + return new FlicButtonHandler(thing); + } else if (thingTypeUID.equals(FlicButtonBindingConstants.BRIDGE_THING_TYPE)) { + FlicButtonDiscoveryService discoveryService = new FlicSimpleclientDiscoveryServiceImpl(thing.getUID()); + FlicDaemonBridgeHandler bridgeHandler = new FlicDaemonBridgeHandler((Bridge) thing, discoveryService); + registerDiscoveryService(discoveryService, thing.getUID()); + + return bridgeHandler; + } + + return null; + } + + @Override + protected synchronized void removeHandler(ThingHandler thingHandler) { + if (thingHandler instanceof FlicDaemonBridgeHandler) { + unregisterDiscoveryService(thingHandler.getThing().getUID()); + } + super.removeHandler(thingHandler); + } + + private synchronized void registerDiscoveryService(FlicButtonDiscoveryService discoveryService, + ThingUID bridgeUID) { + this.discoveryServiceRegs.put(bridgeUID, getBundleContext().registerService(DiscoveryService.class.getName(), + discoveryService, new Hashtable())); + } + + private synchronized void unregisterDiscoveryService(ThingUID bridgeUID) { + ServiceRegistration serviceReg = this.discoveryServiceRegs.get(bridgeUID); + if (serviceReg != null) { + FlicButtonDiscoveryService service = (FlicButtonDiscoveryService) getBundleContext() + .getService(serviceReg.getReference()); + if (service != null) { + service.deactivate(); + } + serviceReg.unregister(); + discoveryServiceRegs.remove(bridgeUID); + } + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicButtonDiscoveryService.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicButtonDiscoveryService.java new file mode 100644 index 0000000000000..917beb9570843 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicButtonDiscoveryService.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.discovery; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingUID; + +import io.flic.fliclib.javaclient.Bdaddr; +import io.flic.fliclib.javaclient.FlicClient; + +/** + * A {@link DiscoveryService} for Flic buttons. + * + * @author Patrick Fink - Initial contribution + * + */ +@NonNullByDefault +public interface FlicButtonDiscoveryService extends DiscoveryService { + + /** + * + * @param bdaddr Bluetooth address of the discovered Flic button + * @return UID that was created by the discovery service + */ + public ThingUID flicButtonDiscovered(Bdaddr bdaddr); + + public void activate(FlicClient client); + + public void deactivate(); +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java new file mode 100644 index 0000000000000..3caa071093be2 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.discovery; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants; +import org.openhab.binding.flicbutton.internal.util.FlicButtonUtils; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.flic.fliclib.javaclient.Bdaddr; +import io.flic.fliclib.javaclient.FlicClient; +import io.flic.fliclib.javaclient.GeneralCallbacks; +import io.flic.fliclib.javaclient.GetInfoResponseCallback; +import io.flic.fliclib.javaclient.enums.BdAddrType; +import io.flic.fliclib.javaclient.enums.BluetoothControllerState; + +/** + * For each configured flicd service, there is a {@link FlicSimpleclientDiscoveryServiceImpl} which will be initialized + * by {@link org.openhab.binding.flicbutton.internal.FlicButtonHandlerFactory}. + * + * It can scan for Flic Buttons already that are already added to fliclib-linux-hci ("verified" buttons), * + * but it does not support adding and verify new buttons on it's own. + * New buttons have to be added (verified) e.g. via simpleclient by Shortcut Labs. + * Background discovery listens for new buttons that are getting verified. + * + * @author Patrick Fink - Initial contribution + */ +@NonNullByDefault +public class FlicSimpleclientDiscoveryServiceImpl extends AbstractDiscoveryService + implements FlicButtonDiscoveryService { + private final Logger logger = LoggerFactory.getLogger(FlicSimpleclientDiscoveryServiceImpl.class); + + private boolean activated = false; + private ThingUID bridgeUID; + private @Nullable FlicClient flicClient; + + public FlicSimpleclientDiscoveryServiceImpl(ThingUID bridgeUID) { + super(FlicButtonBindingConstants.SUPPORTED_THING_TYPES_UIDS, 2, true); + this.bridgeUID = bridgeUID; + } + + @Override + public void activate(FlicClient flicClient) { + this.flicClient = flicClient; + activated = true; + super.activate(null); + } + + @Override + public void deactivate() { + activated = false; + super.deactivate(); + } + + @Override + protected void startScan() { + try { + if (activated) { + discoverVerifiedButtons(); + } + } catch (IOException e) { + logger.warn("Error occured during button discovery", e); + if (this.scanListener != null) { + scanListener.onErrorOccurred(e); + } + } + } + + protected void discoverVerifiedButtons() throws IOException { + flicClient.getInfo(new GetInfoResponseCallback() { + @Override + public void onGetInfoResponse(@Nullable BluetoothControllerState bluetoothControllerState, + @Nullable Bdaddr myBdAddr, @Nullable BdAddrType myBdAddrType, int maxPendingConnections, + int maxConcurrentlyConnectedButtons, int currentPendingConnections, + boolean currentlyNoSpaceForNewConnection, Bdaddr @Nullable [] verifiedButtons) throws IOException { + for (final @Nullable Bdaddr bdaddr : verifiedButtons) { + if (bdaddr != null) { + flicButtonDiscovered((@NonNull Bdaddr) bdaddr); + } + } + } + }); + } + + @Override + protected void startBackgroundDiscovery() { + super.startBackgroundDiscovery(); + flicClient.setGeneralCallbacks(new GeneralCallbacks() { + @Override + public void onNewVerifiedButton(@Nullable Bdaddr bdaddr) throws IOException { + logger.debug("A new Flic button was added by an external flicd client: {}", bdaddr); + if (bdaddr != null) { + flicButtonDiscovered((@NonNull Bdaddr) bdaddr); + } + } + }); + } + + @Override + protected void stopBackgroundDiscovery() { + super.stopBackgroundDiscovery(); + if (flicClient != null) { + flicClient.setGeneralCallbacks(null); + } + } + + @Override + public ThingUID flicButtonDiscovered(Bdaddr bdaddr) { + logger.debug("Flic Button {} discovered!", bdaddr); + ThingUID flicButtonUID = FlicButtonUtils.getThingUIDFromBdAddr(bdaddr, bridgeUID); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(flicButtonUID).withBridge(bridgeUID) + .withLabel("Flic Button " + bdaddr.toString().replace(":", "")) + .withProperty(FlicButtonBindingConstants.CONFIG_ADDRESS, bdaddr.toString()) + .withRepresentationProperty(FlicButtonBindingConstants.CONFIG_ADDRESS).build(); + this.thingDiscovered(discoveryResult); + return flicButtonUID; + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/ChildThingHandler.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/ChildThingHandler.java new file mode 100644 index 0000000000000..58793d295d0d3 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/ChildThingHandler.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.handler; + +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; + +/** + * The {@link ChildThingHandler} class is an abstract class for handlers that are dependent from a parent + * {@link BridgeHandler}. + * + * @author Patrick Fink - Initial contribution + * @param The bridge type this child handler depends on + */ +@NonNullByDefault +public abstract class ChildThingHandler extends BaseThingHandler { + private static final Collection DEFAULT_TOLERATED_BRIDGE_STATUSES = Collections + .singleton(ThingStatus.ONLINE); + protected boolean bridgeValid = false; + protected @Nullable BridgeHandlerType bridgeHandler; + + public ChildThingHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + setStatusBasedOnBridge(); + if (getBridge() != null) { + linkBridge(); + } + } + + protected void linkBridge() { + try { + BridgeHandler bridgeHandlerUncasted = getBridge().getHandler(); + bridgeHandler = (BridgeHandlerType) bridgeHandlerUncasted; + } catch (ClassCastException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge Type is invalid."); + } + } + + protected void setStatusBasedOnBridge() { + setStatusBasedOnBridge(DEFAULT_TOLERATED_BRIDGE_STATUSES); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + this.setStatusBasedOnBridge(); + } + + protected void setStatusBasedOnBridge(Collection toleratedBridgeStatuses) { + if (getBridge() != null) { + if (toleratedBridgeStatuses.contains(getBridge().getStatus())) { + bridgeValid = true; + } else { + bridgeValid = false; + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, + "Bridge in unsupported status: " + getBridge().getStatus()); + } + } else { + bridgeValid = false; + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge missing."); + } + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonBatteryLevelListener.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonBatteryLevelListener.java new file mode 100644 index 0000000000000..43bdda8dd6817 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonBatteryLevelListener.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.handler; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import io.flic.fliclib.javaclient.BatteryStatusListener; +import io.flic.fliclib.javaclient.Bdaddr; + +/** + * Each {@link FlicButtonBatteryLevelListener} object listens to the battery status of a specific Flic button + * and calls updates the {@link FlicButtonHandler} accordingly. + * + * @author Patrick Fink - Initial contribution + * + */ +@NonNullByDefault +public class FlicButtonBatteryLevelListener extends BatteryStatusListener.Callbacks { + + private final FlicButtonHandler thingHandler; + + FlicButtonBatteryLevelListener(FlicButtonHandler thingHandler) { + this.thingHandler = thingHandler; + } + + @Override + public void onBatteryStatus(@Nullable Bdaddr bdaddr, int i, long l) throws IOException { + thingHandler.updateBatteryChannel(i); + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java new file mode 100644 index 0000000000000..77c2d0780ae53 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.handler; + +import java.io.IOException; +import java.util.concurrent.Semaphore; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.flic.fliclib.javaclient.ButtonConnectionChannel; +import io.flic.fliclib.javaclient.enums.ClickType; +import io.flic.fliclib.javaclient.enums.ConnectionStatus; +import io.flic.fliclib.javaclient.enums.CreateConnectionChannelError; +import io.flic.fliclib.javaclient.enums.DisconnectReason; +import io.flic.fliclib.javaclient.enums.RemovedReason; + +/** + * Each {@link FlicButtonEventListener} object listens to events of a specific Flic button and calls the + * associated {@link FlicButtonHandler} back accordingly. + * + * @author Patrick Fink - Initial contribution + * + */ +@NonNullByDefault +public class FlicButtonEventListener extends ButtonConnectionChannel.Callbacks { + private final Logger logger = LoggerFactory.getLogger(FlicButtonEventListener.class); + + private final FlicButtonHandler thingHandler; + private final Semaphore channelResponseSemaphore = new Semaphore(0); + + FlicButtonEventListener(FlicButtonHandler thingHandler) { + this.thingHandler = thingHandler; + } + + public Semaphore getChannelResponseSemaphore() { + return channelResponseSemaphore; + } + + @Override + public synchronized void onCreateConnectionChannelResponse(@Nullable ButtonConnectionChannel channel, + @Nullable CreateConnectionChannelError createConnectionChannelError, + @Nullable ConnectionStatus connectionStatus) { + logger.debug("Create response {}: {}, {}", channel.getBdaddr(), createConnectionChannelError, connectionStatus); + // Handling does not differ from Status change, so redirect + if (connectionStatus != null) { + thingHandler.initializeStatus((@NonNull ConnectionStatus) connectionStatus); + channelResponseSemaphore.release(); + } + } + + @Override + public void onRemoved(@Nullable ButtonConnectionChannel channel, @Nullable RemovedReason removedReason) { + thingHandler.flicButtonRemoved(); + logger.debug("Button {} removed. ThingStatus updated to OFFLINE. Reason: {}", channel.getBdaddr(), + removedReason); + } + + @Override + public void onConnectionStatusChanged(@Nullable ButtonConnectionChannel channel, + @Nullable ConnectionStatus connectionStatus, @Nullable DisconnectReason disconnectReason) { + logger.trace("New status for {}: {}", channel.getBdaddr(), + connectionStatus + (connectionStatus == ConnectionStatus.Disconnected ? ", " + disconnectReason : "")); + if (connectionStatus != null) { + thingHandler.connectionStatusChanged((@NonNull ConnectionStatus) connectionStatus, disconnectReason); + } + } + + @Override + public void onButtonUpOrDown(@Nullable ButtonConnectionChannel channel, @Nullable ClickType clickType, + boolean wasQueued, int timeDiff) throws IOException { + if (channel != null && clickType != null) { + logger.trace("{} {}", channel.getBdaddr(), clickType.name()); + String commonTriggerEvent = FlicButtonBindingConstants.FLIC_OPENHAB_TRIGGER_EVENT_MAP.get(clickType.name()); + if (commonTriggerEvent != null) { + thingHandler.fireTriggerEvent(commonTriggerEvent); + } + } + } + + @Override + public void onButtonSingleOrDoubleClickOrHold(@Nullable ButtonConnectionChannel channel, + @Nullable ClickType clickType, boolean wasQueued, int timeDiff) throws IOException { + // Handling does not differ from up/down events, so redirect + if (channel != null && clickType != null) { + onButtonUpOrDown((@NonNull ButtonConnectionChannel) channel, (@NonNull ClickType) clickType, wasQueued, + timeDiff); + } + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonHandler.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonHandler.java new file mode 100644 index 0000000000000..ffda46abbb5cb --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonHandler.java @@ -0,0 +1,213 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.handler; + +import static org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants.*; + +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.CommonTriggerEvents; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.flic.fliclib.javaclient.BatteryStatusListener; +import io.flic.fliclib.javaclient.Bdaddr; +import io.flic.fliclib.javaclient.ButtonConnectionChannel; +import io.flic.fliclib.javaclient.enums.ConnectionStatus; +import io.flic.fliclib.javaclient.enums.DisconnectReason; + +/** + * The {@link FlicButtonHandler} is responsible for initializing the online status of Flic Buttons + * and trigger channel events when they're used. + * + * @author Patrick Fink - Initial contribution + */ +@NonNullByDefault +public class FlicButtonHandler extends ChildThingHandler { + + private Logger logger = LoggerFactory.getLogger(FlicButtonHandler.class); + private @Nullable ScheduledFuture delayedDisconnectTask; + private @Nullable Future initializationTask; + private @Nullable DisconnectReason latestDisconnectReason; + private @Nullable ButtonConnectionChannel eventConnection; + private @Nullable Bdaddr bdaddr; + private @Nullable BatteryStatusListener batteryConnection; + + public FlicButtonHandler(Thing thing) { + super(thing); + } + + public @Nullable Bdaddr getBdaddr() { + return bdaddr; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // Pure sensor -> no commands have to be handled + } + + @Override + public void initialize() { + super.initialize(); + bdaddr = new Bdaddr((String) this.getThing().getConfiguration().get(CONFIG_ADDRESS)); + if (bridgeValid) { + initializationTask = scheduler.submit(this::initializeThing); + } + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + super.bridgeStatusChanged(bridgeStatusInfo); + if (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE && bridgeValid) { + dispose(); + initializationTask = scheduler.submit(this::initializeThing); + } + } + + private void initializeThing() { + try { + initializeBatteryListener(); + initializeEventListener(); + // EventListener calls initializeStatus() before releasing so that ThingStatus should be set at this point + if (this.getThing().getStatus().equals(ThingStatus.INITIALIZING)) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Got no response by eventListener"); + } + } catch (IOException | InterruptedException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Connection setup failed: {}" + e.getMessage()); + } + } + + private void initializeBatteryListener() throws IOException { + FlicButtonBatteryLevelListener batteryListener = new FlicButtonBatteryLevelListener(this); + BatteryStatusListener batteryConnection = new BatteryStatusListener(getBdaddr(), batteryListener); + bridgeHandler.getFlicClient().addBatteryStatusListener(batteryConnection); + this.batteryConnection = batteryConnection; + } + + public void initializeEventListener() throws IOException, InterruptedException { + FlicButtonEventListener eventListener = new FlicButtonEventListener(this); + ButtonConnectionChannel eventConnection = new ButtonConnectionChannel(getBdaddr(), eventListener); + bridgeHandler.getFlicClient().addConnectionChannel(eventConnection); + this.eventConnection = eventConnection; + eventListener.getChannelResponseSemaphore().tryAcquire(5, TimeUnit.SECONDS); + } + + @Override + public void dispose() { + cancelDelayedDisconnectTask(); + cancelInitializationTask(); + try { + if (eventConnection != null) { + bridgeHandler.getFlicClient().removeConnectionChannel(eventConnection); + } + if (batteryConnection != null) { + bridgeHandler.getFlicClient().removeBatteryStatusListener(this.batteryConnection); + } + } catch (IOException e) { + logger.warn("Button channel could not be properly removed", e); + } + + super.dispose(); + } + + void initializeStatus(ConnectionStatus connectionStatus) { + if (connectionStatus == ConnectionStatus.Disconnected) { + setOffline(); + } else { + setOnline(); + } + } + + void connectionStatusChanged(ConnectionStatus connectionStatus, @Nullable DisconnectReason disconnectReason) { + latestDisconnectReason = disconnectReason; + if (connectionStatus == ConnectionStatus.Disconnected) { + // Status change to offline have to be scheduled to improve stability, + // see https://github.com/pfink/openhab2-flicbutton/issues/2 + scheduleStatusChangeToOffline(); + } else { + setOnline(); + } + } + + private void scheduleStatusChangeToOffline() { + if (delayedDisconnectTask == null) { + delayedDisconnectTask = scheduler.schedule(this::setOffline, BUTTON_OFFLINE_GRACE_PERIOD_SECONDS, + TimeUnit.SECONDS); + } + } + + protected void setOnline() { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); + } + + protected void setOffline() { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE, + "Disconnect Reason: " + Objects.toString(latestDisconnectReason)); + } + + // Cleanup delayedDisconnect on status change to online + @Override + protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) { + if (status == ThingStatus.ONLINE) { + cancelDelayedDisconnectTask(); + } + super.updateStatus(status, statusDetail, description); + } + + private void cancelInitializationTask() { + if (initializationTask != null) { + initializationTask.cancel(true); + initializationTask = null; + } + } + + private void cancelDelayedDisconnectTask() { + if (delayedDisconnectTask != null) { + delayedDisconnectTask.cancel(false); + delayedDisconnectTask = null; + } + } + + void updateBatteryChannel(int percent) { + DecimalType batteryLevel = new DecimalType(percent); + updateState(CHANNEL_ID_BATTERY_LEVEL, batteryLevel); + } + + void flicButtonRemoved() { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE, + "Button was removed/detached from flicd (e.g. by simpleclient)."); + } + + void fireTriggerEvent(String event) { + String channelID = event.equals(CommonTriggerEvents.PRESSED) || event.equals(CommonTriggerEvents.RELEASED) + ? CHANNEL_ID_RAWBUTTON_EVENTS + : CHANNEL_ID_BUTTON_EVENTS; + updateStatus(ThingStatus.ONLINE); + triggerChannel(channelID, event); + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeConfiguration.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeConfiguration.java new file mode 100644 index 0000000000000..1f176b2bac5a8 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeConfiguration.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.handler; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The configuration of a flicd bridge handled by {@link FlicDaemonBridgeHandler}. + * + * @author Patrick Fink - Initial contribution + * + */ +@NonNullByDefault +public class FlicDaemonBridgeConfiguration { + + @Nullable + private String hostname; + private int port; + + public @Nullable InetAddress getHost() throws UnknownHostException { + return InetAddress.getByName(hostname); + } + + public int getPort() { + return port; + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java new file mode 100644 index 0000000000000..cba4319de0e45 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.handler; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.flicbutton.internal.discovery.FlicButtonDiscoveryService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.flic.fliclib.javaclient.FlicClient; + +/** + * The {@link FlicDaemonBridgeHandler} handles a running instance of the fliclib-linux-hci server (flicd). + * + * @author Patrick Fink - Initial contribution + */ +@NonNullByDefault +public class FlicDaemonBridgeHandler extends BaseBridgeHandler { + private final Logger logger = LoggerFactory.getLogger(FlicDaemonBridgeHandler.class); + private static final long REINITIALIZE_DELAY_SECONDS = 10; + // Config parameters + private @Nullable FlicDaemonBridgeConfiguration cfg; + // Services + private FlicButtonDiscoveryService buttonDiscoveryService; + private @Nullable Future flicClientFuture; + // For disposal + private Collection<@Nullable Future> startedTasks = new ArrayList<>(3); + private @Nullable FlicClient flicClient; + + public FlicDaemonBridgeHandler(Bridge bridge, FlicButtonDiscoveryService buttonDiscoveryService) { + super(bridge); + this.buttonDiscoveryService = buttonDiscoveryService; + } + + public @Nullable FlicClient getFlicClient() { + return flicClient; + } + + @Override + public void initialize() { + startedTasks.add(scheduler.submit(this::initializeThing)); + } + + public void initializeThing() { + try { + initConfigParameters(); + startFlicdClientAsync(); + activateButtonDiscoveryService(); + initThingStatus(); + } catch (UnknownHostException ignored) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname wrong or unknown!"); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Error connecting to flicd: " + e.getMessage()); + dispose(); + scheduleReinitialize(); + } + } + + private void initConfigParameters() { + cfg = getConfigAs(FlicDaemonBridgeConfiguration.class); + } + + private void activateButtonDiscoveryService() { + if (flicClient != null) { + buttonDiscoveryService.activate((@NonNull FlicClient) flicClient); + } else { + throw new IllegalStateException("flicClient not properly initialized"); + } + } + + private void startFlicdClientAsync() throws IOException { + flicClient = new FlicClient(cfg.getHost().getHostAddress(), cfg.getPort()); + Runnable flicClientService = () -> { + try { + flicClient.handleEvents(); + flicClient.close(); + logger.debug("Listening to flicd ended"); + } catch (IOException e) { + logger.debug("Error occured while listening to flicd", e); + } finally { + if (Thread.currentThread().isInterrupted()) { + onClientFailure(); + } + } + }; + + if (!Thread.currentThread().isInterrupted()) { + flicClientFuture = scheduler.submit(flicClientService); + startedTasks.add(flicClientFuture); + } + } + + private void onClientFailure() { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "flicd client terminated, probably flicd is not reachable anymore."); + dispose(); + scheduleReinitialize(); + } + + private void initThingStatus() { + if (!flicClientFuture.isDone()) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "flicd client could not be started, probably flicd is not reachable."); + } + } + + @Override + public void dispose() { + super.dispose(); + startedTasks.forEach(task -> task.cancel(true)); + startedTasks = new ArrayList<>(2); + buttonDiscoveryService.deactivate(); + } + + private void scheduleReinitialize() { + startedTasks.add(scheduler.schedule(this::initialize, REINITIALIZE_DELAY_SECONDS, TimeUnit.SECONDS)); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // No commands to the fliclib-linux-hci are supported. + // So there is nothing to handle in the bridge handler + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/util/FlicButtonUtils.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/util/FlicButtonUtils.java new file mode 100644 index 0000000000000..3f59ce45b4615 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/util/FlicButtonUtils.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2022 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.flicbutton.internal.util; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants; +import org.openhab.core.thing.ThingUID; + +import io.flic.fliclib.javaclient.Bdaddr; + +/** + * The {@link FlicButtonUtils} class defines static utility methods that are used within the binding. + * + * @author Patrick Fink - Initial contribution + * + */ +@NonNullByDefault +public class FlicButtonUtils { + public static ThingUID getThingUIDFromBdAddr(Bdaddr bdaddr, ThingUID bridgeUID) { + String thingID = bdaddr.toString().replace(":", "-"); + return new ThingUID(FlicButtonBindingConstants.FLICBUTTON_THING_TYPE, bridgeUID, thingID); + } + + public static Bdaddr getBdAddrFromThingUID(ThingUID thingUID) { + String bdaddrRaw = thingUID.getId().replace("-", ":"); + return new Bdaddr(bdaddrRaw); + } +} diff --git a/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..8055604ca2001 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + FlicButton Binding + This is the binding for Flic buttons by Shortcut Labs. + + diff --git a/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..e5c79c2ce9008 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,44 @@ + + + + + + This bridge represents a running instance of the fliclib-linux-hci server (flicd). + + + + network-address + + IP or Host name of the Flic daemon (flicd). + localhost + + + + Port where flicd is running. Defaults to 5551. + 5551 + + + + + + + + + + The thing(-type) representing a Flic Button + + + + + + + + + Bluetooth address in XX:XX:XX:XX:XX:XX format + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 191b76beade4d..43b52c4f89d22 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -125,6 +125,7 @@ org.openhab.binding.exec org.openhab.binding.feed org.openhab.binding.feican + org.openhab.binding.flicbutton org.openhab.binding.fmiweather org.openhab.binding.folderwatcher org.openhab.binding.folding From 7d0809db31a09c10f7eaa024b8734785ba424da7 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Sun, 20 Feb 2022 22:02:01 +0100 Subject: [PATCH 14/19] New Crowdin updates (#12229) * New translations deconz.properties (Italian) * New translations dynamodb.properties (Finnish) * New translations guntamatic.properties (Italian) * New translations ipp.properties (German) * New translations luxtronikheatpump.properties (German) * New translations mqtt.properties (Italian) * New translations openwebnet.properties (German) * New translations voskstt.properties (Italian) * New translations watsonstt.properties (Italian) --- .../OH-INF/i18n/deconz_it.properties | 5 + .../OH-INF/i18n/guntamatic_it.properties | 199 ++++++++++++++++++ .../resources/OH-INF/i18n/ipp_de.properties | 2 +- .../i18n/luxtronikheatpump_de.properties | 2 +- .../resources/OH-INF/i18n/mqtt_it.properties | 55 +++++ .../resources/OH-INF/i18n/mqtt_it.properties | 17 ++ .../OH-INF/i18n/openwebnet_de.properties | 18 ++ .../OH-INF/i18n/dynamodb_fi.properties | 24 +++ .../OH-INF/i18n/voskstt_it.properties | 20 ++ .../OH-INF/i18n/watsonstt_it.properties | 26 +++ 10 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 bundles/org.openhab.binding.guntamatic/src/main/resources/OH-INF/i18n/guntamatic_it.properties create mode 100644 bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_de.properties create mode 100644 bundles/org.openhab.persistence.dynamodb/src/main/resources/OH-INF/i18n/dynamodb_fi.properties create mode 100644 bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt_it.properties create mode 100644 bundles/org.openhab.voice.watsonstt/src/main/resources/OH-INF/i18n/watsonstt_it.properties diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz_it.properties b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz_it.properties index f53ec9f06e2b8..16377a3c45557 100644 --- a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz_it.properties +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz_it.properties @@ -196,3 +196,8 @@ channel-type.deconz.voltage.label = Tensione channel-type.deconz.voltage.description = Tensione Attuale channel-type.deconz.waterleakage.label = Perdite D'Acqua channel-type.deconz.waterleakage.description = Perdita di acqua rilevata + +# thing status descriptions + +offline.light-not-reachable = Non raggiungibile +offline.sensor-not-reachable = Non raggiungibile diff --git a/bundles/org.openhab.binding.guntamatic/src/main/resources/OH-INF/i18n/guntamatic_it.properties b/bundles/org.openhab.binding.guntamatic/src/main/resources/OH-INF/i18n/guntamatic_it.properties new file mode 100644 index 0000000000000..5f2b748a82b75 --- /dev/null +++ b/bundles/org.openhab.binding.guntamatic/src/main/resources/OH-INF/i18n/guntamatic_it.properties @@ -0,0 +1,199 @@ +# binding + +binding.guntamatic.name = Binding Guntamatic +binding.guntamatic.description = Binding per sistemi di riscaldamento Guntamatic. + +# thing types + +thing-type.guntamatic.biocom.label = Guntamatic Biocom +thing-type.guntamatic.biocom.description = Sistema di riscaldamento a Pellets Guntamatic Biocom. Non testato\! Fornisci feedback\! +thing-type.guntamatic.biocom.channel.controlBoilerApproval.label = Imposta Approvazione Boiler +thing-type.guntamatic.biocom.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.biocom.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.biocom.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.biocom.channel.controlHeatCircProgram0.label = Imposta Programma Riscaldamento Ciclo 0 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram1.label = Imposta Programma Riscaldamento Ciclo 1 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram2.label = Imposta Programma Riscaldamento Ciclo 2 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram3.label = Imposta Programma Riscaldamento Ciclo 3 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram4.label = Imposta Programma Riscaldamento Ciclo 4 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram5.label = Imposta Programma Riscaldamento Ciclo 5 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram6.label = Imposta Programma Riscaldamento Ciclo 6 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram7.label = Imposta Programma Riscaldamento Ciclo 7 +thing-type.guntamatic.biocom.channel.controlHeatCircProgram8.label = Imposta Programma Riscaldamento Ciclo 8 +thing-type.guntamatic.biocom.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.biocom.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.biocom.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.biocom.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda +thing-type.guntamatic.biosmart.label = Guntamatic Biosmart +thing-type.guntamatic.biosmart.description = Log sistema riscaldamento Guntamatic Biosmart +thing-type.guntamatic.biosmart.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.biosmart.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.biosmart.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram0.label = Imposta Programma Ciclo Riscaldamento 0 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram1.label = Imposta Programma Ciclo Riscaldamento 1 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram2.label = Imposta Programma Ciclo Riscaldamento 2 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram3.label = Imposta Programma Ciclo Riscaldamento 3 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram4.label = Imposta Programma Ciclo Riscaldamento 4 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram5.label = Imposta Programma Ciclo Riscaldamento 5 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram6.label = Imposta Programma Ciclo Riscaldamento 6 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram7.label = Imposta Programma Ciclo Riscaldamento 7 +thing-type.guntamatic.biosmart.channel.controlHeatCircProgram8.label = Imposta Programma Ciclo Riscaldamento 8 +thing-type.guntamatic.biosmart.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.biosmart.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.biosmart.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.biosmart.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda +thing-type.guntamatic.biostar.label = Guntamatic Biostar +thing-type.guntamatic.biostar.description = Sistema Riscaldamento a Pellets Guntamatic Biostar +thing-type.guntamatic.biostar.channel.controlBoilerApproval.label = Imposta Approvazione Boiler +thing-type.guntamatic.biostar.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.biostar.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.biostar.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.biostar.channel.controlHeatCircProgram0.label = Imposta Programma Riscaldamento Ciclo 0 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram1.label = Imposta Programma Riscaldamento Ciclo 1 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram2.label = Imposta Programma Riscaldamento Ciclo 2 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram3.label = Imposta Programma Riscaldamento Ciclo 3 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram4.label = Imposta Programma Riscaldamento Ciclo 4 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram5.label = Imposta Programma Riscaldamento Ciclo 5 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram6.label = Imposta Programma Riscaldamento Ciclo 6 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram7.label = Imposta Programma Riscaldamento Ciclo 7 +thing-type.guntamatic.biostar.channel.controlHeatCircProgram8.label = Imposta Programma Riscaldamento Ciclo 8 +thing-type.guntamatic.biostar.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.biostar.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.biostar.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.biostar.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda +thing-type.guntamatic.generic.label = Guntamatic Generico +thing-type.guntamatic.generic.description = Sistema di riscaldamento Guntamatic Generico. Usa questo tipo, se il tuo sistema di riscaldamento non è nessuno degli altri. Fornisci un feedback\! +thing-type.guntamatic.generic.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.generic.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.generic.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.generic.channel.controlHeatCircProgram0.label = Imposta Programma Ciclo Riscaldamento 0 +thing-type.guntamatic.generic.channel.controlHeatCircProgram1.label = Imposta Programma Ciclo Riscaldamento 1 +thing-type.guntamatic.generic.channel.controlHeatCircProgram2.label = Imposta Programma Ciclo Riscaldamento 2 +thing-type.guntamatic.generic.channel.controlHeatCircProgram3.label = Imposta Programma Ciclo Riscaldamento 3 +thing-type.guntamatic.generic.channel.controlHeatCircProgram4.label = Imposta Programma Ciclo Riscaldamento 4 +thing-type.guntamatic.generic.channel.controlHeatCircProgram5.label = Imposta Programma Ciclo Riscaldamento 5 +thing-type.guntamatic.generic.channel.controlHeatCircProgram6.label = Imposta Programma Ciclo Riscaldamento 6 +thing-type.guntamatic.generic.channel.controlHeatCircProgram7.label = Imposta Programma Ciclo Riscaldamento 7 +thing-type.guntamatic.generic.channel.controlHeatCircProgram8.label = Imposta Programma Ciclo Riscaldamento 8 +thing-type.guntamatic.generic.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.generic.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.generic.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.generic.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda +thing-type.guntamatic.powerchip.label = Guntamatic Powerchip +thing-type.guntamatic.powerchip.description = Sistema Riscaldamento Guntamatic Powerchip WoodChip +thing-type.guntamatic.powerchip.channel.controlBoilerApproval.label = Imposta Approvazione Boiler +thing-type.guntamatic.powerchip.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.powerchip.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.powerchip.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram0.label = Imposta Programma Riscaldamento Ciclo 0 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram1.label = Imposta Programma Riscaldamento Ciclo 1 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram2.label = Imposta Programma Riscaldamento Ciclo 2 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram3.label = Imposta Programma Riscaldamento Ciclo 3 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram4.label = Imposta Programma Riscaldamento Ciclo 4 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram5.label = Imposta Programma Riscaldamento Ciclo 5 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram6.label = Imposta Programma Riscaldamento Ciclo 6 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram7.label = Imposta Programma Riscaldamento Ciclo 7 +thing-type.guntamatic.powerchip.channel.controlHeatCircProgram8.label = Imposta Programma Riscaldamento Ciclo 8 +thing-type.guntamatic.powerchip.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.powerchip.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.powerchip.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.powerchip.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda +thing-type.guntamatic.powercorn.label = Guntamatic Powercorn +thing-type.guntamatic.powercorn.description = Sistema Riscaldamento Guntamatic Powercorn EnergyGrain. Not Testato\! Fornisci un feedback\! +thing-type.guntamatic.powercorn.channel.controlBoilerApproval.label = Imposta Approvazione Boiler +thing-type.guntamatic.powercorn.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.powercorn.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.powercorn.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram0.label = Imposta Programma Riscaldamento Ciclo 0 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram1.label = Imposta Programma Riscaldamento Ciclo 1 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram2.label = Imposta Programma Riscaldamento Ciclo 2 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram3.label = Imposta Programma Riscaldamento Ciclo 3 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram4.label = Imposta Programma Riscaldamento Ciclo 4 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram5.label = Imposta Programma Riscaldamento Ciclo 5 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram6.label = Imposta Programma Riscaldamento Ciclo 6 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram7.label = Imposta Programma Riscaldamento Ciclo 7 +thing-type.guntamatic.powercorn.channel.controlHeatCircProgram8.label = Imposta Programma Riscaldamento Ciclo 8 +thing-type.guntamatic.powercorn.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.powercorn.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.powercorn.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.powercorn.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda +thing-type.guntamatic.pro.label = Guntamatic Pro +thing-type.guntamatic.pro.description = Sistema Riscaldamento Guntamatic Pro Pellets o WoodChip. Non Testato\! Fornisci un Feedback\! +thing-type.guntamatic.pro.channel.controlBoilerApproval.label = Imposta Approvazione Boiler +thing-type.guntamatic.pro.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.pro.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.pro.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.pro.channel.controlHeatCircProgram0.label = Imposta Programma Riscaldamento Ciclo 0 +thing-type.guntamatic.pro.channel.controlHeatCircProgram1.label = Imposta Programma Riscaldamento Ciclo 1 +thing-type.guntamatic.pro.channel.controlHeatCircProgram2.label = Imposta Programma Riscaldamento Ciclo 2 +thing-type.guntamatic.pro.channel.controlHeatCircProgram3.label = Imposta Programma Riscaldamento Ciclo 3 +thing-type.guntamatic.pro.channel.controlHeatCircProgram4.label = Imposta Programma Riscaldamento Ciclo 4 +thing-type.guntamatic.pro.channel.controlHeatCircProgram5.label = Imposta Programma Riscaldamento Ciclo 5 +thing-type.guntamatic.pro.channel.controlHeatCircProgram6.label = Imposta Programma Riscaldamento Ciclo 6 +thing-type.guntamatic.pro.channel.controlHeatCircProgram7.label = Imposta Programma Riscaldamento Ciclo 7 +thing-type.guntamatic.pro.channel.controlHeatCircProgram8.label = Imposta Programma Riscaldamento Ciclo 8 +thing-type.guntamatic.pro.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.pro.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.pro.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.pro.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda +thing-type.guntamatic.therm.label = Guntamatic Therm +thing-type.guntamatic.therm.description = Sistema Riscaldamento Guntamatic Therm Pellets. Non Testato\! Fornisci un Feedback\! +thing-type.guntamatic.therm.channel.controlBoilerApproval.label = Imposta Approvazione Boiler +thing-type.guntamatic.therm.channel.controlExtraWwHeat0.label = Esegue Ciclo 0 Acqua Extra Calda +thing-type.guntamatic.therm.channel.controlExtraWwHeat1.label = Esegue Ciclo 1 Acqua Extra Calda +thing-type.guntamatic.therm.channel.controlExtraWwHeat2.label = Esegue Ciclo 2 Acqua Extra Calda +thing-type.guntamatic.therm.channel.controlHeatCircProgram0.label = Imposta Programma Riscaldamento Ciclo 0 +thing-type.guntamatic.therm.channel.controlHeatCircProgram1.label = Imposta Programma Riscaldamento Ciclo 1 +thing-type.guntamatic.therm.channel.controlHeatCircProgram2.label = Imposta Programma Riscaldamento Ciclo 2 +thing-type.guntamatic.therm.channel.controlHeatCircProgram3.label = Imposta Programma Riscaldamento Ciclo 3 +thing-type.guntamatic.therm.channel.controlHeatCircProgram4.label = Imposta Programma Riscaldamento Ciclo 4 +thing-type.guntamatic.therm.channel.controlHeatCircProgram5.label = Imposta Programma Riscaldamento Ciclo 5 +thing-type.guntamatic.therm.channel.controlHeatCircProgram6.label = Imposta Programma Riscaldamento Ciclo 6 +thing-type.guntamatic.therm.channel.controlHeatCircProgram7.label = Imposta Programma Riscaldamento Ciclo 7 +thing-type.guntamatic.therm.channel.controlHeatCircProgram8.label = Imposta Programma Riscaldamento Ciclo 8 +thing-type.guntamatic.therm.channel.controlProgram.label = Imposta Programma +thing-type.guntamatic.therm.channel.controlWwHeat0.label = Esegue Ciclo 0 Acqua Calda +thing-type.guntamatic.therm.channel.controlWwHeat1.label = Esegue Ciclo 1 Acqua Calda +thing-type.guntamatic.therm.channel.controlWwHeat2.label = Esegue Ciclo 2 Acqua Calda + +# thing types config + +thing-type.config.guntamatic.heatingsystem.encoding.label = Codifica +thing-type.config.guntamatic.heatingsystem.encoding.description = Tabella Codici utilizzata dal sistema di riscaldamento Guntamatic. Predefinito\: 'windows-1252' +thing-type.config.guntamatic.heatingsystem.hostname.label = Nome host +thing-type.config.guntamatic.heatingsystem.hostname.description = Nome host o indirizzo IP del sistema di riscaldamento Guntamatic +thing-type.config.guntamatic.heatingsystem.key.label = Chiave +thing-type.config.guntamatic.heatingsystem.key.description = Facoltativo, ma necessario per leggere i parametri protetti e per controllare il sistema di riscaldamento Guntamatic. La chiave deve essere richiesta al supporto Guntamatic. +thing-type.config.guntamatic.heatingsystem.refreshInterval.label = Intervallo Aggiornamento +thing-type.config.guntamatic.heatingsystem.refreshInterval.description = Intervallo in cui il sistema di riscaldamento Guntamatic viene interrogato in secondi. Predefinito\: 60 s + +# channel types + +channel-type.guntamatic.controlBoilerApproval.label = Imposta Approvazione Boiler +channel-type.guntamatic.controlBoilerApproval.description = Imposta l'approvazione Boiler del sistema di riscaldamento Guntamatic (AUTO, OFF, ON) +channel-type.guntamatic.controlBoilerApproval.state.option.0 = AUTO +channel-type.guntamatic.controlBoilerApproval.state.option.1 = SPENTO +channel-type.guntamatic.controlBoilerApproval.state.option.2 = ACCESO +channel-type.guntamatic.controlExtraWwHeat.label = Esegue Ciclo Acqua Extra Calda +channel-type.guntamatic.controlExtraWwHeat.description = Esegue il ciclo Extra Acqua Calda del sistema di riscaldamento Guntamatic (RICARICA) +channel-type.guntamatic.controlExtraWwHeat.state.option.0 = RICARICA +channel-type.guntamatic.controlHeatCircProgram.label = Imposta Ciclo Programma Riscaldamento +channel-type.guntamatic.controlHeatCircProgram.description = Imposta Ciclo Programma per Sistema Riscaldamento Guntamatic (SPENTO, NORMALE, RISCALDAMENTO, LOWER) +channel-type.guntamatic.controlHeatCircProgram.state.option.0 = SPENTO +channel-type.guntamatic.controlHeatCircProgram.state.option.1 = NORMALE +channel-type.guntamatic.controlHeatCircProgram.state.option.2 = RISCALDAMENTO +channel-type.guntamatic.controlHeatCircProgram.state.option.3 = LOWER +channel-type.guntamatic.controlProgram.label = Imposta Programma +channel-type.guntamatic.controlProgram.description = Imposta Programma per Sistema Riscaldamento Guntamatic (SPENTO, NORMALE, ACQUACALDA, MANUALE) +channel-type.guntamatic.controlProgram.state.option.0 = SPENTO +channel-type.guntamatic.controlProgram.state.option.1 = NORMALE +channel-type.guntamatic.controlProgram.state.option.2 = ACQUACALDA +channel-type.guntamatic.controlProgram.state.option.8 = MANUALE +channel-type.guntamatic.controlProgramWOManu.label = Imposta Programma +channel-type.guntamatic.controlProgramWOManu.description = Imposta Programma per Sistema Riscaldamento Guntamatic (SPENTO, NORMALE, ACQUACALDA) +channel-type.guntamatic.controlProgramWOManu.state.option.0 = SPENTO +channel-type.guntamatic.controlProgramWOManu.state.option.1 = NORMALE +channel-type.guntamatic.controlProgramWOManu.state.option.2 = ACQUACALDA +channel-type.guntamatic.controlWwHeat.label = Esegue Ciclo Acqua Calda +channel-type.guntamatic.controlWwHeat.description = Esegue il ciclo Acqua Calda del sistema di riscaldamento Guntamatic (RICARICA) +channel-type.guntamatic.controlWwHeat.state.option.0 = RICARICA diff --git a/bundles/org.openhab.binding.ipp/src/main/resources/OH-INF/i18n/ipp_de.properties b/bundles/org.openhab.binding.ipp/src/main/resources/OH-INF/i18n/ipp_de.properties index 4884948f56207..8c768f6584585 100644 --- a/bundles/org.openhab.binding.ipp/src/main/resources/OH-INF/i18n/ipp_de.properties +++ b/bundles/org.openhab.binding.ipp/src/main/resources/OH-INF/i18n/ipp_de.properties @@ -22,6 +22,6 @@ thing-type.config.ipp.printer.url.description = URL des Druckers. channel-type.ipp.doneJobs.label = Erledigte Druckaufträge channel-type.ipp.doneJobs.description = Zeigt die Anzahl der erledigten Druckaufträge des Druckers an. channel-type.ipp.jobs.label = Anzahl der Druckaufträge -channel-type.ipp.jobs.description = Zeigt sie Anzahl aller Druckaufträge des Druckers an. +channel-type.ipp.jobs.description = Zeigt die Anzahl aller Druckaufträge des Druckers an. channel-type.ipp.waitingJobs.label = Wartende Druckaufträge channel-type.ipp.waitingJobs.description = Zeigt die Anzahl der wartenden Druckaufträge des Druckers an. diff --git a/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/i18n/luxtronikheatpump_de.properties b/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/i18n/luxtronikheatpump_de.properties index f78545fe2a331..cb5ee100aa7af 100644 --- a/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/i18n/luxtronikheatpump_de.properties +++ b/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/i18n/luxtronikheatpump_de.properties @@ -422,7 +422,7 @@ channel-type.luxtronikheatpump.menuStateLine3.state.option.10 = Kühlbetrieb channel-type.luxtronikheatpump.menuStateLine3.state.option.12 = Schwimmbad / Photovoltaik channel-type.luxtronikheatpump.menuStateLine3.state.option.13 = Heizen ext. Energiequelle channel-type.luxtronikheatpump.menuStateLine3.state.option.14 = Brauchwasser ext. Energiequelle -channel-type.luxtronikheatpump.menuStateLine3.state.option.16 = Durchflussüberachung +channel-type.luxtronikheatpump.menuStateLine3.state.option.16 = Durchflussüberwachung channel-type.luxtronikheatpump.menuStateLine3.state.option.17 = Zweiter Wärmeerzeuger 1 Betrieb channel-type.luxtronikheatpump.operationMode.state.option.0 = Automatisch channel-type.luxtronikheatpump.operationMode.state.option.1 = Zuheizer diff --git a/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt_it.properties b/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt_it.properties index 786d4a410dd6d..3d9197da2bec5 100644 --- a/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt_it.properties +++ b/bundles/org.openhab.binding.mqtt.espmilighthub/src/main/resources/OH-INF/i18n/mqtt_it.properties @@ -1,3 +1,20 @@ +# thing types + +thing-type.mqtt.cct.label = Milight CCT +thing-type.mqtt.cct.description = Lampadina Led con comandi bianchi freddi e caldi +thing-type.mqtt.fut089.label = Milight FUT089 +thing-type.mqtt.fut089.description = Usa questo quando il tuo telecomando è il nuovo tipo con 8 gruppi chiamato FUT089 e le tue lampadine sono rgb_cct +thing-type.mqtt.fut091.label = Milight FUT091 +thing-type.mqtt.fut091.description = Usa questo quando il tuo telecomando è il fut091 più recente e le tue lampadine sono cct +thing-type.mqtt.rgb.label = Milight RGB +thing-type.mqtt.rgb.description = Lampadina RGB senza bianco +thing-type.mqtt.rgb_cct.label = Milight RGBCCT +thing-type.mqtt.rgb_cct.description = Lampadina Led a colori, e bianco sia freddo che caldo. +thing-type.mqtt.rgbw.label = Milight RGBW +thing-type.mqtt.rgbw.description = Lampadina RGB con un bianco fisso + +# thing types config + thing-type.config.mqtt.cct.dimmedCT.label = Temp Colore Impostato thing-type.config.mqtt.cct.dimmedCT.description = Lampadine tradizionali diventano più calde quando sono regolate. Imposta a 370 o lascia bianco per disabilitare. thing-type.config.mqtt.cct.oneTriggersNightMode.label = 1% Attiva modalità Notturna @@ -30,3 +47,41 @@ thing-type.config.mqtt.rgbw.whiteSat.label = Saturazione Bianca thing-type.config.mqtt.rgbw.whiteSat.description = Quando entrambi i valori WhiteHue e WhiteSat sono visti dal binding, si attiveranno il LED bianchi. thing-type.config.mqtt.rgbw.whiteThreshold.label = Soglia Bianco thing-type.config.mqtt.rgbw.whiteThreshold.description = I valori di saturazione uguali o inferiori a questo valore su un controllo di colore RGBW attiveranno la modalità bianco. -1 disabiliterà questa funzione. + +# channel types + +channel-type.mqtt.bulbMode.label = Modalità Lampadina +channel-type.mqtt.bulbMode.description = Mostra il modo in cui è la lampadina in questo momento. +channel-type.mqtt.bulbMode.state.option.white = bianco +channel-type.mqtt.bulbMode.state.option.color = colore +channel-type.mqtt.bulbMode.state.option.scene = scena +channel-type.mqtt.bulbMode.state.option.night = notte +channel-type.mqtt.colour.label = Colore +channel-type.mqtt.colour.description = Permette di modificare il colore, la luminosità e la saturazione della lampadina. +channel-type.mqtt.colourTemperature.label = Temperatura Colore +channel-type.mqtt.colourTemperature.description = Cambia da bianco freddo a caldo con questo controllo. +channel-type.mqtt.command.label = Command +channel-type.mqtt.command.description = Invia un comando diretto alla/e lampadina/e. +channel-type.mqtt.command.state.option.next_mode = Modalità Successiva +channel-type.mqtt.command.state.option.previous_mode = Modalità Precedente +channel-type.mqtt.command.state.option.mode_speed_up = Modalità Veloce +channel-type.mqtt.command.state.option.mode_speed_down = Modalità Lenta +channel-type.mqtt.command.state.option.set_white = Imposta Bianco +channel-type.mqtt.command.state.option.level_down = Livello Inferiore +channel-type.mqtt.command.state.option.level_up = Livello Superiore +channel-type.mqtt.command.state.option.temperature_down = Abbassa Temperatura +channel-type.mqtt.command.state.option.temperature_up = Alza Temperatura +channel-type.mqtt.command.state.option.night_mode = Modalità Notturna +channel-type.mqtt.discoMode.label = Modalità Discoteca +channel-type.mqtt.discoMode.description = Passa direttamente alla modalità Discoteca. +channel-type.mqtt.discoMode.state.option.0 = Disco 0 +channel-type.mqtt.discoMode.state.option.1 = Disco 1 +channel-type.mqtt.discoMode.state.option.2 = Disco 2 +channel-type.mqtt.discoMode.state.option.3 = Disco 3 +channel-type.mqtt.discoMode.state.option.4 = Disco 4 +channel-type.mqtt.discoMode.state.option.5 = Disco 5 +channel-type.mqtt.discoMode.state.option.6 = Disco 6 +channel-type.mqtt.discoMode.state.option.7 = Disco 7 +channel-type.mqtt.discoMode.state.option.8 = Disco 8 +channel-type.mqtt.level.label = Livello +channel-type.mqtt.level.description = Il livello cambia la luminosità della lampadina. diff --git a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_it.properties b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_it.properties index 23d2db9c6543c..1b8bc8126b06f 100644 --- a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_it.properties +++ b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_it.properties @@ -10,6 +10,12 @@ thing-type.mqtt.broker.description = Una connessione a un broker MQTT # thing types config +thing-type.config.mqtt.broker.birthMessage.label = Messaggio Nascita +thing-type.config.mqtt.broker.birthMessage.description = Messaggio da inviare al broker quando si stabilisce una connessione. +thing-type.config.mqtt.broker.birthRetain.label = Mantenere Messaggio Birth +thing-type.config.mqtt.broker.birthRetain.description = VERO se il messaggio Birth deve essere mantenuto (i valori predefiniti sono true) +thing-type.config.mqtt.broker.birthTopic.label = Topic Nascita +thing-type.config.mqtt.broker.birthTopic.description = I valori predefiniti sono vuoti e quindi disabilitano Messaggio Nascita. thing-type.config.mqtt.broker.certificate.label = Certificato Hash thing-type.config.mqtt.broker.certificate.description = Se **certificatepin** è impostato, questo hash viene utilizzato per verificare la connessione. Cancella per consentire un nuovo blocco di certificati al prossimo tentativo di connessione. Se vuoto verrà riempito automaticamente dalla prossima connessione di successo. Un esempio di input è `SHA-256\:83F9171E06A313118889F7D79302BD1B7A2042EE0CFD029ABF8DDD06FFA6CD9D3`. thing-type.config.mqtt.broker.certificatepin.label = Selezione Certificato @@ -49,6 +55,12 @@ thing-type.config.mqtt.broker.reconnectTime.label = Tempo Riconnessione thing-type.config.mqtt.broker.reconnectTime.description = Tempo riconnessione in ms. Se una connessione viene persa, l'associazione aspetterà questa tempo prima di tentare di riconnettersi. thing-type.config.mqtt.broker.secure.label = Connessione Sicura thing-type.config.mqtt.broker.secure.description = Utilizza TLS/SSL per stabilire una connessione sicura al broker. +thing-type.config.mqtt.broker.shutdownMessage.label = Messaggio Arresto +thing-type.config.mqtt.broker.shutdownMessage.description = Il messaggio da inviare al broker prima della fine della connessione. +thing-type.config.mqtt.broker.shutdownRetain.label = Messaggio Arresto Mantenuto +thing-type.config.mqtt.broker.shutdownRetain.description = Vero se il messaggio di arresto deve essere mantenuto (predefinito a vero) +thing-type.config.mqtt.broker.shutdownTopic.label = Topic Spegnimento +thing-type.config.mqtt.broker.shutdownTopic.description = Il valore predefinito è vuoto e quindi disabilita il messaggio di arresto. thing-type.config.mqtt.broker.username.label = Username thing-type.config.mqtt.broker.username.description = Il nome utente MQTT @@ -66,6 +78,8 @@ channel-type.config.mqtt.publishTrigger.separator.description = Il payload del c channel-type.config.mqtt.publishTrigger.stateTopic.label = Topic MQTT channel-type.config.mqtt.publishTrigger.stateTopic.description = Questo canale si attiverà su questo topic MQTT. Questo topic può contenere caratteri jolly come + e \# per esempio "all/in/\#" o "sensori/+/config". +# thing actions + actionInputTopicLabel = Topic MQTT actionInputTopicDesc = Il topic a cui pubblicare un valore. actionInputValueLabel = Valore @@ -74,5 +88,8 @@ actionInputRetainLabel = Conservare (Retain) actionInputRetainDesc = Messaggio da Conservare actionLabel = pubblica un messaggio MQTT actionDesc = Pubblica un valore sul topic MQTT specificato. + +# thing status + offline.notextualconfig = La connessione di sistema con il nome {0} non esiste più. offline.sharedremoved = Un altro binding ha rimosso inaspettatamente la connessione interna del broker. diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_de.properties b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_de.properties new file mode 100644 index 0000000000000..e21def8ae9eeb --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_de.properties @@ -0,0 +1,18 @@ +# binding + +binding.openwebnet.name = OpenWebNet (BTicino/Legrand) Binding +binding.openwebnet.description = Das OpenWebNet (BTicino/Legrand) Binding zur Integration von BTicino (Legrand) MyHOME® BUS- und drahtlosen Komponenten, die das OpenWebNet-Protokoll verwenden. + +# thing status descriptions + +offline.conf-error-no-ip-address = Es kann keine Verbindung zum Gateway hergestellt werden. In der Konfiguration wurde keine Host-/IP-Adresse angegeben. +offline.conf-error-no-serial-port = Es kann keine Verbindung zum Gateway hergestellt werden. Es wurde kein serieller Anschluss konfiguriert. +offline.conf-error-where = OpenWebNet Adresse vom (where)-Parameter in der Konfiguration ist null oder ungültig. +offline.conf-error-no-bridge = Keine Bridge zugeordnet (Gateway Thing nicht konfiguriert). +offline.conf-error-auth = Authentifizierung fehlgeschlagen. Überprüfen Sie das Gateway-Passwort in der Konfiguration. + +offline.comm-error-disconnected = Vom Gateway getrennt. +offline.comm-error-timeout = Zeitüberschreitung bei Verbindung zum Gateway. +offline.comm-error-connection = Verbindung zum Gateway konnte nicht hergestellt werden. + +unknown.waiting-state = Warten auf Statusaktualisierung... diff --git a/bundles/org.openhab.persistence.dynamodb/src/main/resources/OH-INF/i18n/dynamodb_fi.properties b/bundles/org.openhab.persistence.dynamodb/src/main/resources/OH-INF/i18n/dynamodb_fi.properties new file mode 100644 index 0000000000000..2c106e0a134f9 --- /dev/null +++ b/bundles/org.openhab.persistence.dynamodb/src/main/resources/OH-INF/i18n/dynamodb_fi.properties @@ -0,0 +1,24 @@ +persistence.config.dynamodb.accessKey.label = AWS pääsyavain +persistence.config.dynamodb.accessKey.description = AWS pääsyavain
Anna joko 1) pääsyavain ja salainen avain tai 2) AWS-käyttötunnistetiedosto, jossa on pääsytunnukset ja profiilin nimi. +persistence.config.dynamodb.expireDays.label = Datan vanhentuminen, päivinä +persistence.config.dynamodb.expireDays.description = Datan vanhentumisaika.
DynamoDB poistaa tätä vanhemman datan automaattisesti käyttäen TTL-toimintoa (Time to Live). Käytä tyhjää arvoa ottaaksesi datan vanhentuminen pois käytöstä. +persistence.config.dynamodb.profile.label = Profiilin nimi +persistence.config.dynamodb.profile.description = Profiilin nimi AWS-käyttötunnistetiedostossa.
Anna joko 1) käyttöoikeusavain ja salainen avain, tai 2) käyttötunnistetiedosto ja profiilin nimi. +persistence.config.dynamodb.profilesConfigFile.label = AWS-käyttötunnistetiedosto +persistence.config.dynamodb.profilesConfigFile.description = Polku AWS-käyttötunnistetiedostoon.
Esimerkiksi /etc/openhab/aws_creds. Huomaa, että käyttäjällä, joka ajaa openHABia, on oltava lukijan oikeudet tiedostoon.
Anna joko 1) pääsyavain ja salainen avain tai 2) AWS-käyttötunnistetiedosto ja profiilin nimi. +persistence.config.dynamodb.readCapacityUnits.label = Lukukapasiteetti +persistence.config.dynamodb.readCapacityUnits.description = Lukujen varattu kapasiteetti.
Oletus on 1. +persistence.config.dynamodb.region.label = AWS-alueen tunnus +persistence.config.dynamodb.region.description = AWS-alueen tunnus
Alueen on vastattava AWS-käyttäjän aluetta, joka käyttää Amazon DynamoDB\: tä.
Esimerkiksi eu-west-1. +persistence.config.dynamodb.secretKey.label = AWS salainen avain +persistence.config.dynamodb.secretKey.description = AWS salainen avain
Anna joko 1) pääsyavain ja salainen avain tai 2) AWS-pääsytunnustiedosto ja profiilin nimi. +persistence.config.dynamodb.table.label = Taulu +persistence.config.dynamodb.table.description = Taulun nimi.
Määritä tämä parametri tauluetuliitteen sijaan käyttääksesi uutta optimoitua taulumuotoa. +persistence.config.dynamodb.tablePrefix.label = Tauluetuliite +persistence.config.dynamodb.tablePrefix.description = Vanhentunut\: Taulun etuliitettä käytetään luotujen taulujen nimessä.
Oletus on "openhab-" +persistence.config.dynamodb.writeCapacityUnits.label = Kirjoituksen kapasiteetti +persistence.config.dynamodb.writeCapacityUnits.description = Kirjoitukseen varattu kapasiteetti.
Oletus on 1. + +# service + +service.persistence.dynamodb.label = DynamoDB tallennuspalvelu diff --git a/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt_it.properties b/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt_it.properties new file mode 100644 index 0000000000000..63e57ae950ce4 --- /dev/null +++ b/bundles/org.openhab.voice.voskstt/src/main/resources/OH-INF/i18n/voskstt_it.properties @@ -0,0 +1,20 @@ +voice.config.voskstt.errorMessage.label = Messaggio Errore +voice.config.voskstt.errorMessage.description = Messaggio da dire quando si è verificato un errore. (Vuoto per disabilitato) +voice.config.voskstt.group.messages.label = Messaggi Info +voice.config.voskstt.group.messages.description = Configurare i messaggi informativi del servizio. +voice.config.voskstt.group.stt.label = Configurazione STT +voice.config.voskstt.group.stt.description = Configura dizione del testo. +voice.config.voskstt.maxSilenceSeconds.label = Max Silenzio in Secondi +voice.config.voskstt.maxSilenceSeconds.description = Funziona solo quando la modalità SingleUtterance è disabilitata, max secondi senza ottenere nuove trascrizioni per interrompere l'ascolto. +voice.config.voskstt.maxTranscriptionSeconds.label = Max Secondi per Trascrizione +voice.config.voskstt.maxTranscriptionSeconds.description = Massimo secondi di attesa per forzare l'arresto della trascrizione. +voice.config.voskstt.noResultsMessage.label = Messaggio No Risultati +voice.config.voskstt.noResultsMessage.description = Messaggio da dire quando non si ottengono risultati. +voice.config.voskstt.preloadModel.label = Modello Preload +voice.config.voskstt.preloadModel.description = Mantieni il modello di lingua caricato. Se Vero il modello è ricaricato all'aggiornamento della configurazione, se False il modello sarà caricato e scaricato ad ogni esecuzione. Altrimenti Ripiegherà cercherà di caricare il modello quando eseguito se non è stato in grado di caricarlo prima. +voice.config.voskstt.singleUtteranceMode.label = Modalità Single Utterance +voice.config.voskstt.singleUtteranceMode.description = Quando abilitato il riconoscimento interrompe l'ascolto dopo un singolo enunciato. + +# service + +service.voice.voskstt.label = Vosk Speech-to-Text diff --git a/bundles/org.openhab.voice.watsonstt/src/main/resources/OH-INF/i18n/watsonstt_it.properties b/bundles/org.openhab.voice.watsonstt/src/main/resources/OH-INF/i18n/watsonstt_it.properties new file mode 100644 index 0000000000000..30cd68a1eb396 --- /dev/null +++ b/bundles/org.openhab.voice.watsonstt/src/main/resources/OH-INF/i18n/watsonstt_it.properties @@ -0,0 +1,26 @@ +voice.config.watsonstt.apiKey.label = Chiave API +voice.config.watsonstt.apiKey.description = Chiave API per l'istanza Speech-to-Text creata su IBM Cloud. +voice.config.watsonstt.backgroundAudioSuppression.label = Soppressione Audio di Fondo +voice.config.watsonstt.backgroundAudioSuppression.description = Utilizzare il parametro per sopprimere le conversazioni laterali o il rumore di fondo. +voice.config.watsonstt.group.authentication.label = Autenticazione +voice.config.watsonstt.group.authentication.description = Informazioni per la connessione all'istanza Watson Speech-to-Text. +voice.config.watsonstt.group.stt.label = Configurazione STT +voice.config.watsonstt.group.stt.description = Parametri per l'API Watson Speech-to-Text. +voice.config.watsonstt.inactivityTimeout.label = Timeout Inattività +voice.config.watsonstt.inactivityTimeout.description = Il tempo in secondi dopo il quale, se viene rilevato solo silenzio (nessun discorso) nell'audio, la connessione viene chiusa. +voice.config.watsonstt.instanceUrl.label = Istanza URL +voice.config.watsonstt.instanceUrl.description = Url per l'istanza Speech-to-Text creata su IBM Cloud. +voice.config.watsonstt.noResultsMessage.label = Messaggio per Nessun Risultato +voice.config.watsonstt.noResultsMessage.description = Messaggio da dire quanto nessuna trascrizione è stata fatta. +voice.config.watsonstt.optOutLogging.label = Opt Out Logging +voice.config.watsonstt.optOutLogging.description = Per impostazione predefinita, tutte le richieste dei servizi IBM Watson™ e i loro risultati sono tracciati. La registrazione viene effettuata solo per migliorare i servizi per i futuri utenti. I dati registrati non sono condivisi o resi pubblici. +voice.config.watsonstt.redaction.label = Redazione +voice.config.watsonstt.redaction.description = Se vero, il servizio redige, o maschera, in dati numerici le trascrizioni finali. (Non disponibile per tutte le lingue) +voice.config.watsonstt.smartFormatting.label = Formattazione Intelligente +voice.config.watsonstt.smartFormatting.description = Se vero, il servizio converte date, orari, cifre decimali e numeri, numeri di telefono, valute e indirizzi internet in modo più leggibile. (Non disponibile per tutti le lingue e regioni) +voice.config.watsonstt.speechDetectorSensitivity.label = Sensibilità Rivelatore Vocale +voice.config.watsonstt.speechDetectorSensitivity.description = Utilizzare il parametro per sopprimere gli inserimenti di parole da musica, tosse e altri eventi non vocali. + +# service + +service.voice.watsonstt.label = IBM Watson Speech-to-Text From d868f0bbd9c43fcc7d9b22ace924f7a1a0da4b00 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Sun, 20 Feb 2022 23:15:42 +0100 Subject: [PATCH 15/19] [flicbutton] Add missing i18n translations properties (#12333) Related to #9234 Signed-off-by: Laurent Garnier --- .../OH-INF/i18n/flicbutton.properties | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/i18n/flicbutton.properties diff --git a/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/i18n/flicbutton.properties b/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/i18n/flicbutton.properties new file mode 100644 index 0000000000000..d88d188f70fe3 --- /dev/null +++ b/bundles/org.openhab.binding.flicbutton/src/main/resources/OH-INF/i18n/flicbutton.properties @@ -0,0 +1,20 @@ +# binding + +binding.flicbutton.name = FlicButton Binding +binding.flicbutton.description = This is the binding for Flic buttons by Shortcut Labs. + +# thing types + +thing-type.flicbutton.button.label = Flic Button +thing-type.flicbutton.button.description = The thing(-type) representing a Flic Button +thing-type.flicbutton.flicd-bridge.label = FlicButton Bridge +thing-type.flicbutton.flicd-bridge.description = This bridge represents a running instance of the fliclib-linux-hci server (flicd). + +# thing types config + +thing-type.config.flicbutton.button.address.label = Address +thing-type.config.flicbutton.button.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format +thing-type.config.flicbutton.flicd-bridge.hostname.label = Flic Daemon (flicd) Hostname +thing-type.config.flicbutton.flicd-bridge.hostname.description = IP or Host name of the Flic daemon (flicd). +thing-type.config.flicbutton.flicd-bridge.port.label = Flic Daemon (flicd) Port +thing-type.config.flicbutton.flicd-bridge.port.description = Port where flicd is running. Defaults to 5551. From 6ef311304f1e6403f3928f7c4ff4bec0d6a99123 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Sun, 20 Feb 2022 23:16:16 +0100 Subject: [PATCH 16/19] Remove some of the empty XML tag "description" (#12332) Signed-off-by: Laurent Garnier --- .../src/main/resources/OH-INF/thing/thing-types.xml | 2 -- .../src/main/resources/OH-INF/thing/bridge.xml | 1 - .../src/main/resources/OH-INF/thing/thing-types.xml | 1 - .../src/main/resources/OH-INF/thing/channels.xml | 8 -------- .../src/main/resources/OH-INF/thing/controller.xml | 1 - .../src/main/resources/OH-INF/thing/channels.xml | 4 ---- .../src/main/resources/OH-INF/thing/SMO40.xml | 3 --- .../src/main/resources/OH-INF/thing/f1x45.xml | 3 --- .../src/main/resources/OH-INF/thing/f1x55.xml | 3 --- .../src/main/resources/OH-INF/thing/f470.xml | 3 --- .../src/main/resources/OH-INF/thing/f750.xml | 3 --- .../src/main/resources/OH-INF/thing/partition.xml | 1 - .../src/main/resources/OH-INF/thing/relay.xml | 1 - .../src/main/resources/OH-INF/thing/thing-types.xml | 1 - .../src/main/resources/OH-INF/thing/thing-types.xml | 2 -- 15 files changed, 37 deletions(-) diff --git a/bundles/org.openhab.binding.boschindego/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.boschindego/src/main/resources/OH-INF/thing/thing-types.xml index 431de7e9ecaa7..6521a1ddbf6a4 100644 --- a/bundles/org.openhab.binding.boschindego/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.boschindego/src/main/resources/OH-INF/thing/thing-types.xml @@ -85,13 +85,11 @@ String - Dimmer - diff --git a/bundles/org.openhab.binding.digiplex/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.digiplex/src/main/resources/OH-INF/thing/bridge.xml index 4403ca5354540..2bf83c7c8ebe6 100644 --- a/bundles/org.openhab.binding.digiplex/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.digiplex/src/main/resources/OH-INF/thing/bridge.xml @@ -17,7 +17,6 @@ communication - diff --git a/bundles/org.openhab.binding.folding/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.folding/src/main/resources/OH-INF/thing/thing-types.xml index 19c5612c27569..d065754d40b8c 100644 --- a/bundles/org.openhab.binding.folding/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.folding/src/main/resources/OH-INF/thing/thing-types.xml @@ -22,7 +22,6 @@ - 36330 diff --git a/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/channels.xml index 8292a30a22d57..393753736a5cb 100644 --- a/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/channels.xml @@ -30,7 +30,6 @@ - @@ -65,7 +64,6 @@ - @@ -81,7 +79,6 @@ - @@ -111,7 +108,6 @@ - @@ -146,7 +142,6 @@ - @@ -176,7 +171,6 @@ - @@ -206,7 +200,6 @@ - @@ -278,7 +271,6 @@ - diff --git a/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/controller.xml b/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/controller.xml index 74793ac6cf648..46610894ff31b 100644 --- a/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/controller.xml +++ b/bundles/org.openhab.binding.ihc/src/main/resources/OH-INF/thing/controller.xml @@ -7,7 +7,6 @@ extensible="switch, contact, number, dimmer, datetime, string, rollershutter, rf-device-low-battery, rf-device-signal-strength, push-button-trigger"> - diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/thing/channels.xml index 3df9a8394112d..40549317223fb 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/resources/OH-INF/thing/channels.xml @@ -65,28 +65,24 @@ Color - Color - Color - trigger - diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/SMO40.xml b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/SMO40.xml index 7d7baed05b5fa..3d471b9df85ce 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/SMO40.xml +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/SMO40.xml @@ -6,7 +6,6 @@ - @@ -67,7 +66,6 @@ - @@ -127,7 +125,6 @@ - diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x45.xml b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x45.xml index a68cfaffb56c0..68d005bd89b87 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x45.xml +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x45.xml @@ -6,7 +6,6 @@ - @@ -67,7 +66,6 @@ - @@ -127,7 +125,6 @@ - diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x55.xml b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x55.xml index 1afdb4d721e61..d8c9a4b56db0b 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x55.xml +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f1x55.xml @@ -6,7 +6,6 @@ - @@ -67,7 +66,6 @@ - @@ -127,7 +125,6 @@ - diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f470.xml b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f470.xml index 0289070610557..1307eb649030e 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f470.xml +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f470.xml @@ -6,7 +6,6 @@ - @@ -67,7 +66,6 @@ - @@ -127,7 +125,6 @@ - diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f750.xml b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f750.xml index 68a8bc0b827ef..8f5823028d2eb 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f750.xml +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/resources/OH-INF/thing/f750.xml @@ -6,7 +6,6 @@ - @@ -67,7 +66,6 @@ - @@ -127,7 +125,6 @@ - diff --git a/bundles/org.openhab.binding.satel/src/main/resources/OH-INF/thing/partition.xml b/bundles/org.openhab.binding.satel/src/main/resources/OH-INF/thing/partition.xml index b8d1262bb47bd..1115086e0980a 100644 --- a/bundles/org.openhab.binding.satel/src/main/resources/OH-INF/thing/partition.xml +++ b/bundles/org.openhab.binding.satel/src/main/resources/OH-INF/thing/partition.xml @@ -41,7 +41,6 @@ access to some portion of the premises to selected users.]]> - diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml index 9e33f9f68b4c0..6f3e069c369ac 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml @@ -527,7 +527,6 @@ Number - diff --git a/bundles/org.openhab.binding.spotify/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.spotify/src/main/resources/OH-INF/thing/thing-types.xml index b591562ad5ec2..ad073b4c41c9e 100644 --- a/bundles/org.openhab.binding.spotify/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.spotify/src/main/resources/OH-INF/thing/thing-types.xml @@ -194,7 +194,6 @@ String - diff --git a/bundles/org.openhab.binding.venstarthermostat/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.venstarthermostat/src/main/resources/OH-INF/thing/thing-types.xml index b027573541532..baf27d57d69d7 100644 --- a/bundles/org.openhab.binding.venstarthermostat/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.venstarthermostat/src/main/resources/OH-INF/thing/thing-types.xml @@ -93,12 +93,10 @@ - password - From cabcaa969803b0e7981ae514fa3e68a57790eec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Sun, 20 Feb 2022 23:24:49 +0100 Subject: [PATCH 17/19] [network] Adressing issue #11503 (#12316) Signed-off-by: clinique --- .../internal/NetworkBindingConfiguration.java | 6 +-- .../NetworkBindingConfigurationListener.java | 3 ++ .../network/internal/PresenceDetection.java | 42 ++++--------------- .../internal/SpeedTestConfiguration.java | 32 +++++++++----- .../internal/WakeOnLanPacketSender.java | 22 +++++----- .../dhcp/DHCPPacketListenerServer.java | 2 +- .../internal/handler/SpeedTestHandler.java | 8 ++-- .../network/internal/utils/LatencyParser.java | 4 +- .../network/internal/utils/NetworkUtils.java | 28 +++++++++---- .../network/internal/utils/PingResult.java | 9 ++-- .../resources/OH-INF/thing/thing-types.xml | 3 ++ .../internal/PresenceDetectionTest.java | 12 +++--- .../internal/WakeOnLanPacketSenderTest.java | 6 ++- .../network/internal/dhcp/DHCPTest.java | 2 + .../cache/ExpiringCacheAsyncTest.java | 2 + .../cache/ExpiringCacheHelper.java | 5 ++- .../internal/utils/LatencyParserTest.java | 2 + 17 files changed, 105 insertions(+), 83 deletions(-) diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java index 581b609614c4c..48704afb9e82b 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java @@ -29,11 +29,11 @@ @NonNullByDefault public class NetworkBindingConfiguration { - public Boolean allowSystemPings = true; - public Boolean allowDHCPlisten = true; + public boolean allowSystemPings = true; + public boolean allowDHCPlisten = true; public BigDecimal cacheDeviceStateTimeInMS = BigDecimal.valueOf(2000); public String arpPingToolPath = "arping"; - public @NonNullByDefault({}) ArpPingUtilEnum arpPingUtilMethod; + public ArpPingUtilEnum arpPingUtilMethod = ArpPingUtilEnum.DISABLED; // For backwards compatibility reasons, the default is to use the ping method execution time as latency value public boolean preferResponseTimeAsLatency = false; diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java index b9a74762672e8..73f2d118812f7 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.network.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Listener for binding configuration changes. * * @author Andreas Hirsch - Initial contribution */ +@NonNullByDefault public interface NetworkBindingConfigurationListener { void bindingConfigurationChanged(); diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java index 7e6de77667040..2e3f702fb5e53 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java @@ -57,10 +57,9 @@ public class PresenceDetection implements IPRequestReceivedCallback { /// Configuration variables private boolean useDHCPsniffing = false; - private String arpPingState = "Disabled"; private String ipPingState = "Disabled"; protected String arpPingUtilPath = ""; - protected ArpPingUtilEnum arpPingMethod = ArpPingUtilEnum.UNKNOWN_TOOL; + protected ArpPingUtilEnum arpPingMethod = ArpPingUtilEnum.DISABLED; protected @Nullable IpPingMethodEnum pingMethod = null; private boolean iosDevice; private Set tcpPorts = new HashSet<>(); @@ -184,40 +183,13 @@ public void setUseIcmpPing(@Nullable Boolean useSystemPing) { * it will be disabled as well. * * @param enable Enable or disable ARP ping - * @param arpPingUtilPath c + * @param destinationAddress target ip address */ private void setUseArpPing(boolean enable, @Nullable InetAddress destinationAddress) { if (!enable || arpPingUtilPath.isEmpty()) { - arpPingState = "Disabled"; - arpPingMethod = ArpPingUtilEnum.UNKNOWN_TOOL; - return; - } else if (destinationAddress == null || !(destinationAddress instanceof Inet4Address)) { - arpPingState = "Destination is not a valid IPv4 address"; - arpPingMethod = ArpPingUtilEnum.UNKNOWN_TOOL; - return; - } - - switch (arpPingMethod) { - case UNKNOWN_TOOL: { - arpPingState = "Unknown arping tool"; - break; - } - case THOMAS_HABERT_ARPING: { - arpPingState = "Arping tool by Thomas Habets"; - break; - } - case THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT: { - arpPingState = "Arping tool by Thomas Habets (old version)"; - break; - } - case ELI_FULKERSON_ARP_PING_FOR_WINDOWS: { - arpPingState = "Eli Fulkerson ARPing tool for Windows"; - break; - } - case IPUTILS_ARPING: { - arpPingState = "Ipuitls Arping"; - break; - } + arpPingMethod = ArpPingUtilEnum.DISABLED; + } else if (!(destinationAddress instanceof Inet4Address)) { + arpPingMethod = ArpPingUtilEnum.DISABLED_INVALID_IP; } } @@ -234,7 +206,7 @@ public void setUseArpPing(boolean enable, String arpPingUtilPath, ArpPingUtilEnu } public String getArpPingState() { - return arpPingState; + return arpPingMethod.description; } public String getIPPingState() { @@ -318,7 +290,7 @@ public boolean performPresenceDetection(boolean waitForDetectionToFinish) { if (pingMethod != null) { detectionChecks += 1; } - if (arpPingMethod != ArpPingUtilEnum.UNKNOWN_TOOL) { + if (arpPingMethod.canProceed) { interfaceNames = networkUtils.getInterfaceNames(); detectionChecks += interfaceNames.size(); } diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/SpeedTestConfiguration.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/SpeedTestConfiguration.java index 65c5e4edf601a..3ae4e8a04c221 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/SpeedTestConfiguration.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/SpeedTestConfiguration.java @@ -12,25 +12,37 @@ */ package org.openhab.binding.network.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * The {@link SpeedTestConfiguration} is the class used to match the * thing configuration. * * @author Gaël L'hopital - Initial contribution */ +@NonNullByDefault public class SpeedTestConfiguration { - public Integer refreshInterval = 20; - public Integer initialDelay = 5; - public Integer uploadSize = 1000000; - public Integer maxTimeout = 3; - private String url; - private String fileName; + public int refreshInterval = 20; + public int initialDelay = 5; + public int uploadSize = 1000000; + public int maxTimeout = 3; + private @Nullable String url; + private @Nullable String fileName; - public String getUploadURL() { - return url + (url.endsWith("/") ? "" : "/"); + public @Nullable String getUploadURL() { + String localUrl = url; + if (localUrl != null) { + localUrl += localUrl.endsWith("/") ? "" : "/"; + } + return localUrl; } - public String getDownloadURL() { - return getUploadURL() + fileName; + public @Nullable String getDownloadURL() { + String result = getUploadURL(); + if (result != null && fileName != null) { + result += fileName; + } + return result; } } diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/WakeOnLanPacketSender.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/WakeOnLanPacketSender.java index 4741a077776a2..c84c58638d017 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/WakeOnLanPacketSender.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/WakeOnLanPacketSender.java @@ -13,13 +13,18 @@ package org.openhab.binding.network.internal; import java.io.IOException; -import java.net.*; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; import java.util.Arrays; import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.net.NetUtil; @@ -48,11 +53,8 @@ public class WakeOnLanPacketSender { private final String macAddress; - @Nullable - private final String hostname; - - @Nullable - private final Integer port; + private final @Nullable String hostname; + private final @Nullable Integer port; private final Consumer magicPacketMacSender; private final Consumer magicPacketIpSender; @@ -131,10 +133,10 @@ private void sendMagicPacketViaMac(byte[] magicPacket) { private void sendMagicPacketViaIp(byte[] magicPacket) { try (DatagramSocket socket = new DatagramSocket()) { - if (!StringUtils.isEmpty(this.hostname)) { + if (hostname != null && !hostname.isBlank()) { logger.debug("Sending Wake-on-LAN Packet via IP Address"); - SocketAddress socketAddress = new InetSocketAddress(this.hostname, - Objects.requireNonNullElse(this.port, WOL_UDP_PORT)); + SocketAddress socketAddress = new InetSocketAddress(hostname, + Objects.requireNonNullElse(port, WOL_UDP_PORT)); sendMagicPacketToIp(magicPacket, socket, socketAddress); } else { throw new IllegalStateException("Hostname is not set!"); diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/dhcp/DHCPPacketListenerServer.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/dhcp/DHCPPacketListenerServer.java index 25c9f7a494a48..2f8b8df2e723b 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/dhcp/DHCPPacketListenerServer.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/dhcp/DHCPPacketListenerServer.java @@ -70,7 +70,7 @@ protected void receivePacket(DHCPPacket request, @Nullable InetAddress udpRemote Byte dhcpMessageType = request.getDHCPMessageType(); - if (dhcpMessageType != DHCPPacket.DHCPREQUEST) { + if (dhcpMessageType == null || dhcpMessageType != DHCPPacket.DHCPREQUEST) { return; // skipping non DHCPREQUEST message types } diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/SpeedTestHandler.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/SpeedTestHandler.java index fc1ce354ee344..2a0e002b0f36d 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/SpeedTestHandler.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/SpeedTestHandler.java @@ -69,7 +69,8 @@ public void initialize() { } private synchronized void startSpeedTest() { - if (speedTestSocket == null) { + String url = configuration.getDownloadURL(); + if (speedTestSocket == null && url != null) { logger.debug("Network speedtest started"); final SpeedTestSocket socket = new SpeedTestSocket(1500); speedTestSocket = socket; @@ -78,7 +79,7 @@ private synchronized void startSpeedTest() { updateState(CHANNEL_TEST_START, new DateTimeType()); updateState(CHANNEL_TEST_END, UnDefType.NULL); updateProgress(new QuantityType<>(0, Units.PERCENT)); - socket.startDownload(configuration.getDownloadURL()); + socket.startDownload(url); } else { logger.info("A speedtest is already in progress, will retry on next refresh"); } @@ -109,7 +110,8 @@ public void onCompletion(final @Nullable SpeedTestReport testReport) { switch (testReport.getSpeedTestMode()) { case DOWNLOAD: updateState(CHANNEL_RATE_DOWN, quantity); - if (speedTestSocket != null && configuration != null) { + String url = configuration.getUploadURL(); + if (speedTestSocket != null && url != null) { speedTestSocket.startUpload(configuration.getUploadURL(), configuration.uploadSize); } break; diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java index 92a714ba3176d..6110e71bdf922 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java @@ -16,6 +16,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +25,10 @@ * * @author Andreas Hirsch - Initial contribution */ +@NonNullByDefault public class LatencyParser { - private static Pattern LATENCY_PATTERN = Pattern.compile(".*time=(.*) ?ms"); + private static final Pattern LATENCY_PATTERN = Pattern.compile(".*time=(.*) ?ms"); private final Logger logger = LoggerFactory.getLogger(LatencyParser.class); // This is how the input looks like on Mac and Linux: diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java index de8ca64cddac3..d657721807b2b 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java @@ -203,19 +203,19 @@ public IpPingMethodEnum determinePingMethod() { public ArpPingUtilEnum determineNativeARPpingMethod(String arpToolPath) { String result = ExecUtil.executeCommandLineAndWaitResponse(Duration.ofMillis(100), arpToolPath, "--help"); if (result == null || result.isBlank()) { - return ArpPingUtilEnum.UNKNOWN_TOOL; + return ArpPingUtilEnum.DISABLED_UNKNOWN_TOOL; } else if (result.contains("Thomas Habets")) { if (result.matches("(?s)(.*)w sec Specify a timeout(.*)")) { return ArpPingUtilEnum.THOMAS_HABERT_ARPING; } else { return ArpPingUtilEnum.THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT; } - } else if (result.contains("-w timeout")) { + } else if (result.contains("-w timeout") || result.contains("-w ")) { return ArpPingUtilEnum.IPUTILS_ARPING; } else if (result.contains("Usage: arp-ping.exe")) { return ArpPingUtilEnum.ELI_FULKERSON_ARP_PING_FOR_WINDOWS; } - return ArpPingUtilEnum.UNKNOWN_TOOL; + return ArpPingUtilEnum.DISABLED_UNKNOWN_TOOL; } public enum IpPingMethodEnum { @@ -292,11 +292,21 @@ public Optional nativePing(@Nullable IpPingMethodEnum method, String } public enum ArpPingUtilEnum { - UNKNOWN_TOOL, - IPUTILS_ARPING, - THOMAS_HABERT_ARPING, - THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT, - ELI_FULKERSON_ARP_PING_FOR_WINDOWS + DISABLED("Disabled", false), + DISABLED_INVALID_IP("Destination is not a valid IPv4 address", false), + DISABLED_UNKNOWN_TOOL("Unknown arping tool", false), + IPUTILS_ARPING("Iputils Arping", true), + THOMAS_HABERT_ARPING("Arping tool by Thomas Habets", true), + THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT("Arping tool by Thomas Habets (old version)", true), + ELI_FULKERSON_ARP_PING_FOR_WINDOWS("Eli Fulkerson ARPing tool for Windows", true); + + public final String description; + public final boolean canProceed; + + ArpPingUtilEnum(String description, boolean canProceed) { + this.description = description; + this.canProceed = canProceed; + } } /** @@ -317,7 +327,7 @@ public Optional nativeARPPing(@Nullable ArpPingUtilEnum arpingTool, String interfaceName, String ipV4address, int timeoutInMS) throws IOException, InterruptedException { double execStartTimeInMS = System.currentTimeMillis(); - if (arpUtilPath == null || arpingTool == null || arpingTool == ArpPingUtilEnum.UNKNOWN_TOOL) { + if (arpUtilPath == null || arpingTool == null || !arpingTool.canProceed) { return Optional.empty(); } Process proc; diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java index e1e674c0f008d..d9fea64551c0a 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java @@ -14,15 +14,18 @@ import java.util.Optional; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Information about the ping result. * * @author Andreas Hirsch - Initial contribution */ +@NonNullByDefault public class PingResult { private boolean success; - private Double responseTimeInMS; + private Optional responseTimeInMS = Optional.empty(); private double executionTimeInMS; /** @@ -46,14 +49,14 @@ public boolean isSuccess() { * by ping command is not available. */ public Optional getResponseTimeInMS() { - return responseTimeInMS == null ? Optional.empty() : Optional.of(responseTimeInMS); + return responseTimeInMS; } /** * @param responseTimeInMS Response time in ms which was returned by the ping command. */ public void setResponseTimeInMS(double responseTimeInMS) { - this.responseTimeInMS = responseTimeInMS; + this.responseTimeInMS = Optional.of(responseTimeInMS); } @Override diff --git a/bundles/org.openhab.binding.network/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.network/src/main/resources/OH-INF/thing/thing-types.xml index ba3d4b1d4a2ca..23cd4cc0bdc35 100644 --- a/bundles/org.openhab.binding.network/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.network/src/main/resources/OH-INF/thing/thing-types.xml @@ -207,16 +207,19 @@ States whether a device is online or offline + Number:Time States the latency time + DateTime States the last seen date/time + time diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java index a42e9fbe00e3d..f5c75725c8cd5 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java @@ -178,8 +178,8 @@ public void cacheTest() throws InterruptedException, IOException { @Test public void reuseValueTests() throws InterruptedException, IOException { - final long START_TIME = 1000L; - when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(START_TIME)); + final long startTime = 1000L; + when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(startTime)); // The PresenceDetectionValue.getLowestLatency() should return the smallest latency PresenceDetectionValue v = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 20); @@ -188,19 +188,19 @@ public void reuseValueTests() throws InterruptedException, IOException { assertThat(v.getLowestLatency(), is(19.0)); // Advance in time but not expire the cache (1ms left) - final long ALMOST_EXPIRE = START_TIME + CACHETIME - 1; - when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ALMOST_EXPIRE)); + final long almostExpire = startTime + CACHETIME - 1; + when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(almostExpire)); // Updating should reset the expire timer of the cache v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 28); assertEquals(v, v2); assertThat(v2.getLowestLatency(), is(19.0)); assertThat(ExpiringCacheHelper.expireTime(subject.cache), - is(TimeUnit.MILLISECONDS.toNanos(ALMOST_EXPIRE + CACHETIME))); + is(TimeUnit.MILLISECONDS.toNanos(almostExpire + CACHETIME))); // Cache expire. A new PresenceDetectionValue instance will be returned when(subject.cache.getCurrentNanoTime()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ALMOST_EXPIRE + CACHETIME + CACHETIME + 1)); + .thenReturn(TimeUnit.MILLISECONDS.toNanos(almostExpire + CACHETIME + CACHETIME + 1)); v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 25); assertNotEquals(v, v2); assertThat(v2.getLowestLatency(), is(25.0)); diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/WakeOnLanPacketSenderTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/WakeOnLanPacketSenderTest.java index 5903941e4de66..16676eeb2cd6a 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/WakeOnLanPacketSenderTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/WakeOnLanPacketSenderTest.java @@ -22,6 +22,8 @@ import java.net.DatagramSocket; import java.util.Arrays; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -33,6 +35,7 @@ * @author Wouter Born - Initial contribution */ @Timeout(value = 10) +@NonNullByDefault public class WakeOnLanPacketSenderTest { private void assertValidMagicPacket(byte[] macBytes, byte[] packet) { @@ -102,7 +105,8 @@ public void sendWithHostnameNull() { assertThrows(IllegalStateException.class, () -> sendWOLTest(null, 4444)); } - private void sendWOLTest(String hostname, Integer port) throws InterruptedException, IOException { + private void sendWOLTest(@Nullable String hostname, @Nullable Integer port) + throws InterruptedException, IOException { DatagramSocket socket = new DatagramSocket(4444); byte[] buf = new byte[256]; diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/dhcp/DHCPTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/dhcp/DHCPTest.java index 1f313162fea55..550a59a5c1edb 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/dhcp/DHCPTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/dhcp/DHCPTest.java @@ -22,6 +22,7 @@ import java.net.InetAddress; import java.net.SocketException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.openhab.binding.network.internal.dhcp.DHCPPacket.BadPacketException; @@ -30,6 +31,7 @@ * * @author David Graeff - Initial contribution */ +@NonNullByDefault public class DHCPTest { @Test public void testService() throws SocketException { diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheAsyncTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheAsyncTest.java index fa8fac825a45b..cd506b080999f 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheAsyncTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheAsyncTest.java @@ -17,6 +17,7 @@ import java.util.function.Consumer; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.openhab.binding.network.internal.toberemoved.cache.ExpiringCacheAsync.ExpiringCacheUpdate; @@ -26,6 +27,7 @@ * * @author David Graeff - Initial contribution */ +@NonNullByDefault public class ExpiringCacheAsyncTest { @Test public void testConstructorWrongCacheTime() { diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheHelper.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheHelper.java index 7101d9dad4f46..2142756a6bac8 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheHelper.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/toberemoved/cache/ExpiringCacheHelper.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.network.internal.toberemoved.cache; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Helper class to make the package private cacheUpdater field available for tests. * - * @author David Graeff + * @author David Graeff - Initial Contribution */ +@NonNullByDefault public class ExpiringCacheHelper { public static long expireTime(@SuppressWarnings("rawtypes") ExpiringCacheAsync cache) { return cache.expiresAt; diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java index 96ecfbc7ac508..71d7cfb1d691d 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java @@ -16,6 +16,7 @@ import java.util.Optional; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; /** @@ -23,6 +24,7 @@ * * @author Andreas Hirsch - Initial contribution */ +@NonNullByDefault public class LatencyParserTest { @Test From 00e16f5b4e7c6b47ff24ff70dd2f4fd9b6d77a47 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sun, 20 Feb 2022 23:42:45 +0100 Subject: [PATCH 18/19] [flicbutton] Fix SAT `@NonNull` findings (#12334) Signed-off-by: Wouter Born --- .../discovery/FlicSimpleclientDiscoveryServiceImpl.java | 5 ++--- .../internal/handler/FlicButtonEventListener.java | 8 +++----- .../internal/handler/FlicDaemonBridgeHandler.java | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java index 3caa071093be2..d43918e326379 100644 --- a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/discovery/FlicSimpleclientDiscoveryServiceImpl.java @@ -14,7 +14,6 @@ import java.io.IOException; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants; @@ -94,7 +93,7 @@ public void onGetInfoResponse(@Nullable BluetoothControllerState bluetoothContro boolean currentlyNoSpaceForNewConnection, Bdaddr @Nullable [] verifiedButtons) throws IOException { for (final @Nullable Bdaddr bdaddr : verifiedButtons) { if (bdaddr != null) { - flicButtonDiscovered((@NonNull Bdaddr) bdaddr); + flicButtonDiscovered(bdaddr); } } } @@ -109,7 +108,7 @@ protected void startBackgroundDiscovery() { public void onNewVerifiedButton(@Nullable Bdaddr bdaddr) throws IOException { logger.debug("A new Flic button was added by an external flicd client: {}", bdaddr); if (bdaddr != null) { - flicButtonDiscovered((@NonNull Bdaddr) bdaddr); + flicButtonDiscovered(bdaddr); } } }); diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java index 77c2d0780ae53..1c33e0896dafe 100644 --- a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicButtonEventListener.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.util.concurrent.Semaphore; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants; @@ -58,7 +57,7 @@ public synchronized void onCreateConnectionChannelResponse(@Nullable ButtonConne logger.debug("Create response {}: {}, {}", channel.getBdaddr(), createConnectionChannelError, connectionStatus); // Handling does not differ from Status change, so redirect if (connectionStatus != null) { - thingHandler.initializeStatus((@NonNull ConnectionStatus) connectionStatus); + thingHandler.initializeStatus(connectionStatus); channelResponseSemaphore.release(); } } @@ -76,7 +75,7 @@ public void onConnectionStatusChanged(@Nullable ButtonConnectionChannel channel, logger.trace("New status for {}: {}", channel.getBdaddr(), connectionStatus + (connectionStatus == ConnectionStatus.Disconnected ? ", " + disconnectReason : "")); if (connectionStatus != null) { - thingHandler.connectionStatusChanged((@NonNull ConnectionStatus) connectionStatus, disconnectReason); + thingHandler.connectionStatusChanged(connectionStatus, disconnectReason); } } @@ -97,8 +96,7 @@ public void onButtonSingleOrDoubleClickOrHold(@Nullable ButtonConnectionChannel @Nullable ClickType clickType, boolean wasQueued, int timeDiff) throws IOException { // Handling does not differ from up/down events, so redirect if (channel != null && clickType != null) { - onButtonUpOrDown((@NonNull ButtonConnectionChannel) channel, (@NonNull ClickType) clickType, wasQueued, - timeDiff); + onButtonUpOrDown(channel, clickType, wasQueued, timeDiff); } } } diff --git a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java index cba4319de0e45..19e16114c3e00 100644 --- a/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java +++ b/bundles/org.openhab.binding.flicbutton/src/main/java/org/openhab/binding/flicbutton/internal/handler/FlicDaemonBridgeHandler.java @@ -19,7 +19,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.flicbutton.internal.discovery.FlicButtonDiscoveryService; @@ -87,8 +86,9 @@ private void initConfigParameters() { } private void activateButtonDiscoveryService() { + FlicClient flicClient = this.flicClient; if (flicClient != null) { - buttonDiscoveryService.activate((@NonNull FlicClient) flicClient); + buttonDiscoveryService.activate(flicClient); } else { throw new IllegalStateException("flicClient not properly initialized"); } From 9e6b952b66afaff04c4a29ce2494679bfe8dd6d2 Mon Sep 17 00:00:00 2001 From: eugen Date: Tue, 22 Feb 2022 06:50:19 +0100 Subject: [PATCH 19/19] fix storage usage (#12305) Signed-off-by: Eugen Freiter --- .../io/homekit/internal/HomekitChangeListener.java | 5 ++--- .../java/org/openhab/io/homekit/internal/HomekitImpl.java | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java index e3148fd199119..2a44a93b80a0c 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java @@ -35,7 +35,6 @@ import org.openhab.core.items.MetadataKey; import org.openhab.core.items.MetadataRegistry; import org.openhab.core.storage.Storage; -import org.openhab.core.storage.StorageService; import org.openhab.io.homekit.internal.accessories.HomekitAccessoryFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,11 +78,11 @@ public class HomekitChangeListener implements ItemRegistryChangeListener { private final Debouncer applyUpdatesDebouncer; HomekitChangeListener(ItemRegistry itemRegistry, HomekitSettings settings, MetadataRegistry metadataRegistry, - StorageService storageService) { + Storage storage) { this.itemRegistry = itemRegistry; this.settings = settings; this.metadataRegistry = metadataRegistry; - storage = storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY); + this.storage = storage; this.applyUpdatesDebouncer = new Debouncer("update-homekit-devices", scheduler, Duration.ofMillis(1000), Clock.systemUTC(), this::applyUpdates); metadataChangeListener = new RegistryChangeListener() { diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java index 77c78a8fc0e82..7fa41890faeb2 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java @@ -37,6 +37,7 @@ import org.openhab.core.net.CidrAddress; import org.openhab.core.net.NetworkAddressChangeListener; import org.openhab.core.net.NetworkAddressService; +import org.openhab.core.storage.Storage; import org.openhab.core.storage.StorageService; import org.openhab.io.homekit.Homekit; import org.osgi.framework.Constants; @@ -70,6 +71,7 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener { private final NetworkAddressService networkAddressService; private final ConfigurationAdmin configAdmin; + private final Storage storage; private HomekitAuthInfoImpl authInfo; private HomekitSettings settings; @@ -92,11 +94,11 @@ public HomekitImpl(@Reference StorageService storageService, @Reference ItemRegi this.configAdmin = configAdmin; this.settings = processConfig(properties); this.mdnsClient = mdnsClient; + this.storage = storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY); networkAddressService.addNetworkAddressChangeListener(this); - this.changeListener = new HomekitChangeListener(itemRegistry, settings, metadataRegistry, storageService); + this.changeListener = new HomekitChangeListener(itemRegistry, settings, metadataRegistry, storage); try { - authInfo = new HomekitAuthInfoImpl(storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY), settings.pin, - settings.setupId, settings.blockUserDeletion); + authInfo = new HomekitAuthInfoImpl(storage, settings.pin, settings.setupId, settings.blockUserDeletion); startHomekitServer(); } catch (IOException | InvalidAlgorithmParameterException e) { logger.warn("cannot activate HomeKit binding. {}", e.getMessage());