diff --git a/bundles/org.openhab.automation.jsscripting/README.md b/bundles/org.openhab.automation.jsscripting/README.md
index 0008159d79b96..830c9eb8dca20 100644
--- a/bundles/org.openhab.automation.jsscripting/README.md
+++ b/bundles/org.openhab.automation.jsscripting/README.md
@@ -7,20 +7,23 @@ to common openHAB functionality within rules including items, things, actions, l
- [Configuration](#configuration)
- [UI Based Rules](#ui-based-rules)
+ - [Adding Triggers](#adding-triggers)
+ - [Adding Actions](#adding-actions)
- [Scripting Basics](#scripting-basics)
- - [require](#require)
- - [console](#console)
- - [setInterval](#setinterval)
- - [setTimeout](#settimeout)
- - [scriptLoaded](#scriptloaded)
- - [scriptUnLoaded](#scriptunloaded)
- - [Paths](#paths)
+ - [Require](#require)
+ - [Console](#console)
+ - [SetTimeout](#settimeout)
+ - [SetInterval](#setinterval)
+ - [ScriptLoaded](#scriptloaded)
+ - [ScriptUnLoaded](#scriptunloaded)
+ - [Paths](#paths)
- [Standard Library](#standard-library)
- - [items](#items)
- - [actions](#actions)
- - [cache](#cache)
- - [log](#log)
- - [time](#time)
+ - [Items](#items)
+ - [Actions](#actions)
+ - [Cache](#cache)
+ - [Log](#log)
+ - [Time](#time)
+ - [Utils](#utils)
- [File Based Rules](#file-based-rules)
- [JSRule](#jsrule)
- [Rule Builder](#rule-builder)
@@ -39,14 +42,15 @@ The quickest way to add rules is through the openHAB Web UI.
Advanced users, or users migrating scripts from existing systems may want to use [File Based Rules](#file-based-rules) for managing rules using files in the user configuration directory.
### Adding Triggers
-
Using the openHAB UI, first create a new rule and set a trigger condition

-### Adding Actions
-Select "Add Action" and then select "ECMAScript 262 Edition 11". Its important this is "Edition 11" or higher, earlier versions will not work. This will bring up a empty script editor where you can enter your javascript.
+### Adding Actions
+Select "Add Action" and then select "ECMAScript 262 Edition 11".
+Its important this is "Edition 11" or higher, earlier versions will not work.
+This will bring up a empty script editor where you can enter your javascript.

@@ -79,12 +83,13 @@ The openHAB JSScripting runtime attempts to provide a familiar environment to Ja
### Require
-Scripts may include standard NPM based libraries by using CommonJS require. The library search path will look in `automation/js/node_modules` in the user configuration directory.
+Scripts may include standard NPM based libraries by using CommonJS require.
+The library search path will look in `automation/js/node_modules` in the user configuration directory.
### Console
The JSScripting binding supports the standard `console` object for logging.
-Script debug logging is enabled by default at the `TRACE` level, but can be configured using the [console logging]({{base}}/administration/logging.html) commands.
+Script debug logging is enabled by default at the `INFO` level, but can be configured using the [console logging]({{base}}/administration/logging.html) commands.
```text
log:set DEBUG org.openhab.automation.script
@@ -98,11 +103,12 @@ Supported logging functions include:
- `console.debug(obj1 [, obj2, ..., objN])`
- `console.trace(obj1 [, obj2, ..., objN])`
-where `obj1 ... objN` is a list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output.
+where `obj1 ... objN` is a list of JavaScript objects to output.
+The string representations of each of these objects are appended together in the order listed and output.
see https://developer.mozilla.org/en-US/docs/Web/API/console for more information about console logging.
-### setTimeout
+### SetTimeout
The global setTimeout() method sets a timer which executes a function or specified piece of code once the timer expires.
```javascript
@@ -114,7 +120,7 @@ The global `clearTimeout()` method cancels a timeout previously established by c
see https://developer.mozilla.org/en-US/docs/Web/API/setTimeout for more information about setTimeout.
-### setInterval
+### SetInterval
The setInterval() method repeatedly calls a function or executes a code snippet, with a fixed time delay between each call.
@@ -125,7 +131,7 @@ var intervalID = setInterval(function[, delay]);
The global `clearInterval()` method cancels a timed, repeating action which was previously established by a call to `setInterval()`.
-NOTE: Timers will not be canceled if a script is deleted or modified, it is up to the user to manage timers. See using the [cache](#cache) namespace as well as [ScriptLoaded](#scriptloaded) and [ScriptUnLoaded](#scriptunloaded) for a convenient way of managing persisted objects, such as timers between reloads or deletions of scripts.
+NOTE: Timers will not be canceled if a script is deleted or modified, it is up to the user to manage timers. See using the [cache](#cache) namespace as well as [ScriptLoaded](#scriptloaded) and [ScriptUnLoaded](#scriptunloaded) for a convenient way of managing persisted objects, such as timers between reloads or deletions of scripts.
see https://developer.mozilla.org/en-US/docs/Web/API/setInterval for more information about setInterval.
@@ -164,12 +170,13 @@ Full documentation for the openHAB JavaScript library can be found at [openhab-j
### Items
-The items namespace allows interactions with openHAB items.
+The items namespace allows interactions with openHAB items.
-See [openhab-js : items ](https://openhab.github.io/openhab-js/items.html) for full API documentation
+See [openhab-js : items](https://openhab.github.io/openhab-js/items.html) for full API documentation
* items : object
* .getItem(name, nullIfMissing) ⇒ Item
+ * .getItems() ⇒ Array.<Item>
* .getItemsByTag(...tagNames) ⇒ Array.<Item>
* .createItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction], [itemMetadata])
* .addItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction])
@@ -194,6 +201,7 @@ Calling `getItem(...)` returns an `Item` object with the following properties:
* .members ⇒ Array.<Item>
* .descendents ⇒ Array.<Item>
* .isUninitialized ⇒ Boolean
+ * .groupNames ⇒ Array.<String>
* .tags ⇒ Array.<String>
* .getMetadataValue(namespace) ⇒ String
* .updateMetadataValue(namespace, value) ⇒ String
@@ -223,7 +231,7 @@ Note `serviceId` is optional, if omitted, the default persistance service will b
* ItemHistory : object
* .averageSince(timestamp, serviceId) ⇒ Number
- * .changedSince(timestamp, serviceId) ⇒ Boolean
+ * .changedSince(timestamp, serviceId) ⇒ Number
* .deltaSince(timestamp, serviceId) ⇒ Number
* .deviationSince(timestamp, serviceId) ⇒ Number
* .evolutionRate(timestamp, serviceId) ⇒ Number
@@ -246,28 +254,29 @@ console.log("KitchenDimmer averageSince", item.history.averageSince(yesterday));
### Actions
-The actions namespace allows interactions with openHAB actions. The following are a list of standard actions.
+The actions namespace allows interactions with openHAB actions.
+The following are a list of standard actions.
Additional actions provided by user installed addons can be accessed using their common name on the actions name space
(example: `actions.Pushsafer.pushsafer(...)`)
-See [openhab-js : actions ](https://openhab.github.io/openhab-js/actions.html) for full API documentation and additional actions.
+See [openhab-js : actions](https://openhab.github.io/openhab-js/actions.html) for full API documentation and additional actions.
#### Audio Actions
-See [openhab-js : actions.Audio ](https://openhab.github.io/openhab-js/actions.html#.Audio) for complete documentation
+See [openhab-js : actions.Audio](https://openhab.github.io/openhab-js/actions.html#.Audio) for complete documentation
-### BusEvent
+#### BusEvent
-See [openhab-js : actions.BusEvent ](https://openhab.github.io/openhab-js/actions.html#.BusEvent) for complete documentation
+See [openhab-js : actions.BusEvent](https://openhab.github.io/openhab-js/actions.html#.BusEvent) for complete documentation
-## Ephemeris Actions
+#### Ephemeris Actions
-See [openhab-js : actions.Ephemeris ](https://openhab.github.io/openhab-js/actions.html#.Ephemeris) for complete documentation
+See [openhab-js : actions.Ephemeris](https://openhab.github.io/openhab-js/actions.html#.Ephemeris) for complete documentation
Ephemeris is a way to determine what type of day today or a number of days before or after today is. For example, a way to determine if today is a weekend, a bank holiday, someone’s birthday, trash day, etc.
-Additional information can be found on the [Ephemeris Actions Docs](https://www.openhab.org/docs/configuration/actions.html#ephemeris) as well as the [Ephemeris JavaDoc](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/ephemeris).
+Additional information can be found on the [Ephemeris Actions Docs](https://www.openhab.org/docs/configuration/actions.html#ephemeris) as well as the [Ephemeris JavaDoc](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/ephemeris).
```javascript
// Example
@@ -276,7 +285,7 @@ let weekend = actions.Ephemeris.isWeekend();
#### Exec Actions
-See [openhab-js : actions.Exec ](https://openhab.github.io/openhab-js/actions.html#.Exec) for complete documentation
+See [openhab-js : actions.Exec](https://openhab.github.io/openhab-js/actions.html#.Exec) for complete documentation
Execute a command line.
@@ -296,9 +305,9 @@ let response = actions.Exec.executeCommandLine('echo', 'Hello World!');
response = actions.Exec.executeCommandLine(Duration.ofSeconds(20), 'echo', 'Hello World!');
```
-### HTTP Actions
+#### HTTP Actions
-See [openhab-js : actions.HTTP ](https://openhab.github.io/openhab-js/actions.html#.HTTP) for complete documentation
+See [openhab-js : actions.HTTP](https://openhab.github.io/openhab-js/actions.html#.HTTP) for complete documentation
```javascript
// Example GET Request
@@ -307,9 +316,9 @@ var response = actions.HTTP.sendHttpGetRequest('');
Replace `` with the request url.
-### ScriptExecution Actions
+#### ScriptExecution Actions
-See [openhab-js : actions.ScriptExecution ](https://openhab.github.io/openhab-js/actions.html#.ScriptExecution) for complete documentation
+See [openhab-js : actions.ScriptExecution](https://openhab.github.io/openhab-js/actions.html#.ScriptExecution) for complete documentation
```javascript
@@ -332,19 +341,19 @@ let active = this.myTimer.isActive();
// Reschedule the timer.
this.myTimer.reschedule(now.plusSeconds(5));
```
-### Semantics Actions
+#### Semantics Actions
-See [openhab-js : actions.Semantics ](https://openhab.github.io/openhab-js/actions.html#.Semantics) for complete documentation
+See [openhab-js : actions.Semantics](https://openhab.github.io/openhab-js/actions.html#.Semantics) for complete documentation
-### Things Actions
+#### Things Actions
-See [openhab-js : actions.Things ](https://openhab.github.io/openhab-js/actions.html#.Things) for complete documentation
+See [openhab-js : actions.Things](https://openhab.github.io/openhab-js/actions.html#.Things) for complete documentation
-### Voice Actions
+#### Voice Actions
-See [openhab-js : actions.Voice ](https://openhab.github.io/openhab-js/actions.html#.Voice) for complete documentation
+See [openhab-js : actions.Voice](https://openhab.github.io/openhab-js/actions.html#.Voice) for complete documentation
-### Cloud Notification Actions
+#### Cloud Notification Actions
(optional action if openhab-cloud is installed)
@@ -365,7 +374,7 @@ Replace `` with the notification text.
The cache namespace provides a default cache that can be use to set and retrieve objects that will be persisted between reloads of scripts.
-See [openhab-js : cache ](https://openhab.github.io/openhab-js/cache.html) for full API documentation
+See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for full API documentation
* cache : object
* .get(key, defaultSupplier) ⇒ Object | null
@@ -374,13 +383,13 @@ See [openhab-js : cache ](https://openhab.github.io/openhab-js/cache.html) for f
The `defaultSupplier` provided function will return a default value if a specified key is not already associated with a value
-**Example** *(Get a previously set value with a default value (times = 0))*
+**Example** *(Get a previously set value with a default value (times = 0))*
```js
let counter = cache.get("counter", () => ({ "times": 0 }));
console.log("Count",counter.times++);
```
-**Example** *(Get a previously set object)*
+**Example** *(Get a previously set object)*
```js
let counter = cache.get("counter");
if(counter == null){
@@ -391,8 +400,8 @@ console.log("Count",counter.times++);
```
### Log
-By default the JS Scripting binding supports console logging like `console.log()` and `console.debug()` to the openHAB
-default log. Additionally scripts may create their own native openHAB logs using the log namespace
+By default the JS Scripting binding supports console logging like `console.log()` and `console.debug()` to the openHAB default log.
+Additionally scripts may create their own native openHAB logs using the log namespace.
```javascript
let logger = log('my_logger');
@@ -403,7 +412,9 @@ logger.debug("Hello {}!", "world");
### Time
-openHAB internally makes extensive use of the `java.time` package. openHAB-JS exports the excellent [JS-Joda](https://js-joda.github.io/js-joda/) library via the `time` namespace, which is a native Javascript port of the same API standard used in Java for `java.time`. Anywhere that a native Java `ZonedDateTime` or `Duration` is required, the runtime will automatically convert a JS-Joda `ZonedDateTime` or `Duration` to its Java counterpart.
+openHAB internally makes extensive use of the `java.time` package.
+openHAB-JS exports the excellent [JS-Joda](#https://js-joda.github.io/js-joda/) library via the `time` namespace, which is a native Javascript port of the same API standard used in Java for `java.time`.
+Anywhere that a native Java `ZonedDateTime` or `Duration` is required, the runtime will automatically convert a JS-Joda `ZonedDateTime` or `Duration` to its Java counterpart.
Examples:
```javascript
@@ -420,9 +431,19 @@ actions.Exec.executeCommandLine(time.Duration.ofSeconds(20), 'echo', 'Hello Worl
See [JS-Joda](https://js-joda.github.io/js-joda/) for more examples and complete API usage.
+### Utils
+
+openHAB internally is a Java program.
+openHAB-JS converts between Java and JavaScript data types and reverse.
+
+See [openhab-js : utils](https://openhab.github.io/openhab-js/utils.html) for full API documentation
+
+
## File Based Rules
-The JSScripting binding will load scripts from `automation/js` in the user configuration directory. The system will automatically reload scripts when changes are detected to files. Local variable state is not persisted among reloads, see using the [cache](#cache) for a connivent way to persist objects.
+The JSScripting binding will load scripts from `automation/js` in the user configuration directory.
+The system will automatically reload scripts when changes are detected to files.
+Local variable state is not persisted among reloads, see using the [cache](#cache) for a connivent way to persist objects.
File based rules can be created in 2 different ways: using [JSRule](#jsrule) or the [Rule Builder](#rule-builder).
@@ -442,10 +463,14 @@ rules.JSRule({
execute: data => {
items.getItem("BalconyLights").sendCommand("ON");
actions.NotificationAction.sendNotification(email, "Balcony lights are ON");
- }
+ },
+ tags: ["Balcony", "Lights"],
+ id: "BalconyLightsOn"
});
```
+Note: `description`, `tags` and `id` are optional.
+
Multiple triggers can be added, some example triggers include:
```javascript
@@ -493,10 +518,15 @@ Rules are started by calling `rules.when()` and can chain together [triggers](#r
[conditions](#rule-builder-conditions) and [operations](#rule-builder-operations) in the following pattern:
```javascript
-rules.when().triggerType()...if().conditionType().then().operationType()...build(name,description);
+rules.when().triggerType()...if().conditionType().then().operationType()...build(name, description, tags, id);
```
-Rule are completed by calling `.build(name,description)` , if name or description are omitted, a generated value will be used.
+Rule are completed by calling `.build(name, description, tags, id)` , all parameters are optional and reasonable defaults will be used if omitted.
+
+- `name` String rule name - defaults generated name
+- `description` String Rule description - defaults generated description
+- `tags` Array of string tag names - defaults empty array
+- `id` String id - defaults random UUID
A simple example of this would look like:
@@ -557,11 +587,13 @@ Additionally all the above triggers have the following functions:
#### Rule Builder Conditions
* `if(optionalFunction)`
- * `.stateOfItem(state)`
+ * `.stateOfItem(itemName)`
+ * `is(state)`
+ * `in(state...)`
#### Rule Builder Operations
* `then(optionalFunction)`
- * `.build(name, description)`
+ * `.build(name, description, tags, id)`
* `.copyAndSendState()`
* `.copyState()`
* `.inGroup(groupName)`
@@ -579,19 +611,19 @@ Additionally all the above triggers have the following functions:
//Basic rule, when the BedroomLight1 is changed, run a custom function
rules.when().item('BedroomLight1').changed().then(e => {
console.log("BedroomLight1 state", e.newState)
-}).build();
+}.build();
//turn on the kitchen light at SUNSET
rules.when().timeOfDay("SUNSET").then().sendOn().toItem("KitchenLight").build("Sunset Rule","turn on the kitchen light
at SUNSET");
-//turn off the kitchen light at 9PM
+//turn off the kitchen light at 9PM and tag rule
rules.when().cron("0 0 21 * * ?").then().sendOff().toItem("KitchenLight").build("9PM Rule", "turn off the kitchen light
-at 9PM");
+at 9PM", ["Tag1", "Tag2"]);
-//set the colour of the hall light to pink at 9PM
+//set the colour of the hall light to pink at 9PM, tag rule and use a custom ID
rules.when().cron("0 0 21 * * ?").then().send("300,100,100").toItem("HallLight").build("Pink Rule", "set the colour of
-the hall light to pink at 9PM");
+the hall light to pink at 9PM", ["Tag1", "Tag2"], "MyCustomID");
//when the switch S1 status changes to ON, then turn on the HallLight
rules.when().item('S1').changed().toOn().then(sendOn().toItem('HallLight')).build("S1 Rule");
diff --git a/bundles/org.openhab.automation.jsscripting/pom.xml b/bundles/org.openhab.automation.jsscripting/pom.xml
index 207cd36bba152..cfd9941551fcc 100644
--- a/bundles/org.openhab.automation.jsscripting/pom.xml
+++ b/bundles/org.openhab.automation.jsscripting/pom.xml
@@ -25,7 +25,7 @@
21.3.06.2.1${project.version}
- openhab@1.2.1
+ openhab@1.2.2
diff --git a/bundles/org.openhab.automation.jsscripting/src/main/resources/node_modules/@jsscripting-globals.js b/bundles/org.openhab.automation.jsscripting/src/main/resources/node_modules/@jsscripting-globals.js
index d60a905174028..f956729ad66af 100644
--- a/bundles/org.openhab.automation.jsscripting/src/main/resources/node_modules/@jsscripting-globals.js
+++ b/bundles/org.openhab.automation.jsscripting/src/main/resources/node_modules/@jsscripting-globals.js
@@ -2,13 +2,20 @@
(function (global) {
'use strict';
+ //Append the script file name OR rule UID depending on which is available
+ const defaultLoggerName = "org.openhab.automation.script" + (globalThis["javax.script.filename"] ? ".file." + globalThis["javax.script.filename"].replace(/^.*[\\\/]/, '') : globalThis["ruleUID"] ? ".ui." + globalThis["ruleUID"] : "");
const System = Java.type('java.lang.System');
- const log = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.automation.script");
const ScriptExecution = Java.type('org.openhab.core.model.script.actions.ScriptExecution');
const ZonedDateTime = Java.type('java.time.ZonedDateTime');
-
const formatRegExp = /%[sdj%]/g;
+ function createLogger(name = defaultLoggerName) {
+ return Java.type("org.slf4j.LoggerFactory").getLogger(name);
+ }
+
+ //user configurable
+ let log = createLogger();
+
function stringify(value) {
try {
if (Java.isJavaObject(value)) {
@@ -140,6 +147,7 @@
timers[label] = System.currentTimeMillis();
}
},
+
timeEnd: function (label) {
if (label) {
const now = System.currentTimeMillis();
@@ -150,6 +158,16 @@
log.info(format.apply(null, [label + ':', '']));
}
}
+ },
+
+ //Allow user customizable logging names
+ set loggerName(name) {
+ log = createLogger(name);
+ this._loggerName = name;
+ },
+
+ get loggerName() {
+ return this._loggerName || defaultLoggerName;
}
};
@@ -200,5 +218,4 @@
//Support legacy NodeJS libraries
globalThis.global = globalThis;
globalThis.process = { env: { NODE_ENV: '' } };
-
})(this);
diff --git a/bundles/org.openhab.binding.ambientweather/src/main/resources/OH-INF/i18n/ambientweather_fr.properties b/bundles/org.openhab.binding.ambientweather/src/main/resources/OH-INF/i18n/ambientweather_fr.properties
new file mode 100644
index 0000000000000..4d6af05386c10
--- /dev/null
+++ b/bundles/org.openhab.binding.ambientweather/src/main/resources/OH-INF/i18n/ambientweather_fr.properties
@@ -0,0 +1,15 @@
+# binding
+binding.ambientweather.name = Extension Ambient Weather
+binding.ambientweather.description = Extension prenant en charge les stations météo Ambient Weather
+
+# thing types
+thing-type.ambientweather.bridge.label = Pont Ambient Weather
+thing-type.ambientweather.bridge.description = Passerelle Ambient Weather
+thing-type.ambientweather.ws1400ip.label = Ambient Weather WS-1400-IP
+thing-type.ambientweather.ws1400ip.description = Station météo Ambient Weather WS-1400-IP
+thing-type.ambientweather.ws8482.label = Ambient Weather WS-8482
+thing-type.ambientweather.ws8482.description = Station météo Ambient Weather WS-8482
+thing-type.ambientweather.ws2902a.label = Ambient Weather WS-2902A
+thing-type.ambientweather.ws2902a.description = Station météo Ambient Weather WS-2902A
+thing-type.ambientweather.ws0900ip.label = Ambient Weather WS-0900-IP
+thing-type.ambientweather.ws0900ip.description = Station météo Ambient Weather WS-0900-IP
diff --git a/bundles/org.openhab.binding.automower/src/main/resources/OH-INF/i18n/automower_fr.properties b/bundles/org.openhab.binding.automower/src/main/resources/OH-INF/i18n/automower_fr.properties
new file mode 100644
index 0000000000000..b9ea7422834a1
--- /dev/null
+++ b/bundles/org.openhab.binding.automower/src/main/resources/OH-INF/i18n/automower_fr.properties
@@ -0,0 +1,67 @@
+# binding
+binding.automower.name = Robot tondeuse
+binding.automower.description = Cette extension permet d'interagir avec les robots tondeuses Husqvarna
+
+
+# automower parameters
+thing-type.config.automower.automower.mowerId.label = ID
+thing-type.config.automower.automower.mowerName.label = Nom
+thing-type.config.automower.automower.mowerModel.label = Modèle
+thing-type.config.automower.automower.mowerId.mowerSerialNumber = Numéro de série
+thing-type.config.automower.automower.connected.label = Connecté
+thing-type.config.automower.automower.timestamp.label = Dernière mise à jour d'état
+thing-type.config.automower.automower.batteryPct.label = Pourcentage de la batterie
+
+# channel types
+channel-type.automower.mode.label = Mode
+channel-type.automower.mode.description = Mode
+channel-type.automower.activity.label = Activité
+channel-type.automower.activity.description = Activité
+channel-type.automower.state.label = État
+channel-type.automower.state.description = État
+channel-type.automower.last-update.label = Dernière Mise à Jour
+channel-type.automower.last-update.description = Dernière mise à jour
+channel-type.automower.planner-next-start.label= Prochain Démarrage Prévu
+channel-type.automower.planner-next-start.description= Prochain démarrage prévu
+channel-type.automower.planner-override-action.label = Action Dérogative Planning
+channel-type.automower.planner-override-action.description = Action dérogative au planning
+channel-type.automower.planner-restricted-reason.label = Restriction Planning
+channel-type.automower.planner-restricted-reason.description = Restriction du planning
+channel-type.automower.calendar-tasks.label = Les informations du planning au format JSON
+channel-type.automower.calendar-tasks.description = Les informations du planning au format JSON
+
+conf-error-no-app-key = Impossible de se connecter à la passerelle Automower car aucune clé d'application n'est définie dans la configuration
+conf-error-no-username = Impossible de se connecter à la passerelle Automower car il n'y a aucun nom d'utilisateur défini dans la configuration
+conf-error-no-password = Impossible de se connecter à la passerelle Automower car il n'y a aucun mot de passe défini dans la configuration
+conf-error-invalid-polling-interval = Définition invalide de l'intervalle d'interrogation. Il doit être >\= 1
+
+conf-error-no-mower-id = Aucun identifiant de robot tondeuse spécifié. Impossible de communiquer avec la tondeuse sans ID
+conf-error-no-bridge = Aucune passerelle valide disponible pour le robot tondeuse
+
+comm-error-httpclient-init-failed = Impossible d'initialiser le client http
+comm-error-query-mowers-failed = Impossible d'interroger les tondeuses enregistrées
+comm-error-query-mower-failed = Impossible d'interroger l'état du robot tondeuse
+comm-error-send-mower-command-failed = Impossible d'envoyer une commande au robot tondeuse
+
+comm-error-mower-not-connected-to-cloud = Robot tondeuse non connecté au cloud
+
+action-start-label = démarrer le robot tondeuse
+action-start-desc = Démarre le robot tondeuse pour un temps défini, en dérogeant au planning.
+
+action-pause-label = mettre le robot tondeuse en pause
+action-pause-desc = Met le robot tondeuse en pause où qu'il se trouve actuellement.
+
+action-parkuntilnextschedule-label = garer jusqu'à la prochaine planification
+action-parkuntilnextschedule-desc = Gare le robot tondeuse jusqu'à la prochaine planification.
+
+action-parkuntilfurthernotice-label = garer jusqu'à nouvel ordre
+action-parkuntilfurthernotice-desc = Gare le robot tondeuse jusqu'à la reprise du planning.
+
+action-park-label = garer le robot tondeuse
+action-park-desc = Gare le robot tondeuse pour un temps défini, en dérogeant au planning.
+
+action-resumeschedule-label = reprendre le planning
+action-resumeschedule-desc = Reprend le planning pour le robot tondeuse.
+
+action-input-duration-label = Durée
+action-input-duration-desc = La durée de la commande du robot tondeuse en minutes
diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java
index ef6a702678a34..c9a3774e77632 100644
--- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java
+++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java
@@ -72,7 +72,6 @@ protected void pollStatus() throws IOException {
return;
}
ControlInfo controlInfo = webTargets.getControlInfo();
- updateStatus(ThingStatus.ONLINE);
updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF);
updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
@@ -153,6 +152,7 @@ protected void pollStatus() throws IOException {
// Suppress any error if energy info is not supported.
logger.debug("getEnergyInfoDayAndWeek() error: {}", e.getMessage());
}
+ updateStatus(ThingStatus.ONLINE);
}
@Override
diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java
index a7f7428b8ebd1..72ed5e5326272 100644
--- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java
+++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java
@@ -77,7 +77,6 @@ protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
@Override
protected void pollStatus() throws IOException {
AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
- updateStatus(ThingStatus.ONLINE);
if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) {
airbaseModelInfo = webTargets.getAirbaseModelInfo();
@@ -115,6 +114,7 @@ protected void pollStatus() throws IOException {
.forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + idx,
OnOffType.from(zoneInfo.zone[idx])));
}
+ updateStatus(ThingStatus.ONLINE);
}
@Override
@@ -178,18 +178,25 @@ protected void changeFanSpeed(String speed) throws DaikinCommunicationException
}
protected void changeZone(int zone, boolean command) throws DaikinCommunicationException {
- if (zone <= 0 || zone > airbaseModelInfo.zonespresent) {
+ AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
+ long commonZones = 0;
+ long maxZones = zoneInfo.zone.length;
+
+ if (airbaseModelInfo != null) {
+ maxZones = Math.min(maxZones - 1, airbaseModelInfo.zonespresent);
+ commonZones = airbaseModelInfo.commonzone;
+ }
+ if (zone <= 0 || zone > maxZones) {
logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
- zone, airbaseModelInfo.zonespresent);
+ zone, maxZones);
return;
}
- AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
- long count = IntStream.range(0, zoneInfo.zone.length).filter(idx -> zoneInfo.zone[idx]).count()
- + airbaseModelInfo.commonzone;
- logger.debug("Number of open zones: \"{}\"", count);
+ long openZones = IntStream.range(0, zoneInfo.zone.length).filter(idx -> zoneInfo.zone[idx]).count()
+ + commonZones;
+ logger.debug("Number of open zones: \"{}\"", openZones);
- if (count >= 1) {
+ if (openZones >= 1) {
zoneInfo.zone[zone] = command;
webTargets.setAirbaseZoneInfo(zoneInfo);
}
diff --git a/bundles/org.openhab.binding.electroluxair/src/main/resources/OH-INF/i18n/electroluxair_fr.properties b/bundles/org.openhab.binding.electroluxair/src/main/resources/OH-INF/i18n/electroluxair_fr.properties
new file mode 100644
index 0000000000000..d212941398aba
--- /dev/null
+++ b/bundles/org.openhab.binding.electroluxair/src/main/resources/OH-INF/i18n/electroluxair_fr.properties
@@ -0,0 +1,63 @@
+# binding
+
+binding.electroluxair.name = Extension ElectroluxAir
+binding.electroluxair.description = Il s'agit de l'extension pour le purificateur d'air Electrolux Pure A9.
+
+# thing types
+
+thing-type.electroluxair.api.label = API Delta Electrolux
+thing-type.electroluxair.api.description = Cette passerelle représente le connecteur de l'API web.
+thing-type.electroluxair.electroluxpurea9.label = ElectroluxAir Pure A9
+thing-type.electroluxair.electroluxpurea9.description = Cet objet représente l'ElectroluxAir Pure A9.
+
+# thing types config
+
+thing-type.config.electroluxair.api.password.label = Mot de passe
+thing-type.config.electroluxair.api.password.description = Le mot de passe utilisé pour se connecter à l'application Wellbeing Electrolux.
+thing-type.config.electroluxair.api.refresh.label = Intervalle d’actualisation
+thing-type.config.electroluxair.api.refresh.description = Définit l'intervalle d'actualisation en secondes.
+thing-type.config.electroluxair.api.username.label = Nom d'utilisateur
+thing-type.config.electroluxair.api.username.description = Le nom d'utilisateur utilisé pour se connecter à l'application Wellbeing Electrolux.
+thing-type.config.electroluxair.electroluxpurea9.deviceId.label = Identifiant de l'appareil
+thing-type.config.electroluxair.electroluxpurea9.deviceId.description = Identifiant unique
+
+# channel types
+
+channel-type.electroluxair.co2.label = CO2
+channel-type.electroluxair.co2.description = Dioxyde de carbone
+channel-type.electroluxair.doorOpen.label = État Porte
+channel-type.electroluxair.doorOpen.description = Statut de la porte ouverte/fermée
+channel-type.electroluxair.fanSpeed.label = Réglage Vitesse Ventilateur
+channel-type.electroluxair.fanSpeed.description = Réglage de la vitesse du ventilateur
+channel-type.electroluxair.fanSpeed.state.option.1 = Vitesse 1
+channel-type.electroluxair.fanSpeed.state.option.2 = Vitesse 2
+channel-type.electroluxair.fanSpeed.state.option.3 = Vitesse 3
+channel-type.electroluxair.fanSpeed.state.option.4 = Vitesse 4
+channel-type.electroluxair.fanSpeed.state.option.5 = Vitesse 5
+channel-type.electroluxair.fanSpeed.state.option.6 = Vitesse 6
+channel-type.electroluxair.fanSpeed.state.option.7 = Vitesse 7
+channel-type.electroluxair.fanSpeed.state.option.8 = Vitesse 8
+channel-type.electroluxair.fanSpeed.state.option.9 = Vitesse 9
+channel-type.electroluxair.filterLife.label = Durée Vie Filtre
+channel-type.electroluxair.filterLife.description = Durée de vie du filtre
+channel-type.electroluxair.humidity.label = Humidité
+channel-type.electroluxair.humidity.description = Humidité
+channel-type.electroluxair.ionizer.label = Statut Ioniseur
+channel-type.electroluxair.ionizer.description = Statut de l'ioniseur
+channel-type.electroluxair.pm1.label = PM1
+channel-type.electroluxair.pm1.description = Densité de particules 1 (0.001mm)
+channel-type.electroluxair.pm10.label = PM10
+channel-type.electroluxair.pm10.description = Densité de particules 10 (0.01mm)
+channel-type.electroluxair.pm2_5.label = PM2.5
+channel-type.electroluxair.pm2_5.description = Densité de particules 2.5 (0.0025mm)
+channel-type.electroluxair.status.label = État Actuel
+channel-type.electroluxair.status.description = Informations sur l'état actuel.
+channel-type.electroluxair.temperature.label = Température
+channel-type.electroluxair.temperature.description = Température
+channel-type.electroluxair.tvoc.label = TVOC
+channel-type.electroluxair.tvoc.description = Total de composés organiques volatils
+channel-type.electroluxair.workMode.label = Réglage Mode Fonctionnement
+channel-type.electroluxair.workMode.description = Réglage du mode de fonctionnement
+channel-type.electroluxair.workMode.state.option.PowerOff = Extinction
+channel-type.electroluxair.workMode.state.option.Auto = Automatique
+channel-type.electroluxair.workMode.state.option.Manual = Manuel
diff --git a/bundles/org.openhab.binding.hdpowerview/README.md b/bundles/org.openhab.binding.hdpowerview/README.md
index d913d53416a82..06547b6345af6 100644
--- a/bundles/org.openhab.binding.hdpowerview/README.md
+++ b/bundles/org.openhab.binding.hdpowerview/README.md
@@ -32,9 +32,12 @@ If the connection succeeds, the hub will indicate its status as Online, otherwis
Once the hub thing has been created and successfully connected, the binding will automatically discover all shades and scenes that are in it.
- For each shade discovered: the binding will create a new dedicated thing with its own channels.
+- For each repeater discovered: the binding will create a new dedicated thing with its own channels.
- For each scene discovered: the binding will create a new channel dynamically within the hub thing.
+- For each scene group discovered: the binding will create a new channel dynamically within the hub thing.
+- For each automation discovered: the binding will create a new channel dynamically within the hub thing.
-If in the future, you add additional shades or scenes to your system, the binding will discover them too.
+If in the future, you add additional shades, repeaters, scenes, scene groups or automations to your system, the binding will discover them too.
## Thing Configuration
@@ -92,7 +95,7 @@ All of these channels appear in the binding, but only those which have a physica
| position | Rollershutter | The vertical position of the shade's rail -- see [next chapter](#Roller-Shutter-Up/Down-Position-vs.-Open/Close-State). Up/Down commands will move the rail completely up or completely down. Percentage commands will move the rail to an intermediate position. Stop commands will halt any current movement of the rail. |
| secondary | Rollershutter | The vertical position of the secondary rail (if any). Its function is similar to the `position` channel above -- but see [next chapter](#Roller-Shutter-Up/Down-Position-vs.-Open/Close-State). |
| vane | Dimmer | The degree of opening of the slats or vanes. Setting this to a non-zero value will first move the shade `position` fully down, since the slats or vanes can only have a defined state if the shade is in its down position -- see [Interdependency between Channel positions](#Interdependency-between-Channel-positions). |
-| command | String | Send a command to the shade. Valid values are: `CALIBRATE` |
+| command | String | Send a command to the shade. Valid values are: `CALIBRATE`, `IDENTIFY` |
| lowBattery | Switch | Indicates ON when the battery level of the shade is low, as determined by the hub's internal rules. |
| batteryLevel | Number | Battery level (10% = low, 50% = medium, 100% = high)
| batteryVoltage | Number:ElectricPotential | Battery voltage reported by the shade. |
@@ -211,7 +214,7 @@ For single shades the refresh takes the item's channel into consideration:
### `demo.things` File
```
-Bridge hdpowerview:hub:g24 "Luxaflex Hub" @ "Living Room" [host="192.168.1.123"] {
+Bridge hdpowerview:hub:home "Luxaflex Hub" @ "Living Room" [host="192.168.1.123"] {
Thing shade s50150 "Living Room Shade" @ "Living Room" [id="50150"]
Thing repeater r16384 "Bedroom Repeater" @ "Bedroom" [id="16384"]
}
@@ -222,32 +225,51 @@ Bridge hdpowerview:hub:g24 "Luxaflex Hub" @ "Living Room" [host="192.168.1.123"]
Shade items:
```
-Rollershutter Living_Room_Shade_Position "Living Room Shade Position [%.0f %%]" {channel="hdpowerview:shade:g24:s50150:position"}
-Rollershutter Living_Room_Shade_Secondary "Living Room Shade Secondary Position [%.0f %%]" {channel="hdpowerview:shade:g24:s50150:secondary"}
-Dimmer Living_Room_Shade_Vane "Living Room Shade Vane [%.0f %%]" {channel="hdpowerview:shade:g24:s50150:vane"}
-Switch Living_Room_Shade_Battery_Low_Alarm "Living Room Shade Battery Low Alarm [%s]" {channel="hdpowerview:shade:g24:s50150:lowBattery"}
-String Living_Room_Shade_Command "Living Room Shade Command" {channel="hdpowerview:shade:g24:s50150:command"}
+Rollershutter Living_Room_Shade_Position "Living Room Shade Position [%.0f %%]" {channel="hdpowerview:shade:home:s50150:position"}
+Rollershutter Living_Room_Shade_Secondary "Living Room Shade Secondary Position [%.0f %%]" {channel="hdpowerview:shade:home:s50150:secondary"}
+Dimmer Living_Room_Shade_Vane "Living Room Shade Vane [%.0f %%]" {channel="hdpowerview:shade:home:s50150:vane"}
+Switch Living_Room_Shade_Battery_Low_Alarm "Living Room Shade Battery Low Alarm [%s]" {channel="hdpowerview:shade:home:s50150:lowBattery"}
+Number Living_Room_Shade_Battery_Level "Battery Level" {channel="hdpowerview:shade:home:s50150:batteryLevel"}
+Number:ElectricPotential Living_Room_Shade_Battery_Voltage "Battery Voltage" {channel="hdpowerview:shade:home:s50150:batteryVoltage"}
+String Living_Room_Shade_Command "Living Room Shade Command" {channel="hdpowerview:shade:home:s50150:command"}
+Number Living_Room_Shade_SignalStrength "Living Room Shade Signal Strength" {channel="hdpowerview:shade:home:s50150:signalStrength"}
```
Repeater items:
```
-String Bedroom_Repeater_Identify "Bedroom Repeater Identify" {channel="hdpowerview:repeater:g24:r16384:identify"}
-Switch Bedroom_Repeater_BlinkingEnabled "Bedroom Repeater Blinking Enabled [%s]" {channel="hdpowerview:repeater:g24:r16384:blinkingEnabled"}
+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"}
```
Scene items:
```
-Switch Living_Room_Shades_Scene_Heart "Living Room Shades Scene Heart" (g_Shades_Scene_Trigger) {channel="hdpowerview:hub:g24:scenes#22663"}
+Switch Living_Room_Shades_Scene_Heart "Living Room Shades Scene Heart" (g_Shades_Scene_Trigger) {channel="hdpowerview:hub:home:scenes#22663"}
+```
+
+Scene Group items:
+
+```
+Switch Children_Rooms_Shades_Up "Good Morning Children" {channel="hdpowerview:hub:home:sceneGroups#27119"}
+```
+
+Automation items:
+
+```
+Switch Automation_Children_Up_Sun "Children Up At Sunrise" {channel="hdpowerview:hub:home:automations#1262"}
+Switch Automation_Children_Up_Time "Children Up At 6:30" {channel="hdpowerview:hub:home:automations#49023"}
```
### `demo.sitemap` File
```
-Frame label="Living Room Shades" {
+Frame label="Living Room" {
Switch item=Living_Room_Shades_Scene_Open
- Slider item=Living_Room_Shade_1_Position
+ Slider item=Living_Room_Shade_Position
+ Switch item=Living_Room_Shade_Command mappings=[CALIBRATE="Calibrate"]
+ Text item=Living_Room_Shade_SignalStrength
+ Text item=Living_Room_Shade_Battery_Voltage
}
Frame label="Bedroom" {
Switch item=Bedroom_Repeater_Identify mappings=[IDENTIFY="Identify"]
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 dd1748fafbfbd..aa7dd2f3ccc90 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
@@ -29,6 +29,7 @@
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.ShadeCalibrate;
+import org.openhab.binding.hdpowerview.internal.api.requests.ShadeJog;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeMove;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeStop;
import org.openhab.binding.hdpowerview.internal.api.responses.FirmwareVersion;
@@ -49,6 +50,7 @@
import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
+import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -207,15 +209,16 @@ public Shades getShades() throws HubInvalidResponseException, HubProcessingExcep
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData moveShade(int shadeId, ShadePosition position)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData moveShade(int shadeId, ShadePosition position) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
String jsonRequest = gson.toJson(new ShadeMove(position));
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return shadeDataFromJson(jsonResponse);
}
- private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException {
+ private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException, HubShadeTimeoutException {
try {
Shade shade = gson.fromJson(json, Shade.class);
if (shade == null) {
@@ -225,6 +228,9 @@ private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseExcept
if (shadeData == null) {
throw new HubInvalidResponseException("Missing 'shade.shade' element");
}
+ if (Boolean.TRUE.equals(shadeData.timedOut)) {
+ throw new HubShadeTimeoutException("Timeout when sending request to the shade");
+ }
return shadeData;
} catch (JsonParseException e) {
throw new HubInvalidResponseException("Error parsing shade response", e);
@@ -239,14 +245,32 @@ private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseExcept
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData stopShade(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData stopShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonRequest = gson.toJson(new ShadeStop());
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return shadeDataFromJson(jsonResponse);
}
+ /**
+ * Instructs the hub to jog a specific shade
+ *
+ * @param shadeId id of the shade to be jogged
+ * @return ShadeData 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
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
+ */
+ public ShadeData jogShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
+ String jsonRequest = gson.toJson(new ShadeJog());
+ String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
+ return shadeDataFromJson(jsonResponse);
+ }
+
/**
* Instructs the hub to calibrate a specific shade
*
@@ -255,9 +279,10 @@ public ShadeData stopShade(int shadeId)
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData calibrateShade(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData calibrateShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonRequest = gson.toJson(new ShadeCalibrate());
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return shadeDataFromJson(jsonResponse);
@@ -557,9 +582,10 @@ private synchronized String invoke(HttpMethod method, String url, @Nullable Quer
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData getShade(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData getShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), null, null);
return shadeDataFromJson(jsonResponse);
}
@@ -574,9 +600,10 @@ public ShadeData getShade(int shadeId)
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
public ShadeData refreshShadePosition(int shadeId)
- throws JsonParseException, HubProcessingException, HubMaintenanceException {
+ throws JsonParseException, HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
Query.of("refresh", Boolean.toString(true)), null);
return shadeDataFromJson(jsonResponse);
@@ -619,9 +646,10 @@ public Survey getShadeSurvey(int shadeId)
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData refreshShadeBatteryLevel(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData refreshShadeBatteryLevel(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
Query.of("updateBatteryLevel", Boolean.toString(true)), null);
return shadeDataFromJson(jsonResponse);
diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/ShadeJog.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/ShadeJog.java
new file mode 100644
index 0000000000000..71fb0477a09ec
--- /dev/null
+++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/ShadeJog.java
@@ -0,0 +1,30 @@
+/**
+ * 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;
+
+/**
+ * A request to jog a shade for identification
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class ShadeJog {
+
+ public ShadeMotion shade;
+
+ public ShadeJog() {
+ this.shade = new ShadeMotion(ShadeMotion.Type.JOG);
+ }
+}
diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/ShadeMotion.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/ShadeMotion.java
index f4c9d784a5aa8..c6b16a000065e 100644
--- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/ShadeMotion.java
+++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/requests/ShadeMotion.java
@@ -24,6 +24,7 @@ class ShadeMotion {
public enum Type {
STOP("stop"),
+ JOG("jog"),
CALIBRATE("calibrate");
private String motion;
diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/exceptions/HubShadeTimeoutException.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/exceptions/HubShadeTimeoutException.java
new file mode 100644
index 0000000000000..3dcedc6848b58
--- /dev/null
+++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/exceptions/HubShadeTimeoutException.java
@@ -0,0 +1,31 @@
+/**
+ * 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.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link HubShadeTimeoutException} is a custom exception for the HD PowerView Hub
+ * which is thrown when a shade does not respond to a request.
+ *
+ * @author @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class HubShadeTimeoutException extends HubException {
+
+ private static final long serialVersionUID = -362347489903471011L;
+
+ public HubShadeTimeoutException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java
index 3f6d1747515c3..cfea061f28e0e 100644
--- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java
+++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java
@@ -58,6 +58,7 @@
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.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
@@ -204,7 +205,11 @@ public void childHandlerInitialized(final ThingHandler childHandler, final Thing
if (childHandler instanceof HDPowerViewShadeHandler) {
ShadeData shadeData = pendingShadeInitializations.remove(childThing.getUID());
if (shadeData != null) {
- updateShadeThing(shadeData.id, childThing, shadeData);
+ if (shadeData.id > 0) {
+ updateShadeThing(shadeData.id, childThing, shadeData);
+ } else {
+ updateUnknownShadeThing(childThing);
+ }
}
}
super.childHandlerInitialized(childHandler, childThing);
@@ -354,15 +359,15 @@ private void pollShades() throws HubInvalidResponseException, HubProcessingExcep
Thing thing = item.getKey();
int shadeId = item.getValue();
ShadeData shadeData = idShadeDataMap.get(shadeId);
- updateShadeThing(shadeId, thing, shadeData);
+ if (shadeData != null) {
+ updateShadeThing(shadeId, thing, shadeData);
+ } else {
+ updateUnknownShadeThing(thing);
+ }
}
}
- private void updateShadeThing(int shadeId, Thing thing, @Nullable ShadeData shadeData) {
- if (shadeData == null) {
- logger.debug("Shade '{}' has no data in hub", shadeId);
- return;
- }
+ private void updateShadeThing(int shadeId, Thing thing, ShadeData shadeData) {
HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
if (thingHandler == null) {
logger.debug("Shade '{}' handler not initialized", shadeId);
@@ -390,6 +395,36 @@ private void updateShadeThing(int shadeId, Thing thing, @Nullable ShadeData shad
}
}
+ private void updateUnknownShadeThing(Thing thing) {
+ String shadeId = thing.getUID().getId();
+ logger.debug("Shade '{}' has no data in hub", shadeId);
+ HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
+ if (thingHandler == null) {
+ logger.debug("Shade '{}' handler not initialized", shadeId);
+ pendingShadeInitializations.put(thing.getUID(), new ShadeData());
+ return;
+ }
+ ThingStatus thingStatus = thingHandler.getThing().getStatus();
+ switch (thingStatus) {
+ case UNKNOWN:
+ case ONLINE:
+ case OFFLINE:
+ thing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
+ "@text/offline.gone.shade-unknown-to-hub"));
+ break;
+ case UNINITIALIZED:
+ case INITIALIZING:
+ logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
+ pendingShadeInitializations.put(thing.getUID(), new ShadeData());
+ break;
+ case REMOVING:
+ case REMOVED:
+ default:
+ logger.debug("Ignoring shade status update for shade '{}' in status {}", shadeId, thingStatus);
+ break;
+ }
+ }
+
private List fetchScenes()
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
HDPowerViewWebTargets webTargets = this.webTargets;
@@ -601,6 +636,11 @@ private void requestRefreshShadePositions() {
Map thingIdMap = getShadeThingIdMap();
for (Entry item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
+ if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
+ // Skip shades unknown to the Hub.
+ logger.debug("Shade '{}' is unknown, skipping position refresh", item.getValue());
+ continue;
+ }
ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShadePosition();
@@ -615,6 +655,11 @@ private void requestRefreshShadeBatteryLevels() {
Map thingIdMap = getShadeThingIdMap();
for (Entry item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
+ if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
+ // Skip shades unknown to the Hub.
+ logger.debug("Shade '{}' is unknown, skipping battery level refresh", item.getValue());
+ continue;
+ }
ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java
index 701cf480547d9..5b0cb829a543a 100644
--- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java
+++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java
@@ -37,6 +37,7 @@
import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
+import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
@@ -72,6 +73,7 @@ private enum RefreshKind {
}
private static final String COMMAND_CALIBRATE = "CALIBRATE";
+ private static final String COMMAND_IDENTIFY = "IDENTIFY";
private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
@@ -176,6 +178,8 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
+ } catch (HubShadeTimeoutException e) {
+ logger.warn("Shade {} timeout when sending command {}", shadeId, command);
} catch (HubException e) {
// ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
// for any ongoing requests. Logging this would only cause confusion.
@@ -186,7 +190,8 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}
private void handleShadeCommand(String channelId, Command command, HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException,
+ HubShadeTimeoutException {
switch (channelId) {
case CHANNEL_SHADE_POSITION:
if (command instanceof PercentType) {
@@ -226,7 +231,10 @@ private void handleShadeCommand(String channelId, Command command, HDPowerViewWe
case CHANNEL_SHADE_COMMAND:
if (command instanceof StringType) {
- if (COMMAND_CALIBRATE.equals(((StringType) command).toString())) {
+ if (COMMAND_IDENTIFY.equals(((StringType) command).toString())) {
+ logger.debug("Identify shade {}", shadeId);
+ identifyShade(webTargets, shadeId);
+ } else if (COMMAND_CALIBRATE.equals(((StringType) command).toString())) {
logger.debug("Calibrate shade {}", shadeId);
calibrateShade(webTargets, shadeId);
}
@@ -240,26 +248,19 @@ private void handleShadeCommand(String channelId, Command command, HDPowerViewWe
/**
* Update the state of the channels based on the ShadeData provided.
*
- * @param shadeData the ShadeData to be used; may be null.
+ * @param shadeData the ShadeData to be used.
*/
- protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
- if (shadeData != null) {
- updateStatus(ThingStatus.ONLINE);
- updateCapabilities(shadeData);
- updateSoftProperties(shadeData);
- updateFirmwareProperties(shadeData);
- ShadePosition shadePosition = shadeData.positions;
- if (shadePosition != null) {
- updatePositionStates(shadePosition);
- }
- updateBatteryLevelStates(shadeData.batteryStatus);
- updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
- shadeData.batteryStrength > 0 ? new QuantityType<>(shadeData.batteryStrength / 10, Units.VOLT)
- : UnDefType.UNDEF);
- updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ protected void onReceiveUpdate(ShadeData shadeData) {
+ updateStatus(ThingStatus.ONLINE);
+ updateCapabilities(shadeData);
+ updateSoftProperties(shadeData);
+ updateFirmwareProperties(shadeData);
+ ShadePosition shadePosition = shadeData.positions;
+ if (shadePosition != null) {
+ updatePositionStates(shadePosition);
}
+ updateBatteryStates(shadeData.batteryStatus, shadeData.batteryStrength);
+ updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
}
private void updateCapabilities(ShadeData shade) {
@@ -400,6 +401,12 @@ private void updatePositionStates(ShadePosition shadePos) {
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(capabilities, SECONDARY_POSITION));
}
+ private void updateBatteryStates(int batteryStatus, double batteryStrength) {
+ updateBatteryLevelStates(batteryStatus);
+ updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
+ batteryStrength > 0 ? new QuantityType<>(batteryStrength / 10, Units.VOLT) : UnDefType.UNDEF);
+ }
+
private void updateBatteryLevelStates(int batteryStatus) {
int mappedValue;
switch (batteryStatus) {
@@ -423,7 +430,8 @@ private void updateBatteryLevelStates(int batteryStatus) {
}
private void moveShade(CoordinateSystem coordSys, int newPercent, HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException,
+ HubShadeTimeoutException {
ShadePosition newPosition = null;
// (try to) read the positions from the hub
ShadeData shadeData = webTargets.getShade(shadeId);
@@ -439,16 +447,21 @@ private void moveShade(CoordinateSystem coordSys, int newPercent, HDPowerViewWeb
updateShadePositions(shadeData);
}
- private void stopShade(HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ private void stopShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
updateShadePositions(webTargets.stopShade(shadeId));
// Positions in response from stop motion is not updated to to actual positions yet,
// so we need to request hard refresh.
requestRefreshShadePosition();
}
- private void calibrateShade(HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ private void identifyShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
+ updateShadePositions(webTargets.jogShade(shadeId));
+ }
+
+ private void calibrateShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
updateShadePositions(webTargets.calibrateShade(shadeId));
}
@@ -517,6 +530,8 @@ private void doRefreshShade(RefreshKind kind) {
switch (kind) {
case POSITION:
shadeData = webTargets.refreshShadePosition(shadeId);
+ updateShadePositions(shadeData);
+ updateHardProperties(shadeData);
break;
case SURVEY:
Survey survey = webTargets.getShadeSurvey(shadeId);
@@ -525,19 +540,14 @@ private void doRefreshShade(RefreshKind kind) {
} else {
logger.warn("No response from shade {} survey", shadeId);
}
- return;
+ break;
case BATTERY_LEVEL:
shadeData = webTargets.refreshShadeBatteryLevel(shadeId);
+ updateBatteryStates(shadeData.batteryStatus, shadeData.batteryStrength);
break;
default:
throw new NotSupportedException("Unsupported refresh kind " + kind.toString());
}
- if (Boolean.TRUE.equals(shadeData.timedOut)) {
- logger.warn("Shade {} wireless refresh time out", shadeId);
- } else if (kind == RefreshKind.POSITION) {
- updateShadePositions(shadeData);
- updateHardProperties(shadeData);
- }
} catch (HubInvalidResponseException e) {
Throwable cause = e.getCause();
if (cause == null) {
@@ -547,6 +557,8 @@ private void doRefreshShade(RefreshKind kind) {
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
+ } catch (HubShadeTimeoutException e) {
+ logger.info("Shade {} wireless refresh time out", shadeId);
} catch (HubException e) {
// ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
// for any ongoing requests. Logging this would only cause confusion.
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 9419bceac40be..d09d2fd988953 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
@@ -40,6 +40,7 @@ channel-type.hdpowerview.repeater-identify.description = Flash repeater to ident
channel-type.hdpowerview.repeater-identify.command.option.IDENTIFY = Identify
channel-type.hdpowerview.shade-command.label = Command
channel-type.hdpowerview.shade-command.description = Send a command to the shade
+channel-type.hdpowerview.shade-command.command.option.IDENTIFY = Identify
channel-type.hdpowerview.shade-command.command.option.CALIBRATE = Calibrate
channel-type.hdpowerview.shade-position.label = Position
channel-type.hdpowerview.shade-position.description = The vertical position of the shade
@@ -51,6 +52,7 @@ channel-type.hdpowerview.shade-vane.description = The opening of the slats in th
offline.conf-error.no-host-address = Host address must be set
offline.conf-error.invalid-id = Configuration 'id' not a valid integer
offline.conf-error.invalid-bridge-handler = Invalid bridge handler
+offline.gone.shade-unknown-to-hub = Shade is unknown to Hub
# dynamic channels
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 7de48494cc1d4..a0c845f803a73 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
@@ -127,6 +127,7 @@
Send a command to the shade
+
diff --git a/bundles/org.openhab.binding.http/src/main/resources/OH-INF/i18n/http_it.properties b/bundles/org.openhab.binding.http/src/main/resources/OH-INF/i18n/http_it.properties
new file mode 100644
index 0000000000000..559440e509693
--- /dev/null
+++ b/bundles/org.openhab.binding.http/src/main/resources/OH-INF/i18n/http_it.properties
@@ -0,0 +1,240 @@
+# binding
+
+binding.http.name = Binding HTTP
+binding.http.description = Questo è il binding per il recupero e l'elaborazione di risorse HTTP.
+
+# thing types
+
+thing-type.http.url.label = Thing URL HTTP
+thing-type.http.url.description = Rappresenta un URL di base e tutte le richieste associate.
+
+# thing types config
+
+thing-type.config.http.url.authMode.label = Modalità di autenticazione
+thing-type.config.http.url.authMode.option.BASIC = Autenticazione Basic
+thing-type.config.http.url.authMode.option.BASIC_PREEMPTIVE = Basic Authentication Preventiva
+thing-type.config.http.url.authMode.option.DIGEST = Autenticazione Digest
+thing-type.config.http.url.baseURL.label = URL base
+thing-type.config.http.url.baseURL.description = L'URL impostato qui può essere esteso nella configurazione del channel.
+thing-type.config.http.url.bufferSize.label = Dimensione del buffer
+thing-type.config.http.url.bufferSize.description = Dimensione del buffer di risposta (predefinito 2048 kB)
+thing-type.config.http.url.commandMethod.label = Metodo Di Comando
+thing-type.config.http.url.commandMethod.description = Metodo HTTP (GET, POST, PUT) per l'invio dei comandi.
+thing-type.config.http.url.commandMethod.option.GET = GET
+thing-type.config.http.url.commandMethod.option.POST = POST
+thing-type.config.http.url.commandMethod.option.PUT = PUT
+thing-type.config.http.url.contentType.label = Content Type
+thing-type.config.http.url.contentType.description = Il tipo di contenuto MIME. Usato solo per `POST` e `PUT`.
+thing-type.config.http.url.contentType.option.application/json = application/json
+thing-type.config.http.url.contentType.option.application/xml = application/xml
+thing-type.config.http.url.contentType.option.text/html = text/html
+thing-type.config.http.url.contentType.option.text/plain = text/plain
+thing-type.config.http.url.contentType.option.text/xml = text/xml
+thing-type.config.http.url.delay.label = Ritardo
+thing-type.config.http.url.delay.description = Ritardo tra le richieste
+thing-type.config.http.url.encoding.label = Codifica Fallback
+thing-type.config.http.url.encoding.description = Testo Fallback Encoding ricevuto dai channels della Thing.
+thing-type.config.http.url.headers.label = Intestazioni
+thing-type.config.http.url.headers.description = Intestazioni aggiuntive inviate insieme alla richiesta
+thing-type.config.http.url.ignoreSSLErrors.label = Ignora Errori SSL
+thing-type.config.http.url.ignoreSSLErrors.description = Se impostato a True ignora errori di certificato SSL non validi. Questo è potenzialmente pericoloso.
+thing-type.config.http.url.password.label = Password
+thing-type.config.http.url.password.description = Password Basic Authentication
+thing-type.config.http.url.refresh.label = Tempo Di Aggiornamento
+thing-type.config.http.url.refresh.description = Tempo tra due aggiornamenti di tutti i channel
+thing-type.config.http.url.stateMethod.label = Metodo Stato
+thing-type.config.http.url.stateMethod.description = Metodo HTTP (GET, POST, PUT) per la ricezione dello stato.
+thing-type.config.http.url.stateMethod.option.GET = GET
+thing-type.config.http.url.stateMethod.option.POST = POST
+thing-type.config.http.url.stateMethod.option.PUT = PUT
+thing-type.config.http.url.timeout.label = Timeout
+thing-type.config.http.url.timeout.description = Il timeout in ms per ogni richiesta
+thing-type.config.http.url.username.label = Nome utente
+thing-type.config.http.url.username.description = Utente Basic Authentication
+
+# channel types
+
+channel-type.http.color.label = Channel Colore
+channel-type.http.contact.label = Channel Contatto
+channel-type.http.datetime.label = Channel DateTime
+channel-type.http.dimmer.label = Channel Dimmer
+channel-type.http.image.label = Channel immagine
+channel-type.http.location.label = Channel Posizione
+channel-type.http.number.label = Channel Numero
+channel-type.http.player.label = Channel Player
+channel-type.http.rollershutter.label = Channel Tapparella
+channel-type.http.string.label = Channel Testo
+channel-type.http.switch.label = Channel Interruttore
+
+# channel types config
+
+channel-type.config.http.channel-config-color.colorMode.label = Channel Modo
+channel-type.config.http.channel-config-color.colorMode.description = Modalità colore per analizzare i valori in arrivo e inviare i valori in uscita
+channel-type.config.http.channel-config-color.colorMode.option.HSB = HSB
+channel-type.config.http.channel-config-color.colorMode.option.RGB = RGB
+channel-type.config.http.channel-config-color.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config-color.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per l'invio dei valori.
+channel-type.config.http.channel-config-color.commandTransformation.label = Trasformazione Comandi
+channel-type.config.http.channel-config-color.commandTransformation.description = Modello di trasformazione utilizzato per l'invio di valori. Catena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-color.decreaseValue.label = Diminuisci Valore
+channel-type.config.http.channel-config-color.decreaseValue.description = Il valore che rappresenta DIMINUZIONE
+channel-type.config.http.channel-config-color.increaseValue.label = Aumenta Valore
+channel-type.config.http.channel-config-color.increaseValue.description = Il valore che rappresenta AUMENTO
+channel-type.config.http.channel-config-color.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config-color.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config-color.mode.option.READONLY = Sola lettura
+channel-type.config.http.channel-config-color.mode.option.WRITEONLY = Sola scrittura
+channel-type.config.http.channel-config-color.offValue.label = Valore Spegnimento
+channel-type.config.http.channel-config-color.offValue.description = Il valore che rappresenta OFF
+channel-type.config.http.channel-config-color.onValue.label = Valore Acceso
+channel-type.config.http.channel-config-color.onValue.description = Il valore che rappresenta Acceso
+channel-type.config.http.channel-config-color.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-color.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-color.stateExtension.label = Stato Estensione URL
+channel-type.config.http.channel-config-color.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per recuperare i valori.
+channel-type.config.http.channel-config-color.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config-color.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-color.step.label = Incrementa/Diminuisci Passo
+channel-type.config.http.channel-config-color.step.description = Il valore di cui la luminosità corrente è aumentata/diminuita se viene ricevuto il comando corrispondente
+channel-type.config.http.channel-config-contact.closedValue.label = Valore Chiuso
+channel-type.config.http.channel-config-contact.closedValue.description = Il valore che rappresenta CHIUSO
+channel-type.config.http.channel-config-contact.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config-contact.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per l'invio dei valori.
+channel-type.config.http.channel-config-contact.commandTransformation.label = Comando Trasformazione
+channel-type.config.http.channel-config-contact.commandTransformation.description = Modello di trasformazione utilizzato per l'invio di valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-contact.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config-contact.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config-contact.mode.option.READONLY = Sola Lettura
+channel-type.config.http.channel-config-contact.mode.option.WRITEONLY = Sola Scrittura
+channel-type.config.http.channel-config-contact.openValue.label = Valore Aperto
+channel-type.config.http.channel-config-contact.openValue.description = Il valore che rappresenta APERTO
+channel-type.config.http.channel-config-contact.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-contact.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-contact.stateExtension.label = Estensione Stato URL
+channel-type.config.http.channel-config-contact.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per recuperare i valori.
+channel-type.config.http.channel-config-contact.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config-contact.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-dimmer.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config-dimmer.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per l'invio dei valori.
+channel-type.config.http.channel-config-dimmer.commandTransformation.label = Comando Trasformazione
+channel-type.config.http.channel-config-dimmer.commandTransformation.description = Modello di trasformazione utilizzato per l'invio di valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-dimmer.decreaseValue.label = Diminuisci Valore
+channel-type.config.http.channel-config-dimmer.decreaseValue.description = Il valore che rappresenta DIMINUIZIONE
+channel-type.config.http.channel-config-dimmer.increaseValue.label = Aumenta Valore
+channel-type.config.http.channel-config-dimmer.increaseValue.description = Il valore che rappresenta AUMENTO
+channel-type.config.http.channel-config-dimmer.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config-dimmer.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config-dimmer.mode.option.READONLY = Sola Lettura
+channel-type.config.http.channel-config-dimmer.mode.option.WRITEONLY = Sola Scrittura
+channel-type.config.http.channel-config-dimmer.offValue.label = Valore Spento
+channel-type.config.http.channel-config-dimmer.offValue.description = Il valore che rappresenta SPENTO
+channel-type.config.http.channel-config-dimmer.onValue.label = Valore Acceso
+channel-type.config.http.channel-config-dimmer.onValue.description = Il valore che rappresenta ACCESO
+channel-type.config.http.channel-config-dimmer.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-dimmer.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-dimmer.stateExtension.label = Estensione URL Stato
+channel-type.config.http.channel-config-dimmer.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per recuperare i valori.
+channel-type.config.http.channel-config-dimmer.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config-dimmer.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-dimmer.step.label = Passo Incrementa/Diminuisci
+channel-type.config.http.channel-config-dimmer.step.description = Il valore di cui la luminosità corrente è aumentata/diminuita se viene ricevuto il comando corrispondente
+channel-type.config.http.channel-config-image.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-image.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-image.stateExtension.label = Estensione URL Stato
+channel-type.config.http.channel-config-image.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per recuperare i valori.
+channel-type.config.http.channel-config-number.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config-number.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per l'invio dei valori.
+channel-type.config.http.channel-config-number.commandTransformation.label = Trasformazione Comandi
+channel-type.config.http.channel-config-number.commandTransformation.description = Modello di trasformazione utilizzato per l'invio di valori. Catena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-number.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config-number.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config-number.mode.option.READONLY = Sola lettura
+channel-type.config.http.channel-config-number.mode.option.WRITEONLY = Sola scrittura
+channel-type.config.http.channel-config-number.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-number.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-number.stateExtension.label = Estensione Stato URL
+channel-type.config.http.channel-config-number.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella cosa per recuperare i valori.
+channel-type.config.http.channel-config-number.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config-number.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-number.unit.label = Unità
+channel-type.config.http.channel-config-number.unit.description = Unità da aggiungere al valore (trasformato).
+channel-type.config.http.channel-config-player.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config-player.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per l'invio dei valori.
+channel-type.config.http.channel-config-player.commandTransformation.label = Comando Trasformazione
+channel-type.config.http.channel-config-player.commandTransformation.description = Modello di trasformazione utilizzato per l'invio di valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-player.fastforwardValue.label = Valore Avanti Veloce
+channel-type.config.http.channel-config-player.fastforwardValue.description = Il valore che rappresenta AVANTIVELOCE
+channel-type.config.http.channel-config-player.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config-player.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config-player.mode.option.READONLY = Sola Lettura
+channel-type.config.http.channel-config-player.mode.option.WRITEONLY = Sola Scrittura
+channel-type.config.http.channel-config-player.nextValue.label = Valore Successivo
+channel-type.config.http.channel-config-player.nextValue.description = Il valore che rappresenta SUCCESSIVO
+channel-type.config.http.channel-config-player.pauseValue.label = Valore Pausa
+channel-type.config.http.channel-config-player.pauseValue.description = Il valore che rappresenta PAUSA
+channel-type.config.http.channel-config-player.playValue.label = Valore Play
+channel-type.config.http.channel-config-player.playValue.description = Il valore che rappresenta PLAY
+channel-type.config.http.channel-config-player.previousValue.label = Valore Precedente
+channel-type.config.http.channel-config-player.previousValue.description = Il valore che rappresenta PRECEDENTE
+channel-type.config.http.channel-config-player.rewindValue.label = Valore Riavvolgimento
+channel-type.config.http.channel-config-player.rewindValue.description = Il valore che rappresenta RIAVVOLGIMENTO
+channel-type.config.http.channel-config-player.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-player.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-player.stateExtension.label = Stato Estensione URL
+channel-type.config.http.channel-config-player.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella cosa per recuperare i valori.
+channel-type.config.http.channel-config-player.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config-player.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-rollershutter.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config-rollershutter.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella cosa per l'invio dei valori.
+channel-type.config.http.channel-config-rollershutter.commandTransformation.label = Comando Trasformazione
+channel-type.config.http.channel-config-rollershutter.commandTransformation.description = Modello di trasformazione utilizzato quando si inviano valori Trasformazioni multiple concatenate con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-rollershutter.downValue.label = Valore In Basso
+channel-type.config.http.channel-config-rollershutter.downValue.description = Il valore che rappresenta GIU'
+channel-type.config.http.channel-config-rollershutter.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config-rollershutter.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config-rollershutter.mode.option.READONLY = Sola Lettura
+channel-type.config.http.channel-config-rollershutter.mode.option.WRITEONLY = Sola Scrittura
+channel-type.config.http.channel-config-rollershutter.moveValue.label = Valore Muovi
+channel-type.config.http.channel-config-rollershutter.moveValue.description = Il valore che rappresenta MUOVI
+channel-type.config.http.channel-config-rollershutter.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-rollershutter.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-rollershutter.stateExtension.label = Stato Estensione URL
+channel-type.config.http.channel-config-rollershutter.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella cosa per recuperare i valori.
+channel-type.config.http.channel-config-rollershutter.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config-rollershutter.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-rollershutter.stopValue.label = Valore Stop
+channel-type.config.http.channel-config-rollershutter.stopValue.description = Il valore che rappresenta STOP
+channel-type.config.http.channel-config-rollershutter.upValue.label = Valore In Alto
+channel-type.config.http.channel-config-rollershutter.upValue.description = Il valore che rappresenta SU
+channel-type.config.http.channel-config-switch.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config-switch.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per l'invio dei valori.
+channel-type.config.http.channel-config-switch.commandTransformation.label = Comando Trasformazione
+channel-type.config.http.channel-config-switch.commandTransformation.description = Modello di trasformazione utilizzato per l'invio di valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config-switch.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config-switch.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config-switch.mode.option.READONLY = Sola Lettura
+channel-type.config.http.channel-config-switch.mode.option.WRITEONLY = Sola Scrittura
+channel-type.config.http.channel-config-switch.offValue.label = Valore Spento
+channel-type.config.http.channel-config-switch.offValue.description = Il valore che rappresenta SPENTO
+channel-type.config.http.channel-config-switch.onValue.label = Valore Acceso
+channel-type.config.http.channel-config-switch.onValue.description = Il valore che rappresenta ACCESO
+channel-type.config.http.channel-config-switch.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config-switch.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config-switch.stateExtension.label = Stato Estensione URL
+channel-type.config.http.channel-config-switch.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per recuperare i valori.
+channel-type.config.http.channel-config-switch.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config-switch.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config.commandExtension.label = Comando Estensione URL
+channel-type.config.http.channel-config.commandExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per l'invio dei valori.
+channel-type.config.http.channel-config.commandTransformation.label = Comando Trasformazione
+channel-type.config.http.channel-config.commandTransformation.description = Modello di trasformazione utilizzato per l'invio di valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
+channel-type.config.http.channel-config.mode.label = Modalità Lettura/Scrittura
+channel-type.config.http.channel-config.mode.option.READWRITE = Lettura/Scrittura
+channel-type.config.http.channel-config.mode.option.READONLY = Sola Lettura
+channel-type.config.http.channel-config.mode.option.WRITEONLY = Sola Scrittura
+channel-type.config.http.channel-config.stateContent.label = Contenuto Stato
+channel-type.config.http.channel-config.stateContent.description = Contenuto per richiesta di stato (utilizzato solo se il metodo è POST/PUT)
+channel-type.config.http.channel-config.stateExtension.label = Stato Estensione URL
+channel-type.config.http.channel-config.stateExtension.description = Questo valore viene aggiunto all'URL di base configurato nella Thing per recuperare i valori.
+channel-type.config.http.channel-config.stateTransformation.label = Trasformazione Stato
+channel-type.config.http.channel-config.stateTransformation.description = Modello di trasformazione usato quando si ricevono i valori. Concatena trasformazioni multiple con il carattere di intersezione matematico "∩".
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java
index 496c956d23207..afadf2de32306 100644
--- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java
+++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java
@@ -90,7 +90,7 @@ protected void doSensorStateChanged(FullSensor sensor, Configuration config) {
}
if (sensor.getConfig().containsKey(CONFIG_LED_INDICATION)) {
- config.put(CONFIG_LED_INDICATION, sensor.getConfig().get(CONFIG_LIGHT_LEVEL_THRESHOLD_DARK));
+ config.put(CONFIG_LED_INDICATION, sensor.getConfig().get(CONFIG_LED_INDICATION));
}
if (sensor.getConfig().containsKey(CONFIG_LIGHT_LEVEL_THRESHOLD_DARK)) {
config.put(CONFIG_LIGHT_LEVEL_THRESHOLD_DARK, sensor.getConfig().get(CONFIG_LIGHT_LEVEL_THRESHOLD_DARK));
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java
index 75af6a4f69bc9..0c21cfe2e0749 100644
--- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java
+++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java
@@ -63,7 +63,7 @@ protected void doSensorStateChanged(FullSensor sensor, Configuration config) {
}
if (sensor.getConfig().containsKey(CONFIG_LED_INDICATION)) {
- config.put(CONFIG_LED_INDICATION, sensor.getConfig().get(CONFIG_LIGHT_LEVEL_THRESHOLD_DARK));
+ config.put(CONFIG_LED_INDICATION, sensor.getConfig().get(CONFIG_LED_INDICATION));
}
}
}
diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java
index 536b8462bd2cb..31ff0fa518fba 100644
--- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java
+++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java
@@ -394,7 +394,15 @@ public void dispose() {
logger.debug("removed {} address = {}", getThing().getUID().getAsString(), address);
}
- getInsteonNetworkHandler().disposed(getThing().getUID());
+ InsteonNetworkHandler handler = null;
+ try {
+ handler = getInsteonNetworkHandler();
+ } catch (IllegalArgumentException e) {
+ }
+
+ if (handler != null) {
+ handler.disposed(getThing().getUID());
+ }
}
super.dispose();
diff --git a/bundles/org.openhab.binding.lifx/src/main/java/org/openhab/binding/lifx/internal/LifxProduct.java b/bundles/org.openhab.binding.lifx/src/main/java/org/openhab/binding/lifx/internal/LifxProduct.java
index 0c0c800de2de7..15edff8b5aa36 100644
--- a/bundles/org.openhab.binding.lifx/src/main/java/org/openhab/binding/lifx/internal/LifxProduct.java
+++ b/bundles/org.openhab.binding.lifx/src/main/java/org/openhab/binding/lifx/internal/LifxProduct.java
@@ -116,7 +116,29 @@ public enum LifxProduct {
PRODUCT_111(111, "LIFX A19 Night Vision", new Features(TR_1500_9000, COLOR, INFRARED)),
PRODUCT_112(112, "LIFX BR30 Night Vision", new Features(TR_1500_9000, COLOR, INFRARED)),
PRODUCT_113(113, "LIFX Mini White to Warm", new Features(TR_1500_9000)),
- PRODUCT_114(114, "LIFX Mini White to Warm", new Features(TR_1500_9000));
+ PRODUCT_114(114, "LIFX Mini White to Warm", new Features(TR_1500_9000)),
+ PRODUCT_115(115, "LIFX Switch", new Features(BUTTONS, RELAYS)),
+ PRODUCT_116(116, "LIFX Switch", new Features(BUTTONS, RELAYS)),
+ PRODUCT_117(117, "LIFX Z", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)),
+ PRODUCT_118(118, "LIFX Z", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)),
+ PRODUCT_119(119, "LIFX Beam", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)),
+ PRODUCT_120(120, "LIFX Beam", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)),
+ PRODUCT_123(123, "LIFX Color", new Features(TR_1500_9000, COLOR)),
+ PRODUCT_124(124, "LIFX Color", new Features(TR_1500_9000, COLOR)),
+ PRODUCT_125(125, "LIFX White to Warm", new Features(TR_1500_9000)),
+ PRODUCT_126(126, "LIFX White to Warm", new Features(TR_1500_9000)),
+ PRODUCT_127(127, "LIFX White", new Features(TR_2700_2700)),
+ PRODUCT_128(128, "LIFX White", new Features(TR_2700_2700)),
+ PRODUCT_129(129, "LIFX Color", new Features(TR_1500_9000, COLOR)),
+ PRODUCT_130(130, "LIFX Color", new Features(TR_1500_9000, COLOR)),
+ PRODUCT_131(131, "LIFX White to Warm", new Features(TR_1500_9000)),
+ PRODUCT_132(132, "LIFX White to Warm", new Features(TR_1500_9000)),
+ PRODUCT_133(133, "LIFX White", new Features(TR_2700_2700)),
+ PRODUCT_134(134, "LIFX White", new Features(TR_2700_2700)),
+ PRODUCT_135(135, "LIFX GU10", new Features(TR_1500_9000, COLOR)),
+ PRODUCT_136(136, "LIFX GU10", new Features(TR_1500_9000, COLOR)),
+ PRODUCT_137(137, "LIFX Candle", new Features(TR_1500_9000, COLOR, MATRIX)),
+ PRODUCT_138(138, "LIFX Candle", new Features(TR_1500_9000, COLOR, MATRIX));
/**
* Enumerates the product features.
diff --git a/bundles/org.openhab.binding.mqtt.generic/README.md b/bundles/org.openhab.binding.mqtt.generic/README.md
index 788069084c712..eb679a04badf9 100644
--- a/bundles/org.openhab.binding.mqtt.generic/README.md
+++ b/bundles/org.openhab.binding.mqtt.generic/README.md
@@ -220,6 +220,7 @@ Once this action instance is retrieved, you can invoke the `publishMQTT(String t
```
mqttActions.publishMQTT("mytopic","myvalue", true)
```
+Alternatively, `publishMQTT(String topic, byte[] value, Boolean retained)` can publish a byte array data.
The retained argument is optional and if not supplied defaults to `false`.
diff --git a/bundles/org.openhab.binding.mqtt/README.md b/bundles/org.openhab.binding.mqtt/README.md
index 01521d6fa7c6c..b41323a04c9ea 100644
--- a/bundles/org.openhab.binding.mqtt/README.md
+++ b/bundles/org.openhab.binding.mqtt/README.md
@@ -17,7 +17,6 @@ MQTT topics. Please check out the available extensions:
## Supported Bridges
* Broker: This bridge represents an MQTT Broker connection, configured and managed by this binding.
-* SystemBroker: A system configured broker cannot be changed by this binding and will be listed as read-only system-broker.
## Bridge Configuration
@@ -39,10 +38,22 @@ Reconnect parameters are:
An MQTT last will and testament can be configured:
-* __lwtMessage__: An optional last will and testament message. Defaults to empty.
-* __lwtTopic__: The last will topic. Defaults to empty and therefore disables the last will.
-* __lwtQos__: The optional qos of the last will. Defaults to 0.
-* __lwtRetain__: Retain last will message. Defaults to false.
+* __lwtMessage__: An optional last will and testament message. Defaults to empty.
+* __lwtTopic__: The last will topic. Defaults to empty and therefore disables the last will.
+* __lwtQos__: The optional qos of the last will. Defaults to 0.
+* __lwtRetain__: Retain last will message. Defaults to true.
+
+An MQTT message can be published upon a successful connection to the MQTT broker with these parameters:
+
+* __birthMessage__: An optional message to be published once the bridge established a connection to the MQTT broker. Defaults to empty.
+* __birthTopic__: The birth topic. Defaults to empty and therefore no birth message will be published.
+* __birthRetain__: Retain the birth message. Defaults to true.
+
+An MQTT message can be published just before disconnecting from the broker with these parameters:
+
+* __shutdownMessage__: An optional message to be published before the bridge disconnects from the MQTT broker. Defaults to empty.
+* __shutdownTopic__: The shutdown topic. Defaults to empty and therefore no shutdown message will be published.
+* __shutdownRetain__: Retain the shutdown message. Defaults to true.
For more security, the following optional parameters can be altered:
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/MqttBindingConstants.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/MqttBindingConstants.java
index 438ee10d9bf04..101193ce0bc7f 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/MqttBindingConstants.java
+++ b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/MqttBindingConstants.java
@@ -26,7 +26,6 @@ public class MqttBindingConstants {
public static final String BINDING_ID = "mqtt";
// List of all Thing Type UIDs
- public static final ThingTypeUID BRIDGE_TYPE_SYSTEMBROKER = new ThingTypeUID(BINDING_ID, "systemBroker");
public static final ThingTypeUID BRIDGE_TYPE_BROKER = new ThingTypeUID(BINDING_ID, "broker");
public static final String PUBLISH_TRIGGER_CHANNEL = "publishTrigger";
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/AbstractBrokerHandler.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/AbstractBrokerHandler.java
index 6eb28d78233a8..f68f65f69e987 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/AbstractBrokerHandler.java
+++ b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/AbstractBrokerHandler.java
@@ -114,8 +114,6 @@ public void initialize() {
}).thenAccept(v -> {
if (!v) {
connectionStateChanged(MqttConnectionState.DISCONNECTED, new TimeoutException("Timeout"));
- } else {
- connectionStateChanged(MqttConnectionState.CONNECTED, null);
}
});
connectionFuture.complete(connection);
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandler.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandler.java
index c5eb7345c86a2..c48da5ccd0974 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandler.java
+++ b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandler.java
@@ -13,6 +13,7 @@
package org.openhab.binding.mqtt.handler;
import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -44,6 +45,7 @@
* connection to the {@link MqttService}.
*
* @author David Graeff - Initial contribution
+ * @author Jimmy Tanagra - Add birth and shutdown message
*/
@NonNullByDefault
public class BrokerHandler extends AbstractBrokerHandler implements PinnedCallback {
@@ -57,15 +59,18 @@ public BrokerHandler(Bridge thing) {
@Override
public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
super.connectionStateChanged(state, error);
- // Store generated client ID if none was set by the user
final MqttBrokerConnection connection = this.connection;
- String clientID = config.clientID;
- if (connection != null && state == MqttConnectionState.CONNECTED && (clientID == null || clientID.isBlank())) {
- clientID = connection.getClientId();
- config.clientID = clientID;
- Configuration editConfig = editConfiguration();
- editConfig.put("clientid", clientID);
- updateConfiguration(editConfig);
+ if (connection != null && state == MqttConnectionState.CONNECTED) {
+ String clientID = config.clientID;
+ if (clientID == null || clientID.isBlank()) {
+ // Store generated client ID if none was set by the user
+ clientID = connection.getClientId();
+ config.clientID = clientID;
+ Configuration editConfig = editConfiguration();
+ editConfig.put("clientid", clientID);
+ updateConfiguration(editConfig);
+ }
+ publish(config.birthTopic, config.birthMessage, config.birthRetain);
}
}
@@ -114,6 +119,8 @@ public void pinnedConnectionAccepted() {
public void dispose() {
try {
if (connection != null) {
+ publish(config.shutdownTopic, config.shutdownMessage, config.shutdownRetain).get(1000,
+ TimeUnit.MILLISECONDS);
connection.stop().get(1000, TimeUnit.MILLISECONDS);
} else {
logger.warn("Trying to dispose handler {} but connection is already null. Most likely this is a bug.",
@@ -236,4 +243,15 @@ public void initialize() {
super.initialize();
}
+
+ /**
+ * Calls the @NonNull MqttBrokerConnection::publish() with @Nullable topic and message
+ */
+ private CompletableFuture publish(@Nullable String topic, @Nullable String message, boolean retain) {
+ if (topic == null || connection == null) {
+ return CompletableFuture.completedFuture(true);
+ }
+ String nonNullMessage = message != null ? message : "";
+ return connection.publish(topic, nonNullMessage.getBytes(), connection.getQos(), retain);
+ }
}
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandlerConfig.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandlerConfig.java
index 5c8a2727bc932..170078fa1a66f 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandlerConfig.java
+++ b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/BrokerHandlerConfig.java
@@ -36,4 +36,14 @@ public class BrokerHandlerConfig extends MqttBrokerConnectionConfig {
public String publickey = "";
public boolean enableDiscovery = true;
+
+ // Birth message parameters
+ public @Nullable String birthTopic;
+ public @Nullable String birthMessage;
+ public Boolean birthRetain = true;
+
+ // Shutdown message parameters
+ public @Nullable String shutdownTopic;
+ public @Nullable String shutdownMessage;
+ public Boolean shutdownRetain = true;
}
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/SystemBrokerHandler.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/SystemBrokerHandler.java
deleted file mode 100644
index 4fcf2404e2233..0000000000000
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/handler/SystemBrokerHandler.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * 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.mqtt.handler;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
-import org.openhab.core.io.transport.mqtt.MqttConnectionState;
-import org.openhab.core.io.transport.mqtt.MqttService;
-import org.openhab.core.io.transport.mqtt.MqttServiceObserver;
-import org.openhab.core.io.transport.mqtt.MqttWillAndTestament;
-import org.openhab.core.io.transport.mqtt.reconnect.PeriodicReconnectStrategy;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-
-/**
- * This handler does not much except providing all information from a
- * {@link MqttBrokerConnection} via Thing properties and put the Thing
- * offline or online depending on the connection.
- *
- * @author David Graeff - Initial contribution
- */
-@NonNullByDefault
-public class SystemBrokerHandler extends AbstractBrokerHandler implements MqttServiceObserver {
- // Properties
- public static final String PROPERTY_URL = "url";
- public static final String PROPERTY_USERNAME = "username";
- public static final String PROPERTY_PASSWORD = "password";
- public static final String PROPERTY_QOS = "qos";
- public static final String PROPERTY_RETAIN = "retain";
- public static final String PROPERTY_LAST_WILL = "lastwill";
- public static final String PROPERTY_RECONNECT_TIME = "reconnect_time_ms";
- public static final String PROPERTY_KEEP_ALIVE_TIME = "keep_alive_time_ms";
- public static final String PROPERTY_CONNECT_TIMEOUT = "connect_timeout_ms";
-
- protected final MqttService service;
-
- protected String brokerID = "";
- protected boolean discoveryEnabled = true;
-
- public SystemBrokerHandler(Bridge thing, MqttService service) {
- super(thing);
- this.service = service;
- }
-
- @Override
- public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
- Map properties = new HashMap<>();
-
- properties.put(PROPERTY_URL, connection.getHost() + ":" + String.valueOf(connection.getPort()));
- final String username = connection.getUser();
- final String password = connection.getPassword();
- if (username != null && password != null) {
- properties.put(PROPERTY_USERNAME, username);
- properties.put(PROPERTY_PASSWORD, password);
- }
- properties.put(PROPERTY_QOS, String.valueOf(connection.getQos()));
- final MqttWillAndTestament lastWill = connection.getLastWill();
- if (lastWill != null) {
- properties.put(PROPERTY_LAST_WILL, lastWill.toString());
- } else {
- properties.put(PROPERTY_LAST_WILL, "");
- }
- if (connection.getReconnectStrategy() instanceof PeriodicReconnectStrategy) {
- final PeriodicReconnectStrategy strategy = (PeriodicReconnectStrategy) connection.getReconnectStrategy();
- if (strategy != null) {
- properties.put(PROPERTY_RECONNECT_TIME, String.valueOf(strategy.getReconnectFrequency()));
- }
- }
- properties.put(PROPERTY_KEEP_ALIVE_TIME, String.valueOf(connection.getKeepAliveInterval()));
-
- updateProperties(properties);
- super.connectionStateChanged(state, error);
- }
-
- /**
- * The base implementation will set the connection variable to the given broker
- * if it matches the brokerID and will start to connect to the broker if there
- * is no connection established yet.
- */
- @Override
- @SuppressWarnings("PMD.CompareObjectsWithEquals")
- public void brokerAdded(String connectionName, MqttBrokerConnection addedConnection) {
- if (!connectionName.equals(brokerID) || connection == addedConnection) {
- return;
- }
-
- this.connection = addedConnection;
- super.initialize();
- }
-
- @Override
- public void brokerRemoved(String connectionName, MqttBrokerConnection removedConnection) {
- final MqttBrokerConnection connection = this.connection;
- if (removedConnection.equals(connection)) {
- connection.removeConnectionObserver(this);
- this.connection = null;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.sharedremoved");
- return;
- }
- }
-
- @Override
- public void initialize() {
- this.brokerID = getThing().getConfiguration().get("brokerid").toString();
- this.discoveryEnabled = (Boolean) getThing().getConfiguration().get("enableDiscovery");
-
- service.addBrokersListener(this);
-
- connection = service.getBrokerConnection(brokerID);
- if (connection == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "@text/offline.notextualconfig [\"" + brokerID + "\"");
- return;
- }
- super.initialize();
- }
-
- @Override
- public void dispose() {
- service.removeBrokersListener(this);
- super.dispose();
- }
-
- @Override
- public boolean discoveryEnabled() {
- return discoveryEnabled;
- }
-}
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttBrokerHandlerFactory.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttBrokerHandlerFactory.java
index 79c8196a32dad..f68b75dde5e42 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttBrokerHandlerFactory.java
+++ b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttBrokerHandlerFactory.java
@@ -27,7 +27,6 @@
import org.openhab.binding.mqtt.discovery.MQTTTopicDiscoveryService;
import org.openhab.binding.mqtt.handler.AbstractBrokerHandler;
import org.openhab.binding.mqtt.handler.BrokerHandler;
-import org.openhab.binding.mqtt.handler.SystemBrokerHandler;
import org.openhab.core.io.transport.mqtt.MqttService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
@@ -54,8 +53,7 @@
public class MqttBrokerHandlerFactory extends BaseThingHandlerFactory implements MQTTTopicDiscoveryService {
private static final Set SUPPORTED_THING_TYPES_UIDS = Stream
- .of(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER, MqttBindingConstants.BRIDGE_TYPE_BROKER)
- .collect(Collectors.toSet());
+ .of(MqttBindingConstants.BRIDGE_TYPE_BROKER).collect(Collectors.toSet());
private final Logger logger = LoggerFactory.getLogger(MqttBrokerHandlerFactory.class);
@@ -107,9 +105,7 @@ protected void createdHandler(AbstractBrokerHandler handler) {
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
final AbstractBrokerHandler handler;
- if (thingTypeUID.equals(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER)) {
- handler = new SystemBrokerHandler((Bridge) thing, mqttService);
- } else if (thingTypeUID.equals(MqttBindingConstants.BRIDGE_TYPE_BROKER)) {
+ if (thingTypeUID.equals(MqttBindingConstants.BRIDGE_TYPE_BROKER)) {
handler = new BrokerHandler((Bridge) thing);
} else {
throw new IllegalStateException("Not supported " + thingTypeUID.toString());
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttThingID.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttThingID.java
index d9a3583a0454d..4069e4c403fa1 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttThingID.java
+++ b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/MqttThingID.java
@@ -38,8 +38,4 @@ public static String getThingID(String host, int port) {
public static ThingUID getThingUID(String host, int port) {
return new ThingUID(MqttBindingConstants.BRIDGE_TYPE_BROKER, getThingID(host, port));
}
-
- public static ThingUID getTextualThingUID(String host, int port) {
- return new ThingUID(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER, getThingID(host, port));
- }
}
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/action/MQTTActions.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/action/MQTTActions.java
index d867778549ac1..dad6decf02ef2 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/action/MQTTActions.java
+++ b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/action/MQTTActions.java
@@ -57,6 +57,25 @@ public void publishMQTT(
@ActionInput(name = "topic", label = "@text/actionInputTopicLabel", description = "@text/actionInputTopicDesc") @Nullable final String topic,
@ActionInput(name = "value", label = "@text/actionInputValueLabel", description = "@text/actionInputValueDesc") @Nullable final String value,
@ActionInput(name = "retain", label = "@text/actionInputRetainlabel", description = "@text/actionInputRetainDesc") @Nullable final Boolean retain) {
+ if (value == null) {
+ logger.debug("skipping MQTT publishing to topic '{}' due to null value.", topic);
+ return;
+ }
+ publishMQTT(topic, value.getBytes(), retain);
+ }
+
+ @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+ public void publishMQTT(
+ @ActionInput(name = "topic", label = "@text/actionInputTopicLabel", description = "@text/actionInputTopicDesc") @Nullable final String topic,
+ @ActionInput(name = "value", label = "@text/actionInputValueLabel", description = "@text/actionInputValueDesc") final byte[] value) {
+ publishMQTT(topic, value, null);
+ }
+
+ @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+ public void publishMQTT(
+ @ActionInput(name = "topic", label = "@text/actionInputTopicLabel", description = "@text/actionInputTopicDesc") @Nullable final String topic,
+ @ActionInput(name = "value", label = "@text/actionInputValueLabel", description = "@text/actionInputValueDesc") final byte[] value,
+ @ActionInput(name = "retain", label = "@text/actionInputRetainlabel", description = "@text/actionInputRetainDesc") @Nullable final Boolean retain) {
AbstractBrokerHandler brokerHandler = handler;
if (brokerHandler == null) {
logger.warn("MQTT Action service ThingHandler is null!");
@@ -67,22 +86,17 @@ public void publishMQTT(
logger.warn("MQTT Action service ThingHandler connection is null!");
return;
}
- if (value == null) {
- logger.debug("skipping MQTT publishing to topic '{}' due to null value.", topic);
- return;
- }
if (topic == null) {
logger.debug("skipping MQTT publishing of value '{}' as topic is null.", value);
return;
}
- connection.publish(topic, value.getBytes(), connection.getQos(), retain != null && retain.booleanValue())
- .thenRun(() -> {
- logger.debug("MQTT publish to {} performed", topic);
- }).exceptionally(e -> {
- logger.warn("MQTT publish to {} failed!", topic);
- return null;
- });
+ connection.publish(topic, value, connection.getQos(), retain != null && retain.booleanValue()).thenRun(() -> {
+ logger.debug("MQTT publish to {} performed", topic);
+ }).exceptionally(e -> {
+ logger.warn("MQTT publish to {} failed!", topic);
+ return null;
+ });
}
public static void publishMQTT(ThingActions actions, @Nullable String topic, @Nullable String value) {
@@ -93,4 +107,13 @@ public static void publishMQTT(ThingActions actions, @Nullable String topic, @Nu
@Nullable Boolean retain) {
((MQTTActions) actions).publishMQTT(topic, value, retain);
}
+
+ public static void publishMQTT(ThingActions actions, @Nullable String topic, byte[] value) {
+ publishMQTT(actions, topic, value, null);
+ }
+
+ public static void publishMQTT(ThingActions actions, @Nullable String topic, byte[] value,
+ @Nullable Boolean retain) {
+ ((MQTTActions) actions).publishMQTT(topic, value, retain);
+ }
}
diff --git a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/discovery/MqttServiceDiscoveryService.java b/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/discovery/MqttServiceDiscoveryService.java
deleted file mode 100644
index dfd8dbce90e90..0000000000000
--- a/bundles/org.openhab.binding.mqtt/src/main/java/org/openhab/binding/mqtt/internal/discovery/MqttServiceDiscoveryService.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * 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.mqtt.internal.discovery;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.openhab.binding.mqtt.MqttBindingConstants;
-import org.openhab.core.config.discovery.AbstractDiscoveryService;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.config.discovery.DiscoveryService;
-import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
-import org.openhab.core.io.transport.mqtt.MqttService;
-import org.openhab.core.io.transport.mqtt.MqttServiceObserver;
-import org.openhab.core.thing.ThingUID;
-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.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link MqttServiceDiscoveryService} is responsible for discovering connections on
- * the MqttService shared connection pool.
- *
- * @author David Graeff - Initial contribution
- */
-@Component(service = DiscoveryService.class, configurationPid = "discovery.systemmqttbroker")
-public class MqttServiceDiscoveryService extends AbstractDiscoveryService implements MqttServiceObserver {
- private final Logger logger = LoggerFactory.getLogger(MqttServiceDiscoveryService.class);
- MqttService mqttService;
-
- public MqttServiceDiscoveryService() {
- super(Stream.of(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER, MqttBindingConstants.BRIDGE_TYPE_BROKER)
- .collect(Collectors.toSet()), 0, true);
- }
-
- @Override
- @Activate
- protected void activate(Map config) {
- super.activate(config);
- }
-
- @Override
- @Deactivate
- protected void deactivate() {
- super.deactivate();
- }
-
- @Reference
- public void setMqttService(MqttService service) {
- mqttService = service;
- }
-
- public void unsetMqttService(MqttService service) {
- mqttService = null;
- }
-
- @Override
- protected void startScan() {
- mqttService.addBrokersListener(this);
- mqttService.getAllBrokerConnections().forEach((brokerId, broker) -> brokerAdded(brokerId, broker));
- stopScan();
- }
-
- @Override
- protected void startBackgroundDiscovery() {
- if (mqttService == null) {
- return;
- }
- mqttService.addBrokersListener(this);
- mqttService.getAllBrokerConnections().forEach((brokerId, broker) -> brokerAdded(brokerId, broker));
- }
-
- @Override
- protected void stopBackgroundDiscovery() {
- if (mqttService == null) {
- return;
- }
- mqttService.removeBrokersListener(this);
- }
-
- @Override
- public void brokerAdded(String brokerId, MqttBrokerConnection broker) {
- logger.trace("Found broker connection {}", brokerId);
-
- Map properties = new HashMap<>();
- properties.put("host", broker.getHost());
- properties.put("port", broker.getPort());
- properties.put("brokerid", brokerId);
- ThingUID thingUID;
- thingUID = new ThingUID(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER, brokerId);
- thingDiscovered(DiscoveryResultBuilder.create(thingUID).withProperties(properties)
- .withRepresentationProperty("brokerid").withLabel("MQTT Broker").build());
- }
-
- @Override
- public void brokerRemoved(String brokerId, MqttBrokerConnection broker) {
- ThingUID thingUID;
- thingUID = new ThingUID(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER, brokerId);
- thingRemoved(thingUID);
- }
-}
diff --git a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker.properties b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt.properties
similarity index 85%
rename from bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker.properties
rename to bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt.properties
index fca41c5352f70..3c131e9f1b6ae 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker.properties
+++ b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt.properties
@@ -7,11 +7,15 @@ binding.mqtt.description = Allows management of MQTT broker connections and link
thing-type.mqtt.broker.label = MQTT Broker
thing-type.mqtt.broker.description = A connection to a MQTT broker
-thing-type.mqtt.systemBroker.label = System MQTT Broker
-thing-type.mqtt.systemBroker.description = A system configured and therefore read-only broker connection. Properties are reflecting the configuration and internal connection status.
# thing types config
+thing-type.config.mqtt.broker.birthMessage.label = Birth Message
+thing-type.config.mqtt.broker.birthMessage.description = The message to send to the broker when a connection is established.
+thing-type.config.mqtt.broker.birthRetain.label = Birth Message Retain
+thing-type.config.mqtt.broker.birthRetain.description = True if the birth message should be retained (defaults to true)
+thing-type.config.mqtt.broker.birthTopic.label = Birth Topic
+thing-type.config.mqtt.broker.birthTopic.description = Defaults to empty and therefore disables the birth message.
thing-type.config.mqtt.broker.certificate.label = Certificate Hash
thing-type.config.mqtt.broker.certificate.description = If **certificatepin** is set this hash is used to verify the connection. Clear to allow a new certificate pinning on the next connection attempt. If empty will be filled automatically by the next successful connection. An example input would be `SHA-256:83F9171E06A313118889F7D79302BD1B7A2042EE0CFD029ABF8DD06FFA6CD9D3`.
thing-type.config.mqtt.broker.certificatepin.label = Certificate Pinning
@@ -51,12 +55,14 @@ thing-type.config.mqtt.broker.reconnectTime.label = Reconnect Time
thing-type.config.mqtt.broker.reconnectTime.description = Reconnect time in ms. If a connection is lost, the binding will wait this time before it tries to reconnect.
thing-type.config.mqtt.broker.secure.label = Secure Connection
thing-type.config.mqtt.broker.secure.description = Uses TLS/SSL to establish a secure connection to the broker.
+thing-type.config.mqtt.broker.shutdownMessage.label = Shutdown Message
+thing-type.config.mqtt.broker.shutdownMessage.description = The message to send to the broker before the connection terminates.
+thing-type.config.mqtt.broker.shutdownRetain.label = Shutdown Message Retain
+thing-type.config.mqtt.broker.shutdownRetain.description = True if the shutdown message should be retained (defaults to true)
+thing-type.config.mqtt.broker.shutdownTopic.label = Shutdown Topic
+thing-type.config.mqtt.broker.shutdownTopic.description = Defaults to empty and therefore disables the shutdown message.
thing-type.config.mqtt.broker.username.label = Username
thing-type.config.mqtt.broker.username.description = The MQTT username
-thing-type.config.mqtt.systemBroker.brokerid.label = Broker ID
-thing-type.config.mqtt.systemBroker.brokerid.description = Each system wide configured MQTT broker has a unique broker ID.
-thing-type.config.mqtt.systemBroker.enableDiscovery.label = Enable Discovery
-thing-type.config.mqtt.systemBroker.enableDiscovery.description = If set to true enables this broker for all discovery services.
# channel types
@@ -81,6 +87,4 @@ actionInputRetainDesc = Retain message
actionLabel = publish an MQTT message
actionDesc = Publishes a value to the given MQTT topic.
offline.notextualconfig = The system connection with the name {0} doesn't exist anymore.
-offline.dyninsteadoftextual = A binding owned connection was found instead of a system connection for the broker name: {0}.
-offline.textualinsteadofdny = A system connection was found instead of a dynamic connection for the broker name: {0}.
offline.sharedremoved = Another binding unexpectedly removed the internal broker connection.
diff --git a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_de.properties b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_de.properties
similarity index 74%
rename from bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_de.properties
rename to bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_de.properties
index ec6bf645c78d9..f4a2eef0866b4 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_de.properties
+++ b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_de.properties
@@ -2,8 +2,6 @@ binding.mqtt.name = MQTT Binding
binding.mqtt.description = Erlaubt die Verwaltung von MQTT Verbindungen und das Verknüpfen von MQTT Topics
offline.notextualconfig=Die Systemverbindung mit dem Namen {0} existiert nicht mehr.
-offline.dyninsteadoftextual=Eine dynamische Verbindung wurde gefunden, statt der erwarteten Systemverbindung: {0}.
-offline.textualinsteadofdny=Eine Systemverbindung wurde gefunden, statt der erwarteten dynamischen Verbindung: {0}.
offline.sharedremoved=Eine andere Erweiterung hat unerwartet die Broker Verbindung entfernt.
actionLabel=sende eine MQTT Nachricht
diff --git a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_hu.properties b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_hu.properties
similarity index 88%
rename from bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_hu.properties
rename to bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_hu.properties
index 0b2c2d0f303a3..275c41012da08 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_hu.properties
+++ b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_hu.properties
@@ -7,8 +7,6 @@ binding.mqtt.description = MQTT bróker kezelését teszi lehetővé, mellyel MQ
thing-type.mqtt.broker.label = MQTT bróker
thing-type.mqtt.broker.description = Kapcsolat az MQTT brókerhez
-thing-type.mqtt.systemBroker.label = Rendszer MQTT bróker
-thing-type.mqtt.systemBroker.description = Rendszerbeállított és ezért csak olvasható bróker kapcsolat. A tulajdonságok a beállítások és belső kapcsolat állapotot tükrözik.
# thing types config
@@ -53,10 +51,6 @@ thing-type.config.mqtt.broker.secure.label = Biztonságos kapcsolat
thing-type.config.mqtt.broker.secure.description = TLS/SSL használata a bróker biztonságos kapcsolódásához.
thing-type.config.mqtt.broker.username.label = Felhasználónév
thing-type.config.mqtt.broker.username.description = Az MQTT felhasználói neve
-thing-type.config.mqtt.systemBroker.brokerid.label = Bróker azonosító
-thing-type.config.mqtt.systemBroker.brokerid.description = Minden rendszerszintú MQTT brókerhez egyedi bórker azonosító tartozik.
-thing-type.config.mqtt.systemBroker.enableDiscovery.label = Felderítés használata
-thing-type.config.mqtt.systemBroker.enableDiscovery.description = Ha bekapcsolja a felderítés szolgáltatótk ezt a brókert fogják használni.
# channel types
@@ -81,6 +75,4 @@ actionInputRetainDesc = Üzenet megtartása
actionLabel = MQTT üzenet küldése
actionDesc = Egy megadott MQTT témára küldött üzenet.
offline.notextualconfig = A {0} nevű rendszerkapcsolat nem létezik.
-offline.dyninsteadoftextual = A {0} nevű brókerhez kötés alapú kapcsolat léteik, rendszerkapcsolat helyett.
-offline.textualinsteadofdny = A {0} nevű brókerhez dinamikus kapcsolat léteik, rendszerkapcsolat helyett.
offline.sharedremoved = Egy másik kötés váratlanul eltávolított a belső bróker kapcsolatot.
diff --git a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_it.properties b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_it.properties
similarity index 88%
rename from bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_it.properties
rename to bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_it.properties
index bcb64e0981c06..23d2db9c6543c 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqttbroker_it.properties
+++ b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/i18n/mqtt_it.properties
@@ -7,8 +7,6 @@ binding.mqtt.description = Consente la gestione delle connessioni dei broker MQT
thing-type.mqtt.broker.label = Broker MQTT
thing-type.mqtt.broker.description = Una connessione a un broker MQTT
-thing-type.mqtt.systemBroker.label = Broker MQTT di Sistema
-thing-type.mqtt.systemBroker.description = Un sistema configurato e quindi con connessione broker di sola lettura. Le proprietà riflettono lo stato della configurazione e della connessione interna.
# thing types config
@@ -53,10 +51,6 @@ 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.username.label = Username
thing-type.config.mqtt.broker.username.description = Il nome utente MQTT
-thing-type.config.mqtt.systemBroker.brokerid.label = ID Broker
-thing-type.config.mqtt.systemBroker.brokerid.description = Ogni broker MQTT configurato in tutto il sistema ha un ID broker unico.
-thing-type.config.mqtt.systemBroker.enableDiscovery.label = Abilita Ricerca
-thing-type.config.mqtt.systemBroker.enableDiscovery.description = Se impostato a true abilita questo broker per la scoperta di tutti i servizi.
# channel types
@@ -81,6 +75,4 @@ actionInputRetainDesc = Messaggio da Conservare
actionLabel = pubblica un messaggio MQTT
actionDesc = Pubblica un valore sul topic MQTT specificato.
offline.notextualconfig = La connessione di sistema con il nome {0} non esiste più.
-offline.dyninsteadoftextual = È stata trovata una connessione associata al binding invece di una connessione di sistema per il nome del broker\: {0}.
-offline.textualinsteadofdny = È stata trovata una connessione di sistema invece di una connessione dinamica per il broker di nome\: {0}.
offline.sharedremoved = Un altro binding ha rimosso inaspettatamente la connessione interna del broker.
diff --git a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/thing/thing-types.xml
index d3d7699150472..0f9f5a68aa53c 100644
--- a/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.mqtt/src/main/resources/OH-INF/thing/thing-types.xml
@@ -94,6 +94,44 @@
true
+
+
+ The message to send to the broker when a connection is established.
+ true
+
+
+
+
+ Defaults to empty and therefore disables the birth message.
+ true
+
+
+
+
+ True if the birth message should be retained (defaults to true)
+ true
+ true
+
+
+
+
+ The message to send to the broker before the connection terminates.
+ true
+
+
+
+
+ Defaults to empty and therefore disables the shutdown message.
+ true
+
+
+
+
+ True if the shutdown message should be retained (defaults to true)
+ false
+ true
+
+
The MQTT username
@@ -144,36 +182,6 @@
-
-
- A system configured and therefore read-only broker connection. Properties are reflecting the
- configuration and internal connection status.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Each system wide configured MQTT broker has a unique broker ID.
-
-
-
- If set to true enables this broker for all discovery services.
- true
- true
-
-
-
-
trigger
diff --git a/bundles/org.openhab.binding.mqtt/src/test/java/org/openhab/binding/mqtt/handler/AbstractBrokerHandlerTest.java b/bundles/org.openhab.binding.mqtt/src/test/java/org/openhab/binding/mqtt/handler/AbstractBrokerHandlerTest.java
deleted file mode 100644
index b43f511a0279a..0000000000000
--- a/bundles/org.openhab.binding.mqtt/src/test/java/org/openhab/binding/mqtt/handler/AbstractBrokerHandlerTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * 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.mqtt.handler;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
-
-import java.util.Collections;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.mqtt.internal.MqttThingID;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
-import org.openhab.core.io.transport.mqtt.MqttException;
-import org.openhab.core.io.transport.mqtt.MqttService;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.binding.ThingHandlerCallback;
-import org.osgi.service.cm.ConfigurationException;
-
-/**
- * Tests cases for {@link org.openhab.binding.mqtt.handler.AbstractBrokerHandler}.
- *
- * @author David Graeff - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.WARN)
-public class AbstractBrokerHandlerTest {
- private final String HOST = "tcp://123.1.2.3";
- private final int PORT = 80;
- private SystemBrokerHandler handler;
- int stateChangeCounter = 0;
-
- private @Mock ThingHandlerCallback callback;
- private @Mock Bridge thing;
- private @Mock MqttService service;
-
- @BeforeEach
- public void setUp() {
- doReturn(new Configuration(Collections.singletonMap("brokerid", MqttThingID.getThingUID(HOST, PORT).getId())))
- .when(thing).getConfiguration();
- handler = new SystemBrokerHandler(thing, service);
- handler.setCallback(callback);
- assertThat(handler.getThing().getConfiguration().get("brokerid"), is(MqttThingID.getThingID(HOST, PORT)));
- stateChangeCounter = 0;
- }
-
- @Test
- public void brokerAddedWrongID() throws ConfigurationException, MqttException {
- MqttBrokerConnection brokerConnection = mock(MqttBrokerConnection.class);
- handler.brokerAdded("nonsense_id", brokerConnection);
- assertNull(handler.connection);
- // We do not expect a status change, because brokerAdded will do nothing with invalid connections.
- verify(callback, times(0)).statusUpdated(any(), any());
- }
-
- @Test
- public void brokerRemovedBroker() throws ConfigurationException, MqttException {
- MqttBrokerConnectionEx connection = spy(
- new MqttBrokerConnectionEx("10.10.0.10", 80, false, "BrokerHandlerTest"));
- handler.brokerAdded(handler.brokerID, connection);
- assertThat(handler.connection, is(connection));
- handler.brokerRemoved("something", connection);
- assertNull(handler.connection);
- }
-
- @Test
- public void brokerAdded() throws ConfigurationException, MqttException {
- MqttBrokerConnectionEx connection = spy(
- new MqttBrokerConnectionEx("10.10.0.10", 80, false, "BrokerHandlerTest"));
-
- verify(callback, times(0)).statusUpdated(any(), any());
- handler.brokerAdded(handler.brokerID, connection);
-
- assertThat(handler.connection, is(connection));
-
- verify(connection).start();
-
- // First connecting then connected and another connected after the future completes
- verify(callback, times(3)).statusUpdated(any(), any());
- }
-}
diff --git a/bundles/org.openhab.binding.mqtt/src/test/java/org/openhab/binding/mqtt/internal/discovery/ServiceDiscoveryServiceTest.java b/bundles/org.openhab.binding.mqtt/src/test/java/org/openhab/binding/mqtt/internal/discovery/ServiceDiscoveryServiceTest.java
deleted file mode 100644
index f69bd03b363f0..0000000000000
--- a/bundles/org.openhab.binding.mqtt/src/test/java/org/openhab/binding/mqtt/internal/discovery/ServiceDiscoveryServiceTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * 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.mqtt.internal.discovery;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.*;
-
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.mqtt.MqttBindingConstants;
-import org.openhab.core.config.discovery.DiscoveryListener;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
-import org.openhab.core.io.transport.mqtt.MqttService;
-
-/**
- * Tests cases for {@link org.openhab.binding.mqtt.internal.discovery.MqttServiceDiscoveryService}.
- *
- * @author David Graeff - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.WARN)
-public class ServiceDiscoveryServiceTest {
-
- private @Mock MqttService service;
- private @Mock DiscoveryListener discoverListener;
-
- @BeforeEach
- public void initMocks() {
- Map brokers = new TreeMap<>();
- brokers.put("testname", new MqttBrokerConnection("tcp://123.123.123.123", null, false, null));
- brokers.put("textual", new MqttBrokerConnection("tcp://123.123.123.123", null, true, null));
- when(service.getAllBrokerConnections()).thenReturn(brokers);
- }
-
- @Test
- public void testDiscovery() {
- // Setting the MqttService will enable the background scanner
- MqttServiceDiscoveryService d = new MqttServiceDiscoveryService();
- d.addDiscoveryListener(discoverListener);
- d.setMqttService(service);
- d.startScan();
-
- // We expect 3 discoveries. An embedded thing, a textual configured one, a non-textual one
- ArgumentCaptor discoveryCapture = ArgumentCaptor.forClass(DiscoveryResult.class);
- verify(discoverListener, times(2)).thingDiscovered(eq(d), discoveryCapture.capture());
- List discoveryResults = discoveryCapture.getAllValues();
- assertThat(discoveryResults.size(), is(2));
- assertThat(discoveryResults.get(0).getThingTypeUID(), is(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER));
- assertThat(discoveryResults.get(1).getThingTypeUID(), is(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER));
-
- // Add another thing
- d.brokerAdded("anotherone", new MqttBrokerConnection("tcp://123.123.123.123", null, false, null));
- discoveryCapture = ArgumentCaptor.forClass(DiscoveryResult.class);
- verify(discoverListener, times(3)).thingDiscovered(eq(d), discoveryCapture.capture());
- discoveryResults = discoveryCapture.getAllValues();
- assertThat(discoveryResults.size(), is(3));
- assertThat(discoveryResults.get(2).getThingTypeUID(), is(MqttBindingConstants.BRIDGE_TYPE_SYSTEMBROKER));
- }
-}
diff --git a/bundles/org.openhab.binding.playstation/README.md b/bundles/org.openhab.binding.playstation/README.md
index 3319ea20c6d97..ec7421b83574d 100644
--- a/bundles/org.openhab.binding.playstation/README.md
+++ b/bundles/org.openhab.binding.playstation/README.md
@@ -30,8 +30,12 @@ Settings -> System Settings -> Connect PS Vita System Using Network.
| outboundIP | | No | Use this if your PS4 is not on the normal openHAB network. |
| ipPort | 997 | No | The port to probe the PS4 on, no need to change normally. |
-If you want to control your PS4 the first thing you need is your user-credentials, this is a 64 characters HEX string that is easiest obtained by using PS4-waker https://github.com/dhleong/ps4-waker.
-The result file is called ".ps4-wake.credentials.json" in your home directory.
+If you want to control your PS4 the first thing you need is your user-credentials, this is a 64 characters HEX string that is easiest obtained by using PS4-waker https://github.com/dhleong/ps4-waker.
+To run the PS4-waker you will need a Node.js command prompt (for example: https://nodejs.org/en/download/).
+Enter "npx ps4-waker --help" int the command prompt. Agree to install by entering "y".
+After that send "npx ps4-waker --check". You will get asked to connect the "PS4 Second screen" Android app to the running clone.
+Do this and then you will need to get the pairing key from your PS4 --> Settings ---> Mobile device pairing settings.
+On the PS4 screen you will see your pairing code and in the command prompt you will find the user credentials.
Then you need to pair your openHAB device with the PS4.
This can be done by saving the Thing while the pairing screen is open on the PS4. The code is only needed during pairing.
diff --git a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/PlexBindingConstants.java b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/PlexBindingConstants.java
index 083faf1446311..d271aa621c519 100644
--- a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/PlexBindingConstants.java
+++ b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/PlexBindingConstants.java
@@ -75,8 +75,9 @@ public class PlexBindingConstants {
public static final String CHANNEL_PLAYER_THUMB = "thumb";
public static final String CHANNEL_PLAYER_PROGRESS = "progress";
public static final String CHANNEL_PLAYER_ENDTIME = "endtime";
+ public static final String CHANNEL_PLAYER_CONTROL = "player";
public static final Set SUPPORTED_CHANNEL_IDS = Stream.of(CHANNEL_SERVER_COUNT, CHANNEL_SERVER_COUNTACTIVE,
CHANNEL_PLAYER_STATE, CHANNEL_PLAYER_TITLE, CHANNEL_PLAYER_TYPE, CHANNEL_PLAYER_POWER, CHANNEL_PLAYER_ART,
- CHANNEL_PLAYER_PROGRESS, CHANNEL_PLAYER_ENDTIME, CHANNEL_PLAYER_THUMB).collect(Collectors.toSet());
+ CHANNEL_PLAYER_PROGRESS, CHANNEL_PLAYER_ENDTIME, CHANNEL_PLAYER_THUMB, CHANNEL_PLAYER_CONTROL).collect(Collectors.toSet());
}
diff --git a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexApiConnector.java b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexApiConnector.java
index 077185930dcd3..51b1fe65fbe76 100644
--- a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexApiConnector.java
+++ b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexApiConnector.java
@@ -41,6 +41,9 @@
import org.openhab.binding.plex.internal.dto.NotificationContainer;
import org.openhab.binding.plex.internal.dto.User;
import org.openhab.core.io.net.http.HttpUtil;
+import org.openhab.core.library.types.NextPreviousType;
+import org.openhab.core.library.types.PlayPauseType;
+import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -356,4 +359,48 @@ private void asyncWeb() {
}
}
}
+
+ /**
+ * Handles control commands to the plex player.
+ *
+ * Supports:
+ * - Play / Pause
+ * - Previous / Next
+ *
+ * @param command The control command
+ * @param playerID The ID of the Plex player
+ */
+ public void controlPlayer(Command command, String playerID) {
+ String commandPath = null;
+ if (command instanceof PlayPauseType) {
+ if (command.equals(PlayPauseType.PLAY)) {
+ commandPath = "/player/playback/play";
+ }
+ if (command.equals(PlayPauseType.PAUSE)) {
+ commandPath = "/player/playback/pause";
+ }
+ }
+
+ if (command instanceof NextPreviousType) {
+ if (command.equals(NextPreviousType.PREVIOUS)) {
+ commandPath = "/player/playback/skipPrevious";
+ }
+ if (command.equals(NextPreviousType.NEXT)) {
+ commandPath = "/player/playback/skipNext";
+ }
+ }
+
+ if (commandPath != null) {
+ try {
+ String url = "http://" + host + ":" + String.valueOf(port) + commandPath;
+ Properties headers = getClientHeaders();
+ headers.put("X-Plex-Target-Client-Identifier", playerID);
+ HttpUtil.executeUrl("GET", url, headers, null, null, REQUEST_TIMEOUT_MS);
+ } catch (Exception e) {
+ logger.error("An exception occurred trying to send command '{}' to the play player: {}", commandPath, e.getMessage());
+ }
+ } else {
+ logger.warn("Could not match command '{}' to an action", command);
+ }
+ }
}
diff --git a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexPlayerHandler.java b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexPlayerHandler.java
index 8dd3fc66982e6..acc5d16416e27 100644
--- a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexPlayerHandler.java
+++ b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexPlayerHandler.java
@@ -16,19 +16,23 @@
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.plex.internal.PlexBindingConstants;
import org.openhab.binding.plex.internal.config.PlexPlayerConfiguration;
+import org.openhab.binding.plex.internal.config.PlexServerConfiguration;
import org.openhab.binding.plex.internal.dto.MediaContainer.MediaType;
import org.openhab.binding.plex.internal.dto.PlexPlayerState;
import org.openhab.binding.plex.internal.dto.PlexSession;
+import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.*;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import static org.openhab.binding.plex.internal.PlexBindingConstants.*;
+
/**
* The {@link PlexBindingConstants} class defines common constants, which are
* used across the whole binding.
@@ -45,9 +49,12 @@ public class PlexPlayerHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PlexPlayerHandler.class);
+ private PlexApiConnector plexAPIConnector;
+
public PlexPlayerHandler(Thing thing) {
super(thing);
currentSessionData = new PlexSession();
+ plexAPIConnector = new PlexApiConnector(scheduler);
}
/**
@@ -78,8 +85,15 @@ public void initialize() {
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
- // Readonly, we don't have any channels
- // logger.warn("Handling command '{}' for {}", command, channelUID);
+ assert bridgeHandler != null;
+ PlexApiConnector plexApiConnector = bridgeHandler.getPlexAPIConnector();
+ switch (channelUID.getId()) {
+ case CHANNEL_PLAYER_CONTROL:
+ plexApiConnector.controlPlayer(command, playerID);
+ break;
+ default:
+ logger.debug("Channel {} not implemented/supported to control player {}", channelUID.getId(), this.thing.getUID());
+ }
}
/**
@@ -162,5 +176,13 @@ public synchronized void updateChannels() {
new StringType(String.valueOf(foundInSession ? currentSessionData.getProgress() : "0")));
updateState(new ChannelUID(getThing().getUID(), PlexBindingConstants.CHANNEL_PLAYER_ENDTIME),
new StringType(String.valueOf(foundInSession ? currentSessionData.getEndTime() : "")));
+
+ // Make sure player control is in sync with the play state
+ if (currentSessionData.getState() == PlexPlayerState.Playing) {
+ updateState(new ChannelUID(getThing().getUID(), PlexBindingConstants.CHANNEL_PLAYER_CONTROL), PlayPauseType.PLAY);
+ }
+ if (currentSessionData.getState() == PlexPlayerState.Paused) {
+ updateState(new ChannelUID(getThing().getUID(), PlexBindingConstants.CHANNEL_PLAYER_CONTROL), PlayPauseType.PAUSE);
+ }
}
}
diff --git a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexServerHandler.java b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexServerHandler.java
index c144aa396eb18..42828ad2706c2 100644
--- a/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexServerHandler.java
+++ b/bundles/org.openhab.binding.plex/src/main/java/org/openhab/binding/plex/internal/handler/PlexServerHandler.java
@@ -72,6 +72,10 @@ public PlexServerHandler(Bridge bridge, PlexStateDescriptionOptionProvider state
this.stateDescriptionProvider = stateDescriptionProvider;
}
+ public PlexApiConnector getPlexAPIConnector() {
+ return plexAPIConnector;
+ }
+
/**
* Initialize the Bridge set the config paramaters for the PLEX Server and
* start the refresh Job.
diff --git a/bundles/org.openhab.binding.plex/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.plex/src/main/resources/OH-INF/thing/thing-types.xml
index 12054e4de9645..a16d2d5fe7eca 100644
--- a/bundles/org.openhab.binding.plex/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.plex/src/main/resources/OH-INF/thing/thing-types.xml
@@ -30,6 +30,7 @@
+
@@ -93,4 +94,9 @@
The URL of the cover art for currently playing media
+
+ Player
+
+ Control the Player
+
diff --git a/bundles/org.openhab.binding.teleinfo/src/main/resources/OH-INF/i18n/teleinfo.properties b/bundles/org.openhab.binding.teleinfo/src/main/resources/OH-INF/i18n/teleinfo.properties
index 11cf16fdfe5e2..1ff23b27655c3 100644
--- a/bundles/org.openhab.binding.teleinfo/src/main/resources/OH-INF/i18n/teleinfo.properties
+++ b/bundles/org.openhab.binding.teleinfo/src/main/resources/OH-INF/i18n/teleinfo.properties
@@ -29,17 +29,230 @@ thing-type.teleinfo.cbetm_hc_electricitymeter.label = CBETM HC/HP
thing-type.teleinfo.cbetm_hc_electricitymeter.description = Three-phase Electricity meter with HC option - CBETM (aka "Compteur Bleu Electronique Triphasé Multitarif")
thing-type.teleinfo.cbetm_tempo_electricitymeter.label = CBETM TEMPO
thing-type.teleinfo.cbetm_tempo_electricitymeter.description = Three-phase Electricity meter with TEMPO option - CBETM (aka "Compteur Bleu Electronique Triphasé Multitarif")
+thing-type.teleinfo.lsmm_electricitymeter.label = Linky Single-phase
+thing-type.teleinfo.lsmm_electricitymeter.description = Single-phase Linky Electricity meter in standard mode
+thing-type.teleinfo.lsmm_prod_electricitymeter.label = Linky Single-phase Producer
+thing-type.teleinfo.lsmm_prod_electricitymeter.description = Single-phase producer Linky Electricity meter in standard mode
+thing-type.teleinfo.lsmt_electricitymeter.label = Linky Three-phase
+thing-type.teleinfo.lsmt_electricitymeter.description = Three-phase Linky Electricity meter in standard mode
+thing-type.teleinfo.lsmt_prod_electricitymeter.label = Linky Three-phase Producer
+thing-type.teleinfo.lsmt_prod_electricitymeter.description = Three-phase producer Linky Electricity meter in standard mode
thing-type.teleinfo.serialcontroller.label = Teleinfo Serial Controller
thing-type.teleinfo.serialcontroller.description = Teleinfo USB Stick with Serial Interface
# thing types config
-thing-type.config.teleinfo.adco.adco.label = ADCO
+thing-type.config.teleinfo.adco.adco.label = ADCO/ADSC
thing-type.config.teleinfo.adco.adco.description = Electricity meter identifier (format: 12 characters / e.g: '031528042289')
thing-type.config.teleinfo.serialcontroller.autoRepairInvalidADPSgroupLine.label = Auto Repair Malformed ADPS Data
thing-type.config.teleinfo.serialcontroller.autoRepairInvalidADPSgroupLine.description = Try to auto repair malformed ADPS data from hardware issues (e.g: "ADPS032" instead of "ADPS 032" expected/well-formed data)
thing-type.config.teleinfo.serialcontroller.serialport.label = Serial Port
thing-type.config.teleinfo.serialcontroller.serialport.description = Serial port of Teleinfo device (e.g.: /dev/ttyUSB0 on Linux, COM1 on Windows)
+thing-type.config.teleinfo.serialcontroller.ticMode.label = TIC mode
+thing-type.config.teleinfo.serialcontroller.ticMode.description = TIC Mode of the telemeter (Standard TIC mode is only available on Linky telemeters)
+thing-type.config.teleinfo.serialcontroller.ticMode.option.HISTORICAL = Historical
+thing-type.config.teleinfo.serialcontroller.ticMode.option.STANDARD = Standard
+thing-type.config.teleinfo.serialcontroller.verifyChecksum.label = Checksum Verification
+thing-type.config.teleinfo.serialcontroller.verifyChecksum.description = Activate checksum verification
+
+# channel group types
+
+channel-group-type.teleinfo.commonLSMGroupType.label = Common
+channel-group-type.teleinfo.commonLSMGroupType.description = Common channels for Linky telemeter in standard teleinformation mode
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasn.label = CCASN
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasn.description = Active charge point N
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasnDate.label = CCASN TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasnDate.description = Timestamp of CCASN value
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasnMinus1.label = CCASN-1
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasnMinus1.description = Active charge point N-1
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasnMinus1Date.label = CCASN-1 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.ccasnMinus1Date.description = Timestamp of CCASN-1 value
+channel-group-type.teleinfo.commonLSMGroupType.channel.date.label = DATE
+channel-group-type.teleinfo.commonLSMGroupType.channel.date.description = Date and Time
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm1.label = DPM1
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm1.description = Start of mobile peak period 1
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm1Date.label = DPM1 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm1Date.description = Date of DPM1
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm2.label = DPM2
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm2.description = Start of mobile peak period 2
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm2Date.label = DPM2 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm2Date.description = Date of DPM2
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm3.label = DPM3
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm3.description = Start of mobile peak period 3
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm3Date.label = DPM3 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.dpm3Date.description = Date of DPM3
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd01.label = EASD01
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd01.description = Active energy withdrawn from distributor on index 01
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd02.label = EASD02
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd02.description = Active energy withdrawn from distributor on index 02
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd03.label = EASD03
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd03.description = Active energy withdrawn from distributor on index 03
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd04.label = EASD04
+channel-group-type.teleinfo.commonLSMGroupType.channel.easd04.description = Active energy withdrawn from distributor on index 04
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf01.label = EASF01
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf01.description = Active energy withdrawn from provider on index 01
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf02.label = EASF02
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf02.description = Active energy withdrawn from provider on index 02
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf03.label = EASF03
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf03.description = Active energy withdrawn from provider on index 03
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf04.label = EASF04
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf04.description = Active energy withdrawn from provider on index 04
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf05.label = EASF05
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf05.description = Active energy withdrawn from provider on index 05
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf06.label = EASF06
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf06.description = Active energy withdrawn from provider on index 06
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf07.label = EASF07
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf07.description = Active energy withdrawn from provider on index 07
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf08.label = EASF08
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf08.description = Active energy withdrawn from provider on index 08
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf09.label = EASF09
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf09.description = Active energy withdrawn from provider on index 09
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf10.label = EASF10
+channel-group-type.teleinfo.commonLSMGroupType.channel.easf10.description = Active energy withdrawn from provider on index 10
+channel-group-type.teleinfo.commonLSMGroupType.channel.east.label = EAST
+channel-group-type.teleinfo.commonLSMGroupType.channel.east.description = Total active energy withdrawn
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm1.label = FPM1
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm1.description = End of mobile peak period 1
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm1Date.label = FPM1 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm1Date.description = Date of FPM1
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm2.label = FPM2
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm2.description = End of mobile peak period 2
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm2Date.label = FPM2 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm2Date.description = Date of FPM2
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm3.label = FPM3
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm3.description = End of mobile peak period 3
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm3Date.label = FPM3 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.fpm3Date.description = Date of FPM3
+channel-group-type.teleinfo.commonLSMGroupType.channel.irms1.label = IRMS1
+channel-group-type.teleinfo.commonLSMGroupType.channel.irms1.description = RMS Current on phase 1
+channel-group-type.teleinfo.commonLSMGroupType.channel.ltarf.label = LTARF
+channel-group-type.teleinfo.commonLSMGroupType.channel.ltarf.description = Current pricing label
+channel-group-type.teleinfo.commonLSMGroupType.channel.msg1.label = MSG1
+channel-group-type.teleinfo.commonLSMGroupType.channel.msg1.description = Short message
+channel-group-type.teleinfo.commonLSMGroupType.channel.msg2.label = MSG2
+channel-group-type.teleinfo.commonLSMGroupType.channel.msg2.description = Very short message
+channel-group-type.teleinfo.commonLSMGroupType.channel.ngtf.label = NGTF
+channel-group-type.teleinfo.commonLSMGroupType.channel.ngtf.description = Provider schedule name
+channel-group-type.teleinfo.commonLSMGroupType.channel.njourf.label = NJOURF
+channel-group-type.teleinfo.commonLSMGroupType.channel.njourf.description = Number of current provider schedule
+channel-group-type.teleinfo.commonLSMGroupType.channel.njourfPlus1.label = NJOURF+1
+channel-group-type.teleinfo.commonLSMGroupType.channel.njourfPlus1.description = Number of next day provider schedule
+channel-group-type.teleinfo.commonLSMGroupType.channel.ntarf.label = NTARF
+channel-group-type.teleinfo.commonLSMGroupType.channel.ntarf.description = Index of current pricing
+channel-group-type.teleinfo.commonLSMGroupType.channel.pcoup.label = PCOUP
+channel-group-type.teleinfo.commonLSMGroupType.channel.pcoup.description = Apparent power rupture capacity
+channel-group-type.teleinfo.commonLSMGroupType.channel.pjourfPlus1.label = PJOURF+1
+channel-group-type.teleinfo.commonLSMGroupType.channel.pjourfPlus1.description = Profile of next day provider schedule
+channel-group-type.teleinfo.commonLSMGroupType.channel.ppointe.label = PPOINTE
+channel-group-type.teleinfo.commonLSMGroupType.channel.ppointe.description = Profile of next rush day
+channel-group-type.teleinfo.commonLSMGroupType.channel.pref.label = PREF
+channel-group-type.teleinfo.commonLSMGroupType.channel.pref.description = Reference apparent power
+channel-group-type.teleinfo.commonLSMGroupType.channel.prm.label = PRM
+channel-group-type.teleinfo.commonLSMGroupType.channel.prm.description = PRM
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais1.label = Relais 1
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais2.label = Relais 2
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais3.label = Relais 3
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais4.label = Relais 4
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais5.label = Relais 5
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais6.label = Relais 6
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais7.label = Relais 7
+channel-group-type.teleinfo.commonLSMGroupType.channel.relais8.label = Relais 8
+channel-group-type.teleinfo.commonLSMGroupType.channel.sinsts.label = SINSTS
+channel-group-type.teleinfo.commonLSMGroupType.channel.sinsts.description = Instantaneous withdrawn apparent power
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsn.label = SMAXSN
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsn.description = Maximum withdrawn apparent power of the day
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsnDate.label = SMAXSN TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsnDate.description = Timestamp of SMAXSN value
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsnMinus1.label = SMAXSN-1
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsnMinus1.description = Maximum withdrawn apparent power of the previous day
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsnMinus1Date.label = SMAXSN-1 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.smaxsnMinus1Date.description = Timestamp of SMAXSN-1 value
+channel-group-type.teleinfo.commonLSMGroupType.channel.stge.label = STGE
+channel-group-type.teleinfo.commonLSMGroupType.channel.stge.description = Status registry
+channel-group-type.teleinfo.commonLSMGroupType.channel.umoy1.label = UMOY1
+channel-group-type.teleinfo.commonLSMGroupType.channel.umoy1.description = Mean Voltage on phase 1
+channel-group-type.teleinfo.commonLSMGroupType.channel.umoy1Date.label = UMOY1 TIMESTAMP
+channel-group-type.teleinfo.commonLSMGroupType.channel.umoy1Date.description = Timestamp of UMOY1 value
+channel-group-type.teleinfo.commonLSMGroupType.channel.urms1.label = URMS1
+channel-group-type.teleinfo.commonLSMGroupType.channel.urms1.description = RMS Voltage on phase 1
+channel-group-type.teleinfo.producerLSMGroupType.label = Producer
+channel-group-type.teleinfo.producerLSMGroupType.description = Producer channels for Linky telemeter in standard teleinformation mode
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccain.label = CCAIN
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccain.description = Injected active charge point N
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccainDate.label = CCAIN TIMESTAMP
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccainDate.description = Timestamp of CCAIN value
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccainMinus1.label = CCAIN-1
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccainMinus1.description = Injected active charge point N-1
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccainMinus1Date.label = CCAIN-1 TIMESTAMP
+channel-group-type.teleinfo.producerLSMGroupType.channel.ccainMinus1Date.description = Timestamp of CCAIN-1 value
+channel-group-type.teleinfo.producerLSMGroupType.channel.eait.label = EAIT
+channel-group-type.teleinfo.producerLSMGroupType.channel.eait.description = Total active energy injected
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq1.label = ERQ1
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq1.description = Total reactive energy Q1
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq2.label = ERQ2
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq2.description = Total reactive energy Q2
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq3.label = ERQ3
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq3.description = Total reactive energy Q3
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq4.label = ERQ4
+channel-group-type.teleinfo.producerLSMGroupType.channel.erq4.description = Total reactive energy Q4
+channel-group-type.teleinfo.producerLSMGroupType.channel.sinsti.label = SINSTI
+channel-group-type.teleinfo.producerLSMGroupType.channel.sinsti.description = Instantaneous injected apparent power
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxin.label = SMAXIN
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxin.description = Maximum injected apparent power of the day
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxinDate.label = SMAXIN TIMESTAMP
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxinDate.description = Timestamp of SMAXIN value
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxinMinus1.label = SMAXIN-1
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxinMinus1.description = Maximum injected apparent power of the previous day
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxinMinus1Date.label = SMAXIN-1 TIMESTAMP
+channel-group-type.teleinfo.producerLSMGroupType.channel.smaxinMinus1Date.description = Timestamp of SMAXIN-1 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.label = Three-phase
+channel-group-type.teleinfo.threePhasedLSMGroupType.description = Three-phased channels for Linky telemeter in standard teleinformation mode
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.irms2.label = IRMS2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.irms2.description = RMS Current on phase 2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.irms3.label = IRMS3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.irms3.description = RMS Current on phase 3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.sinsts1.label = SINSTS1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.sinsts1.description = Instantaneous withdrawn apparent power on phase 1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.sinsts2.label = SINSTS2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.sinsts2.description = Instantaneous withdrawn apparent power on phase 2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.sinsts3.label = SINSTS3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.sinsts3.description = Instantaneous withdrawn apparent power on phase 3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1.label = SMAXSN1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1.description = Maximum withdrawn apparent power of the day on phase 1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1Date.label = SMAXSN1 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1Date.description = Timestamp of SMAXSN1 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1Minus1.label = SMAXSN1-1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1Minus1.description = Maximum withdrawn apparent power of the previous day on phase 1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1Minus1Date.label = SMAXSN1-1 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn1Minus1Date.description = Timestamp of SMAXSN1-1 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2.label = SMAXSN2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2.description = Maximum withdrawn apparent power of the day on phase 2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2Date.label = SMAXSN2 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2Date.description = Timestamp of SMAXSN2 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2Minus1.label = SMAXSN2-1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2Minus1.description = Maximum withdrawn apparent power of the previous day on phase 2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2Minus1Date.label = SMAXSN2-1 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn2Minus1Date.description = Timestamp of SMAXSN2-1 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3.label = SMAXSN3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3.description = Maximum withdrawn apparent power of the day on phase 3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3Date.label = SMAXSN3 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3Date.description = Timestamp of SMAXSN3 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3Minus1.label = SMAXSN3-1
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3Minus1.description = Maximum withdrawn apparent power of the previous day on phase 3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3Minus1Date.label = SMAXSN3-1 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.smaxsn3Minus1Date.description = Timestamp of SMAXSN3-1 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy2.label = UMOY2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy2.description = Mean Voltage on phase 2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy2Date.label = UMOY2 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy2Date.description = Timestamp of UMOY2 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy3.label = UMOY3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy3.description = Mean Voltage on phase 3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy3Date.label = UMOY3 TIMESTAMP
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.umoy3Date.description = Timestamp of UMOY3 value
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.urms2.label = URMS2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.urms2.description = RMS Voltage on phase 2
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.urms3.label = URMS3
+channel-group-type.teleinfo.threePhasedLSMGroupType.channel.urms3.description = RMS Voltage on phase 3
# channel types
@@ -70,10 +283,14 @@ channel-type.teleinfo.couleurDemainType.description = Following day color
channel-type.teleinfo.couleurDemainType.state.option.Bleu = Bleu
channel-type.teleinfo.couleurDemainType.state.option.Blanc = Blanc
channel-type.teleinfo.couleurDemainType.state.option.Rouge = Rouge
+channel-type.teleinfo.currentType.label = Teleinfo Current Type
+channel-type.teleinfo.dateTimeType.label = Teleinfo DateTime Type
+channel-type.teleinfo.dateTimeType.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
channel-type.teleinfo.ejphnType.label = EJPHN
channel-type.teleinfo.ejphnType.description = Total consumed energy at low rate pricing
channel-type.teleinfo.ejphpmType.label = EJPHPM
channel-type.teleinfo.ejphpmType.description = Total consumed energy at high rate pricing
+channel-type.teleinfo.energyType.label = Teleinfo Energy Type
channel-type.teleinfo.frameTypeType.label = frameType
channel-type.teleinfo.frameTypeType.description = frameType
channel-type.teleinfo.frameTypeType.state.option.Long = Long
@@ -113,12 +330,12 @@ channel-type.teleinfo.lastUpdateType.label = Last Update
channel-type.teleinfo.lastUpdateType.description = Timestamp of last received Teleinfo frame
channel-type.teleinfo.pappType.label = PAPP
channel-type.teleinfo.pappType.description = Instantaneous apparent power
-channel-type.teleinfo.pappType.label = PAPP
-channel-type.teleinfo.pappType.description = Instantaneous apparent power
channel-type.teleinfo.pejpType.label = PEJP
channel-type.teleinfo.pejpType.description = Prior notice to EJP start
channel-type.teleinfo.pmaxType.label = PMAX
channel-type.teleinfo.pmaxType.description = Maximum consumed electric power on all phases
+channel-type.teleinfo.potentialType.label = Teleinfo Potential Type
+channel-type.teleinfo.powerType.label = Teleinfo Power Type
channel-type.teleinfo.ppotType.label = PPOT
channel-type.teleinfo.ppotType.description = Electrical potential presence
channel-type.teleinfo.programmeCircuit1Type.label = ProgrammeCircuit1
@@ -149,3 +366,5 @@ channel-type.teleinfo.ptecType.state.option.HCJR = HCJR
channel-type.teleinfo.ptecType.state.option.HPJB = HPJB
channel-type.teleinfo.ptecType.state.option.HPJW = HPJW
channel-type.teleinfo.ptecType.state.option.HPJR = HPJR
+channel-type.teleinfo.stringType.label = Teleinfo String Type
+channel-type.teleinfo.switchType.label = Teleinfo Switch Type
diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java
index 254dc508e3fed..c755970004688 100644
--- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java
+++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java
@@ -109,6 +109,7 @@ public enum EventKeys {
// thing configurations
public static final String CONFIG_ALLOWWAKEUP = "allowWakeup";
+ public static final String CONFIG_ALLOWWAKEUPFORCOMMANDS = "allowWakeupForCommands";
public static final String CONFIG_ENABLEEVENTS = "enableEvents";
public static final String CONFIG_REFRESHTOKEN = "refreshToken";
public static final String CONFIG_USERNAME = "username";
diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java
index f7ac9fbc130d1..8f870c42e11b9 100644
--- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java
+++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java
@@ -71,7 +71,7 @@
*/
public class TeslaAccountHandler extends BaseBridgeHandler {
- public static final int API_MAXIMUM_ERRORS_IN_INTERVAL = 2;
+ public static final int API_MAXIMUM_ERRORS_IN_INTERVAL = 3;
public static final int API_ERROR_INTERVAL_SECONDS = 15;
private static final int CONNECT_RETRY_INTERVAL = 15000;
private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
@@ -219,7 +219,7 @@ protected Vehicle[] queryVehicles() {
Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class);
for (Vehicle vehicle : vehicleArray) {
- String responseString = invokeAndParse(vehicle.id, VEHICLE_CONFIG, null, dataRequestTarget);
+ String responseString = invokeAndParse(vehicle.id, VEHICLE_CONFIG, null, dataRequestTarget, 0);
if (responseString == null || responseString.isBlank()) {
continue;
}
@@ -308,7 +308,8 @@ private ThingStatusInfo authenticate() {
return new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
}
- protected String invokeAndParse(String vehicleId, String command, String payLoad, WebTarget target) {
+ protected String invokeAndParse(String vehicleId, String command, String payLoad, WebTarget target,
+ int noOfretries) {
logger.debug("Invoking: {}", command);
if (vehicleId != null) {
@@ -339,6 +340,16 @@ protected String invokeAndParse(String vehicleId, String command, String payLoad
logger.debug("An error occurred while communicating with the vehicle during request {}: {}: {}",
command, (response != null) ? response.getStatus() : "",
(response != null) ? response.getStatusInfo().getReasonPhrase() : "No Response");
+ if (response.getStatus() == 408 && noOfretries > 0) {
+ try {
+ // we give the vehicle a moment to wake up and try the request again
+ Thread.sleep(TimeUnit.SECONDS.toMillis(API_ERROR_INTERVAL_SECONDS));
+ logger.debug("Retrying to send the command {}.", command);
+ return invokeAndParse(vehicleId, command, payLoad, target, noOfretries - 1);
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
return null;
}
@@ -445,16 +456,21 @@ private String getBasicAuthentication() {
protected class Request implements Runnable {
+ private static final int NO_OF_RETRIES = 3;
+
private TeslaVehicleHandler handler;
private String request;
private String payLoad;
private WebTarget target;
+ private boolean allowWakeUpForCommands;
- public Request(TeslaVehicleHandler handler, String request, String payLoad, WebTarget target) {
+ public Request(TeslaVehicleHandler handler, String request, String payLoad, WebTarget target,
+ boolean allowWakeUpForCommands) {
this.handler = handler;
this.request = request;
this.payLoad = payLoad;
this.target = target;
+ this.allowWakeUpForCommands = allowWakeUpForCommands;
}
@Override
@@ -463,7 +479,8 @@ public void run() {
String result = "";
if (getThing().getStatus() == ThingStatus.ONLINE) {
- result = invokeAndParse(handler.getVehicleId(), request, payLoad, target);
+ result = invokeAndParse(handler.getVehicleId(), request, payLoad, target,
+ allowWakeUpForCommands ? NO_OF_RETRIES : 0);
if (result != null && !"".equals(result)) {
handler.parseAndUpdate(request, payLoad, result);
}
@@ -474,9 +491,9 @@ public void run() {
}
}
- public Request newRequest(TeslaVehicleHandler teslaVehicleHandler, String command, String payLoad,
- WebTarget target) {
- return new Request(teslaVehicleHandler, command, payLoad, target);
+ public Request newRequest(TeslaVehicleHandler teslaVehicleHandler, String command, String payLoad, WebTarget target,
+ boolean allowWakeUpForCommands) {
+ return new Request(teslaVehicleHandler, command, payLoad, target, allowWakeUpForCommands);
}
@Override
diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java
index 83deb99b422f5..e7c89ca0cfacb 100644
--- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java
+++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java
@@ -112,6 +112,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
protected ClimateState climateState;
protected boolean allowWakeUp;
+ protected boolean allowWakeUpForCommands;
protected boolean enableEvents = false;
protected long lastTimeStamp;
protected long apiIntervalTimestamp;
@@ -151,6 +152,7 @@ public void initialize() {
logger.trace("Initializing the Tesla handler for {}", getThing().getUID());
updateStatus(ThingStatus.UNKNOWN);
allowWakeUp = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ALLOWWAKEUP);
+ allowWakeUpForCommands = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ALLOWWAKEUPFORCOMMANDS);
// the streaming API seems to be broken - let's keep the code, if it comes back one day
// enableEvents = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ENABLEEVENTS);
@@ -251,6 +253,11 @@ public void handleCommand(ChannelUID channelUID, Command command) {
requestAllData();
} else {
if (selector != null) {
+ if (!isAwake() && allowWakeUpForCommands) {
+ logger.debug("Waking vehicle to send command.");
+ wakeUp();
+ setActive();
+ }
try {
switch (selector) {
case CHARGE_LIMIT_SOC: {
@@ -449,8 +456,8 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}
public void sendCommand(String command, String payLoad, WebTarget target) {
- if (command.equals(COMMAND_WAKE_UP) || isAwake()) {
- Request request = account.newRequest(this, command, payLoad, target);
+ if (command.equals(COMMAND_WAKE_UP) || isAwake() || allowWakeUpForCommands) {
+ Request request = account.newRequest(this, command, payLoad, target, allowWakeUpForCommands);
if (stateThrottler != null) {
stateThrottler.submit(COMMAND_THROTTLE, request);
}
@@ -462,8 +469,8 @@ public void sendCommand(String command) {
}
public void sendCommand(String command, String payLoad) {
- if (command.equals(COMMAND_WAKE_UP) || isAwake()) {
- Request request = account.newRequest(this, command, payLoad, account.commandTarget);
+ if (command.equals(COMMAND_WAKE_UP) || isAwake() || allowWakeUpForCommands) {
+ Request request = account.newRequest(this, command, payLoad, account.commandTarget, allowWakeUpForCommands);
if (stateThrottler != null) {
stateThrottler.submit(COMMAND_THROTTLE, request);
}
@@ -471,8 +478,8 @@ public void sendCommand(String command, String payLoad) {
}
public void sendCommand(String command, WebTarget target) {
- if (command.equals(COMMAND_WAKE_UP) || isAwake()) {
- Request request = account.newRequest(this, command, "{}", target);
+ if (command.equals(COMMAND_WAKE_UP) || isAwake() || allowWakeUpForCommands) {
+ Request request = account.newRequest(this, command, "{}", target, allowWakeUpForCommands);
if (stateThrottler != null) {
stateThrottler.submit(COMMAND_THROTTLE, request);
}
@@ -480,8 +487,8 @@ public void sendCommand(String command, WebTarget target) {
}
public void requestData(String command, String payLoad) {
- if (command.equals(COMMAND_WAKE_UP) || isAwake()) {
- Request request = account.newRequest(this, command, payLoad, account.dataRequestTarget);
+ if (command.equals(COMMAND_WAKE_UP) || isAwake() || allowWakeUpForCommands) {
+ Request request = account.newRequest(this, command, payLoad, account.dataRequestTarget, false);
if (stateThrottler != null) {
stateThrottler.submit(DATA_THROTTLE, request);
}
@@ -731,7 +738,8 @@ protected Vehicle queryVehicle() {
Response response = account.vehiclesTarget.request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", authHeader).get();
- logger.debug("Querying the vehicle : Response : {}:{}", response.getStatus(), response.getStatusInfo());
+ logger.debug("Querying the vehicle, response : {}, {}", response.getStatus(),
+ response.getStatusInfo().getReasonPhrase());
if (!checkResponse(response, true)) {
logger.error("An error occurred while querying the vehicle");
@@ -965,23 +973,27 @@ protected BigDecimal roundBigDecimal(BigDecimal value) {
}
protected Runnable slowStateRunnable = () -> {
- queryVehicleAndUpdate();
+ try {
+ queryVehicleAndUpdate();
- boolean allowQuery = allowQuery();
+ boolean allowQuery = allowQuery();
- if (allowQuery) {
- requestData(CHARGE_STATE);
- requestData(CLIMATE_STATE);
- requestData(GUI_STATE);
- queryVehicle(MOBILE_ENABLED_STATE);
- } else {
- if (allowWakeUp) {
- wakeUp();
+ if (allowQuery) {
+ requestData(CHARGE_STATE);
+ requestData(CLIMATE_STATE);
+ requestData(GUI_STATE);
+ queryVehicle(MOBILE_ENABLED_STATE);
} else {
- if (isAwake()) {
- logger.debug("Vehicle is neither charging nor moving, skipping updates to allow it to sleep");
+ if (allowWakeUp) {
+ wakeUp();
+ } else {
+ if (isAwake()) {
+ logger.debug("Vehicle is neither charging nor moving, skipping updates to allow it to sleep");
+ }
}
}
+ } catch (Exception e) {
+ logger.warn("Exception occurred in slowStateRunnable", e);
}
};
diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/ScheduledChannelThrottler.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/ScheduledChannelThrottler.java
index ac56ecb0aac0a..96201080dd629 100644
--- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/ScheduledChannelThrottler.java
+++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/ScheduledChannelThrottler.java
@@ -21,7 +21,7 @@
/**
* The {@link ScheduledChannelThrottler} implements a throttler that maintains a
- * single execution rates, and does not maintains order of calls (thus have to
+ * single execution rates, and does not maintain order of calls (thus has to
* start from back rather than try to insert things in middle)
*
* @author Karel Goderis - Initial contribution
diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties
index 44bf04cc92714..aa6777427d0e4 100644
--- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties
+++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties
@@ -26,24 +26,32 @@ thing-type.config.tesla.account.username.label = Username
thing-type.config.tesla.account.username.description = Username for the Tesla Remote Service, e.g email address.
thing-type.config.tesla.model3.allowWakeup.label = Allow Wake-Up
thing-type.config.tesla.model3.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+thing-type.config.tesla.model3.allowWakeupForCommands.label = Allow Wake-Up For Commands
+thing-type.config.tesla.model3.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long.
thing-type.config.tesla.model3.valetpin.label = Valet PIN
thing-type.config.tesla.model3.valetpin.description = PIN to use when enabling Valet Mode
thing-type.config.tesla.model3.vin.label = Vehicle Identification Number
thing-type.config.tesla.model3.vin.description = VIN of the vehicle
thing-type.config.tesla.models.allowWakeup.label = Allow Wake-Up
thing-type.config.tesla.models.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+thing-type.config.tesla.models.allowWakeupForCommands.label = Allow Wake-Up For Commands
+thing-type.config.tesla.models.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long.
thing-type.config.tesla.models.valetpin.label = Valet PIN
thing-type.config.tesla.models.valetpin.description = PIN to use when enabling Valet Mode
thing-type.config.tesla.models.vin.label = Vehicle Identification Number
thing-type.config.tesla.models.vin.description = VIN of the vehicle
thing-type.config.tesla.modelx.allowWakeup.label = Allow Wake-Up
thing-type.config.tesla.modelx.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+thing-type.config.tesla.modelx.allowWakeupForCommands.label = Allow Wake-Up For Commands
+thing-type.config.tesla.modelx.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long.
thing-type.config.tesla.modelx.valetpin.label = Valet PIN
thing-type.config.tesla.modelx.valetpin.description = PIN to use when enabling Valet Mode
thing-type.config.tesla.modelx.vin.label = Vehicle Identification Number
thing-type.config.tesla.modelx.vin.description = VIN of the vehicle
thing-type.config.tesla.modely.allowWakeup.label = Allow Wake-Up
thing-type.config.tesla.modely.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+thing-type.config.tesla.modely.allowWakeupForCommands.label = Allow Wake-Up For Commands
+thing-type.config.tesla.modely.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long.
thing-type.config.tesla.modely.valetpin.label = Valet PIN
thing-type.config.tesla.modely.valetpin.description = PIN to use when enabling Valet Mode
thing-type.config.tesla.modely.vin.label = Vehicle Identification Number
diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/model3.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/model3.xml
index 0566a8eee1dbb..55d7109df20b8 100644
--- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/model3.xml
+++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/model3.xml
@@ -134,6 +134,12 @@
Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+
+ false
+
+ Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in
+ this case and you could cause the vehicle to stay awake very long.
+
diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/models.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/models.xml
index 622a27b080e42..5fda08586b2f1 100644
--- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/models.xml
+++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/models.xml
@@ -141,6 +141,12 @@
Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+
+ false
+
+ Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in
+ this case and you could cause the vehicle to stay awake very long.
+
diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modelx.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modelx.xml
index 4087941fa3df5..b1753a842fc94 100644
--- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modelx.xml
+++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modelx.xml
@@ -141,6 +141,12 @@
Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+
+ false
+
+ Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in
+ this case and you could cause the vehicle to stay awake very long.
+
diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modely.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modely.xml
index 7724139c35146..95e60569f5ef9 100644
--- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modely.xml
+++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/modely.xml
@@ -136,6 +136,12 @@
Allows waking up the vehicle. Caution: This can result in huge vampire drain!
+
+ false
+
+ Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in
+ this case and you could cause the vehicle to stay awake very long.
+
diff --git a/bundles/org.openhab.binding.tibber/src/main/java/org/openhab/binding/tibber/internal/handler/TibberHandler.java b/bundles/org.openhab.binding.tibber/src/main/java/org/openhab/binding/tibber/internal/handler/TibberHandler.java
index 1d6ad8bfb4dd8..ac3461fa71444 100644
--- a/bundles/org.openhab.binding.tibber/src/main/java/org/openhab/binding/tibber/internal/handler/TibberHandler.java
+++ b/bundles/org.openhab.binding.tibber/src/main/java/org/openhab/binding/tibber/internal/handler/TibberHandler.java
@@ -20,7 +20,6 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;
-import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -37,7 +36,6 @@
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.tibber.internal.config.TibberConfiguration;
-import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
@@ -69,9 +67,8 @@ public class TibberHandler extends BaseThingHandler {
private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(20);
private final Logger logger = LoggerFactory.getLogger(TibberHandler.class);
private final Properties httpHeader = new Properties();
- private final SslContextFactory sslContextFactory = new SslContextFactory.Client(true);
- private final Executor websocketExecutor = ThreadPoolManager.getPool("tibber.websocket");
private TibberConfiguration tibberConfig = new TibberConfiguration();
+ private @Nullable SslContextFactory sslContextFactory;
private @Nullable TibberWebSocketListener socket;
private @Nullable Session session;
private @Nullable WebSocketClient client;
@@ -125,10 +122,10 @@ public void getTibberParameters() {
.getAsJsonObject("features").get("realTimeConsumptionEnabled").toString();
if ("true".equals(rtEnabled)) {
- logger.info("Pulse associated with HomeId: Live stream will be started");
+ logger.debug("Pulse associated with HomeId: Live stream will be started");
open();
} else {
- logger.info("No Pulse associated with HomeId: No live stream will be started");
+ logger.debug("No Pulse associated with HomeId: No live stream will be started");
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
@@ -163,7 +160,8 @@ public void getURLInput(String url) throws IOException {
updateState(CURRENT_TOTAL, new DecimalType(myObject.get("total").toString()));
String timestamp = myObject.get("startsAt").toString().substring(1, 20);
updateState(CURRENT_STARTSAT, new DateTimeType(timestamp));
- updateState(CURRENT_LEVEL, new StringType(myObject.get("level").toString()));
+ updateState(CURRENT_LEVEL,
+ new StringType(myObject.get("level").toString().replaceAll("^\"|\"$", "")));
} catch (JsonSyntaxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
@@ -246,7 +244,7 @@ public void startRefresh(int refresh) {
public void updateRequest() throws IOException {
getURLInput(BASE_URL);
if ("true".equals(rtEnabled) && !isConnected()) {
- logger.info("Attempting to reopen Websocket connection");
+ logger.debug("Attempting to reopen Websocket connection");
open();
}
}
@@ -288,12 +286,12 @@ public void dispose() {
WebSocketClient client = this.client;
if (client != null) {
try {
- logger.warn("Stopping and Terminating Websocket connection");
+ logger.debug("DISPOSE - Stopping and Terminating Websocket connection");
client.stop();
- client.destroy();
} catch (Exception e) {
logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
}
+ client.destroy();
this.client = null;
}
}
@@ -302,23 +300,26 @@ public void dispose() {
public void open() {
WebSocketClient client = this.client;
- if (client == null || !client.isRunning()) {
+ if (client == null || !client.isRunning() || !isConnected()) {
if (client != null) {
try {
client.stop();
} catch (Exception e) {
- logger.warn("Failed to stop websocket client: {}", e.getMessage());
+ logger.warn("OPEN FRAME - Failed to stop websocket client: {}", e.getMessage());
}
+ client.destroy();
}
+ sslContextFactory = new SslContextFactory.Client(true);
sslContextFactory.setTrustAll(true);
sslContextFactory.setEndpointIdentificationAlgorithm(null);
- client = new WebSocketClient(sslContextFactory, websocketExecutor);
- client.setMaxIdleTimeout(600 * 1000);
+ client = new WebSocketClient(sslContextFactory);
+ client.setMaxIdleTimeout(30 * 1000);
this.client = client;
TibberWebSocketListener socket = this.socket;
if (socket == null) {
+ logger.debug("New socket being created");
socket = new TibberWebSocketListener();
this.socket = socket;
}
@@ -328,14 +329,25 @@ public void open() {
newRequest.setSubProtocols("graphql-subscriptions");
try {
- logger.info("Starting Websocket connection");
+ logger.debug("Starting Websocket connection");
client.start();
} catch (Exception e) {
logger.warn("Websocket Start Exception: {}", e.getMessage());
}
try {
- logger.info("Connecting Websocket connection");
+ logger.debug("Connecting Websocket connection");
sessionFuture = client.connect(socket, new URI(SUBSCRIPTION_URL), newRequest);
+ try {
+ Thread.sleep(10 * 1000);
+ } catch (InterruptedException e) {
+ }
+ Session session = this.session;
+ if (!session.isOpen()) {
+ close();
+ logger.warn("Unable to establish websocket session");
+ } else {
+ logger.debug("Websocket session established");
+ }
} catch (IOException e) {
logger.warn("Websocket Connect Exception: {}", e.getMessage());
} catch (URISyntaxException e) {
@@ -353,15 +365,19 @@ public void close() {
try {
TibberWebSocketListener socket = this.socket;
if (socket != null) {
- logger.info("Sending websocket disconnect message");
+ logger.debug("Sending websocket disconnect message");
socket.sendMessage(disconnect);
} else {
- logger.debug("Socket unable to send disconnect message: Socket is null");
+ logger.warn("Socket unable to send disconnect message: Socket is null");
}
} catch (IOException e) {
logger.warn("Websocket Close Exception: {}", e.getMessage());
}
- session.close();
+ try {
+ session.close();
+ } catch (Exception e) {
+ logger.warn("Unable to disconnect session");
+ }
this.session = null;
this.socket = null;
}
@@ -374,8 +390,9 @@ public void close() {
try {
client.stop();
} catch (Exception e) {
- logger.warn("Failed to stop websocket client: {}", e.getMessage());
+ logger.warn("CLOSE FRAME - Failed to stop websocket client: {}", e.getMessage());
}
+ client.destroy();
}
}
@@ -395,7 +412,7 @@ public void onConnect(Session wssession) {
String connection = "{\"type\":\"connection_init\", \"payload\":\"token=" + tibberConfig.getToken() + "\"}";
try {
if (socket != null) {
- logger.info("Sending websocket connect message");
+ logger.debug("Sending websocket connect message");
socket.sendMessage(connection);
} else {
logger.debug("Socket unable to send connect message: Socket is null");
@@ -407,11 +424,11 @@ public void onConnect(Session wssession) {
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
- logger.info("Closing a WebSocket due to {}", reason);
+ logger.debug("Closing a WebSocket due to {}", reason);
WebSocketClient client = TibberHandler.this.client;
if (client != null && client.isRunning()) {
try {
- logger.info("Stopping and Terminating Websocket connection");
+ logger.debug("ONCLOSE - Stopping and Terminating Websocket connection");
client.stop();
} catch (Exception e) {
logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
@@ -423,13 +440,13 @@ public void onClose(int statusCode, String reason) {
public void onWebSocketError(Throwable e) {
String message = e.getMessage();
logger.debug("Error during websocket communication: {}", message);
- onClose(0, message != null ? message : "null");
+ close();
}
@OnWebSocketMessage
public void onMessage(String message) {
if (message.contains("connection_ack")) {
- logger.info("Connected to Server");
+ logger.debug("Connected to Server");
startSubscription();
} else if (message.contains("error") || message.contains("terminate")) {
logger.debug("Error/terminate received from server: {}", message);
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 17e2a60df1478..fce85e4432923 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
@@ -291,8 +291,8 @@ public void clearHomekitPairings() {
}
@Override
- public void onChanged(final List added, final List removed) {
- logger.trace("restarting HomeKit bridge on network interface changes.");
+ public synchronized void onChanged(final List added, final List removed) {
+ logger.trace("HomeKit bridge reacting on network interface changes.");
removed.forEach(i -> {
logger.trace("removed interface {}", i.getAddress().toString());
if (i.getAddress().equals(networkInterface)) {
diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java
index a7a9de2aadb6b..4367611823c02 100644
--- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java
+++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java
@@ -262,7 +262,7 @@ private static > double convertAndRound(double value, Unit
}
public static @Nullable Double stateAsTemperature(@Nullable State state) {
- if (state == null) {
+ if (state == null || state instanceof UnDefType) {
return null;
}
diff --git a/bundles/org.openhab.voice.voicerss/README.md b/bundles/org.openhab.voice.voicerss/README.md
index 7cca6bc40983f..5d8d908455d81 100644
--- a/bundles/org.openhab.voice.voicerss/README.md
+++ b/bundles/org.openhab.voice.voicerss/README.md
@@ -177,17 +177,17 @@ Synopsis of this tool:
```
Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache
-Arguments: --api-key { | @inputfile }
+Arguments: --api-key { | @inputfile } [ ]
key the VoiceRSS API Key, e.g. "123456789"
cache-dir is directory where the files will be stored, e.g. "voicerss-cache"
locale the language locale, has to be valid, e.g. "en-us", "de-de"
+ voice the voice, "default" for the default voice
text the text to create audio file for, e.g. "Hello World"
inputfile a name of a file, where all lines will be translatet to text, e.g. "@message.txt"
+ codec the audio codec, "MP3", "WAV", "OGG" or "AAC", "MP3" by default
+ format the audio format, "44khz_16bit_mono" by default
-Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US @messages.txt
+Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US default @messages.txt
```
-
-## Open Issues
-
-* do not log API-Key in plain text
+You will need to specify the classpath for your addon (jar) in the command line (java -cp ...).
diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java
index 8657c5805ffaf..787a0312f2dcd 100644
--- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java
+++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSAudioStream.java
@@ -14,6 +14,7 @@
import java.io.File;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.audio.AudioException;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioStream;
@@ -27,6 +28,7 @@
*
* @author Jochen Hiller - Initial contribution and API
*/
+@NonNullByDefault
class VoiceRSSAudioStream extends FileAudioStream {
public VoiceRSSAudioStream(File audioFile, AudioFormat format) throws AudioException {
diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java
index 2fdae5808475e..ca25f7342ecef 100644
--- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java
+++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java
@@ -20,6 +20,8 @@
import java.util.Map;
import java.util.Set;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.OpenHAB;
import org.openhab.core.audio.AudioException;
import org.openhab.core.audio.AudioFormat;
@@ -30,6 +32,7 @@
import org.openhab.core.voice.Voice;
import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl;
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.Modified;
import org.slf4j.Logger;
@@ -41,6 +44,7 @@
* @author Jochen Hiller - Initial contribution and API
* @author Laurent Garnier - add support for OGG and AAC audio formats
*/
+@NonNullByDefault
@Component(configurationPid = "org.openhab.voicerss", property = Constants.SERVICE_PID + "=org.openhab.voicerss")
@ConfigurableService(category = "voice", label = "VoiceRSS Text-to-Speech", description_uri = "voice:voicerss")
public class VoiceRSSTTSService implements TTSService {
@@ -66,27 +70,28 @@ public class VoiceRSSTTSService implements TTSService {
private final Logger logger = LoggerFactory.getLogger(VoiceRSSTTSService.class);
- private String apiKey;
+ private @Nullable String apiKey;
/**
* We need the cached implementation to allow for FixedLengthAudioStream.
*/
- private CachedVoiceRSSCloudImpl voiceRssImpl;
+ private @Nullable CachedVoiceRSSCloudImpl voiceRssImpl;
/**
* Set of supported voices
*/
- private Set voices;
+ private @Nullable Set voices;
/**
* Set of supported audio formats
*/
- private Set audioFormats;
+ private @Nullable Set audioFormats;
/**
* DS activate, with access to ConfigAdmin
*/
- protected void activate(Map config) {
+ @Activate
+ protected void activate(@Nullable Map config) {
try {
modified(config);
voiceRssImpl = initVoiceImplementation();
@@ -100,7 +105,7 @@ protected void activate(Map config) {
}
@Modified
- protected void modified(Map config) {
+ protected void modified(@Nullable Map config) {
if (config != null) {
apiKey = config.containsKey(CONFIG_API_KEY) ? config.get(CONFIG_API_KEY).toString() : null;
}
@@ -108,37 +113,41 @@ protected void modified(Map config) {
@Override
public Set getAvailableVoices() {
- return Collections.unmodifiableSet(voices);
+ Set localVoices = voices;
+ return localVoices == null ? Set.of() : Collections.unmodifiableSet(localVoices);
}
@Override
public Set getSupportedFormats() {
- return Collections.unmodifiableSet(audioFormats);
+ Set localFormats = audioFormats;
+ return localFormats == null ? Set.of() : Collections.unmodifiableSet(localFormats);
}
@Override
public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFormat) throws TTSException {
logger.debug("Synthesize '{}' for voice '{}' in format {}", text, voice.getUID(), requestedFormat);
+ CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl;
+ if (voiceRssCloud == null) {
+ throw new TTSException("The service is not correctly initialized");
+ }
// Validate known api key
- if (apiKey == null) {
+ String key = apiKey;
+ if (key == null) {
throw new TTSException("Missing API key, configure it first before using");
}
- // Validate arguments
- if (text == null) {
- throw new TTSException("The passed text is null");
- }
// trim text
String trimmedText = text.trim();
if (trimmedText.isEmpty()) {
throw new TTSException("The passed text is empty");
}
- if (!voices.contains(voice)) {
+ Set localVoices = voices;
+ if (localVoices == null || !localVoices.contains(voice)) {
throw new TTSException("The passed voice is unsupported");
}
// now create the input stream for given text, locale, voice, codec and format.
try {
- File cacheAudioFile = voiceRssImpl.getTextToSpeechAsFile(apiKey, trimmedText,
+ File cacheAudioFile = voiceRssCloud.getTextToSpeechAsFile(key, trimmedText,
voice.getLocale().toLanguageTag(), voice.getLabel(), getApiAudioCodec(requestedFormat),
getApiAudioFormat(requestedFormat));
return new VoiceRSSAudioStream(cacheAudioFile, requestedFormat);
@@ -153,11 +162,16 @@ public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFor
* Initializes voices.
*
* @return The voices of this instance
+ * @throws IllegalStateException if voiceRssImpl is null
*/
- private Set initVoices() {
+ private Set initVoices() throws IllegalStateException {
+ CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl;
+ if (voiceRssCloud == null) {
+ throw new IllegalStateException("The service is not correctly initialized");
+ }
Set voices = new HashSet<>();
- for (Locale locale : voiceRssImpl.getAvailableLocales()) {
- for (String voiceLabel : voiceRssImpl.getAvailableVoices(locale)) {
+ for (Locale locale : voiceRssCloud.getAvailableLocales()) {
+ for (String voiceLabel : voiceRssCloud.getAvailableVoices(locale)) {
voices.add(new VoiceRSSVoice(locale, voiceLabel));
}
}
@@ -168,9 +182,89 @@ private Set initVoices() {
* Initializes audioFormats
*
* @return The audio formats of this instance
+ * @throws IllegalStateException if voiceRssImpl is null
*/
- private Set initAudioFormats() {
- return voiceRssImpl.getAvailableAudioFormats();
+ private Set initAudioFormats() throws IllegalStateException {
+ CachedVoiceRSSCloudImpl voiceRssCloud = voiceRssImpl;
+ if (voiceRssCloud == null) {
+ throw new IllegalStateException("The service is not correctly initialized");
+ }
+ Set audioFormats = new HashSet<>();
+ for (String codec : voiceRssCloud.getAvailableAudioCodecs()) {
+ switch (codec) {
+ case "MP3":
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, 64000,
+ 44_100L));
+ break;
+ case "OGG":
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, null, 16,
+ null, 44_100L));
+ break;
+ case "AAC":
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, null, 16, null,
+ 44_100L));
+ break;
+ case "WAV":
+ // Consider only mono formats
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 64_000, 8_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 128_000, 8_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 88_200, 11_025L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 176_400, 11_025L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 96_000, 12_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 192_000, 12_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 128_000, 16_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 256_000, 16_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 176_400, 22_050L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 352_800, 22_050L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 192_000, 24_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 384_000, 24_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 256_000, 32_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 512_000, 32_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 352_800, 44_100L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 705_600, 44_100L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false,
+ 8, 384_000, 48_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false,
+ 16, 768_000, 48_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8,
+ 64_000, 8_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8,
+ 88_200, 11_025L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8,
+ 176_400, 22_050L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8,
+ 352_800, 44_100L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8,
+ 64_000, 8_000L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8,
+ 88_200, 11_025L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8,
+ 176_400, 22_050L));
+ audioFormats.add(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8,
+ 352_800, 44_100L));
+ break;
+ default:
+ logger.debug("Audio codec {} not yet supported", codec);
+ break;
+ }
+ }
+ return audioFormats;
}
/**
@@ -196,15 +290,18 @@ private String getApiAudioCodec(AudioFormat format) throws TTSException {
* @throws TTSException if {@code format} is not supported
*/
private String getApiAudioFormat(AudioFormat format) throws TTSException {
- final int bitDepth = format.getBitDepth() != null ? format.getBitDepth() : 16;
- final Long frequency = format.getFrequency() != null ? format.getFrequency() : 44_100L;
+ final Integer formatBitDepth = format.getBitDepth();
+ final int bitDepth = formatBitDepth != null ? formatBitDepth.intValue() : 16;
+ final Long formatFrequency = format.getFrequency();
+ final Long frequency = formatFrequency != null ? formatFrequency.longValue() : 44_100L;
final String apiFrequency = FREQUENCY_MAP.get(frequency);
if (apiFrequency == null || (bitDepth != 8 && bitDepth != 16)) {
throw new TTSException("Unsupported audio format: " + format);
}
- switch (format.getCodec() != null ? format.getCodec() : AudioFormat.CODEC_PCM_SIGNED) {
+ String codec = format.getCodec();
+ switch (codec != null ? codec : AudioFormat.CODEC_PCM_SIGNED) {
case AudioFormat.CODEC_PCM_ALAW:
return "alaw_" + apiFrequency + "_mono";
case AudioFormat.CODEC_PCM_ULAW:
@@ -221,7 +318,7 @@ private String getApiAudioFormat(AudioFormat format) throws TTSException {
}
private CachedVoiceRSSCloudImpl initVoiceImplementation() throws IllegalStateException {
- return new CachedVoiceRSSCloudImpl(getCacheFolderName());
+ return new CachedVoiceRSSCloudImpl(getCacheFolderName(), true);
}
private String getCacheFolderName() {
@@ -235,7 +332,7 @@ public String getId() {
}
@Override
- public String getLabel(Locale locale) {
+ public String getLabel(@Nullable Locale locale) {
return "VoiceRSS";
}
}
diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java
index 4b2c4b0af2859..6a752f70db4e4 100644
--- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java
+++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java
@@ -14,6 +14,7 @@
import java.util.Locale;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.voice.Voice;
import org.openhab.voice.voicerss.internal.cloudapi.VoiceRSSCloudImpl;
@@ -23,6 +24,7 @@
*
* @author Jochen Hiller - Initial contribution and API
*/
+@NonNullByDefault
public class VoiceRSSVoice implements Voice {
/**
diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java
index fd4977f2f4cfa..ba6f8fdb81b9d 100644
--- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java
+++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java
@@ -23,7 +23,8 @@
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
-import org.slf4j.Logger;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.LoggerFactory;
/**
@@ -33,6 +34,7 @@
*
* @author Jochen Hiller - Initial contribution
*/
+@NonNullByDefault
public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
/**
@@ -40,12 +42,11 @@ public class CachedVoiceRSSCloudImpl extends VoiceRSSCloudImpl {
*/
private static final int READ_BUFFER_SIZE = 4096;
- private final Logger logger = LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class);
-
private final File cacheFolder;
- public CachedVoiceRSSCloudImpl(String cacheFolderName) throws IllegalStateException {
- if (cacheFolderName == null) {
+ public CachedVoiceRSSCloudImpl(String cacheFolderName, boolean logging) throws IllegalStateException {
+ super(logging);
+ if (cacheFolderName.isBlank()) {
throw new IllegalStateException("Folder for cache must be defined");
}
// Lazy create the cache folder
@@ -89,7 +90,7 @@ public File getTextToSpeechAsFile(String apiKey, String text, String locale, Str
*
* Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3"
*/
- private String getUniqueFilenameForText(String text, String locale, String voice, String format) {
+ private @Nullable String getUniqueFilenameForText(String text, String locale, String voice, String format) {
try {
byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8);
MessageDigest md = MessageDigest.getInstance("MD5");
@@ -112,7 +113,10 @@ private String getUniqueFilenameForText(String text, String locale, String voice
return filename;
} catch (NoSuchAlgorithmException ex) {
// should not happen
- logger.error("Could not create MD5 hash for '{}'", text, ex);
+ if (logging) {
+ LoggerFactory.getLogger(CachedVoiceRSSCloudImpl.class).error("Could not create MD5 hash for '{}'", text,
+ ex);
+ }
return null;
}
}
diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java
index 27fb29290a627..84ec44ffcbcd2 100644
--- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java
+++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java
@@ -17,7 +17,7 @@
import java.util.Locale;
import java.util.Set;
-import org.openhab.core.audio.AudioFormat;
+import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Interface which represents the functionality needed from the VoiceRSS TTS
@@ -25,6 +25,7 @@
*
* @author Jochen Hiller - Initial contribution
*/
+@NonNullByDefault
public interface VoiceRSSCloudAPI {
/**
@@ -35,13 +36,12 @@ public interface VoiceRSSCloudAPI {
Set getAvailableLocales();
/**
- * Get all supported audio formats by the TTS service. This includes MP3,
- * WAV and more audio formats as used in APIs. About supported audio
- * formats, see {@link AudioFormat}
+ * Get all supported audio codecs by the TTS service. This includes MP3,
+ * WAV and more audio formats as used in APIs.
*
- * @return A set of all audio formats supported
+ * @return A set of all audio codecs supported
*/
- Set getAvailableAudioFormats();
+ Set getAvailableAudioCodecs();
/**
* Get all supported voices.
diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java
index 0c6f8c372fdaf..cb6bd5f2d5656 100644
--- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java
+++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java
@@ -27,7 +27,7 @@
import java.util.Map.Entry;
import java.util.Set;
-import org.openhab.core.audio.AudioFormat;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,6 +48,7 @@
* @author Laurent Garnier - add support for OGG and AAC audio formats
* @author Andreas Brenk - add support for WAV audio format
*/
+@NonNullByDefault
public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
public static final String DEFAULT_VOICE = "default";
@@ -55,36 +56,7 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
public static final String API_URL = "https://api.voicerss.org/?key=%s&hl=%s&c=%s&f=%s&src=%s";
public static final String API_URL_WITH_VOICE = API_URL + "&v=%s";
- private static final Set SUPPORTED_AUDIO_FORMATS = Set.of(
- new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, 16, null, 44_100L),
- new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, null, 16, null, 44_100L),
- new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, null, 16, null, 44_100L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, null, 8, 64_000, 8_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null, 16, 128_000, 8_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 88_200, 11_025L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 176_400, 11_025L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 96_000, 12_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 192_000, 12_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 128_000, 16_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 256_000, 16_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 176_400, 22_050L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 352_800, 22_050L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 192_000, 24_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 384_000, 24_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 256_000, 32_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 512_000, 32_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 352_800, 44_100L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 705_600, 44_100L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_UNSIGNED, false, 8, 384_000, 48_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 768_000, 48_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 64_000, 8_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 88_200, 11_025L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 176_400, 22_050L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ALAW, null, 8, 352_800, 44_100L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 64_000, 8_000L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 88_200, 11_025L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 176_400, 22_050L),
- new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_ULAW, null, 8, 352_800, 44_100L));
+ private static final Set SUPPORTED_AUDIO_CODECS = Set.of("MP3", "OGG", "AAC", "WAV", "CAF");
private static final Set SUPPORTED_LOCALES = new HashSet<>();
static {
@@ -192,11 +164,15 @@ public class VoiceRSSCloudImpl implements VoiceRSSCloudAPI {
SUPPORTED_VOICES.put("zh-tw", Set.of("Akemi", "Lin", "Lee"));
}
- private final Logger logger = LoggerFactory.getLogger(VoiceRSSCloudImpl.class);
+ protected boolean logging;
+
+ public VoiceRSSCloudImpl(boolean logging) {
+ this.logging = logging;
+ }
@Override
- public Set getAvailableAudioFormats() {
- return SUPPORTED_AUDIO_FORMATS;
+ public Set getAvailableAudioCodecs() {
+ return SUPPORTED_AUDIO_CODECS;
}
@Override
@@ -242,7 +218,9 @@ public Set getAvailableVoices(Locale locale) {
public InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioCodec,
String audioFormat) throws IOException {
String url = createURL(apiKey, text, locale, voice, audioCodec, audioFormat);
- logger.debug("Call {}", url.replace(apiKey, "***"));
+ if (logging) {
+ LoggerFactory.getLogger(VoiceRSSCloudImpl.class).debug("Call {}", url.replace(apiKey, "***"));
+ }
URLConnection connection = new URL(url).openConnection();
// we will check return codes. The service will ALWAYS return a HTTP
@@ -250,12 +228,18 @@ public InputStream getTextToSpeech(String apiKey, String text, String locale, St
// the error message in body
int status = ((HttpURLConnection) connection).getResponseCode();
if (HttpURLConnection.HTTP_OK != status) {
- logger.warn("Call {} returned HTTP {}", url.replace(apiKey, "***"), status);
+ if (logging) {
+ LoggerFactory.getLogger(VoiceRSSCloudImpl.class).warn("Call {} returned HTTP {}",
+ url.replace(apiKey, "***"), status);
+ }
throw new IOException("Could not read from service: HTTP code " + status);
}
- if (logger.isTraceEnabled()) {
- for (Entry> header : connection.getHeaderFields().entrySet()) {
- logger.trace("Response.header: {}={}", header.getKey(), header.getValue());
+ if (logging) {
+ Logger logger = LoggerFactory.getLogger(VoiceRSSCloudImpl.class);
+ if (logger.isTraceEnabled()) {
+ for (Entry> header : connection.getHeaderFields().entrySet()) {
+ logger.trace("Response.header: {}={}", header.getKey(), header.getValue());
+ }
}
}
String contentType = connection.getHeaderField("Content-Type");
@@ -268,7 +252,9 @@ public InputStream getTextToSpeech(String apiKey, String text, String locale, St
try {
is.close();
} catch (IOException ex) {
- logger.debug("Failed to close inputstream", ex);
+ if (logging) {
+ LoggerFactory.getLogger(VoiceRSSCloudImpl.class).debug("Failed to close inputstream", ex);
+ }
}
throw new IOException(
"Could not read audio content, service returned an error: " + new String(bytes, "UTF-8"));
diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java
index f5d1c1ae61623..9c9214ebbd342 100644
--- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java
+++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/tool/CreateTTSCache.java
@@ -16,7 +16,9 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
+import java.io.PrintStream;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.voice.voicerss.internal.cloudapi.CachedVoiceRSSCloudImpl;
/**
@@ -24,12 +26,14 @@
*
* @author Jochen Hiller - Initial contribution
*/
+@NonNullByDefault
public class CreateTTSCache {
public static final int RC_OK = 0;
public static final int RC_USAGE = 1;
public static final int RC_INPUT_FILE_NOT_FOUND = 2;
public static final int RC_API_KEY_MISSING = 3;
+ public static final int RC_INVALID_CODEC = 4;
public static void main(String[] args) throws IOException {
CreateTTSCache tool = new CreateTTSCache();
@@ -38,7 +42,7 @@ public static void main(String[] args) throws IOException {
}
public int doMain(String[] args) throws IOException {
- if ((args == null) || (args.length != 5)) {
+ if (args.length < 6) {
usage();
return RC_USAGE;
}
@@ -50,64 +54,100 @@ public int doMain(String[] args) throws IOException {
String cacheDir = args[2];
String locale = args[3];
String voice = args[4];
+ String codec = "MP3";
+ if (args.length >= 7) {
+ switch (args[6]) {
+ case "MP3":
+ case "WAV":
+ case "OGG":
+ case "AAC":
+ codec = args[6];
+ break;
+ default:
+ usage();
+ return RC_INVALID_CODEC;
+ }
+ }
+ String format = args.length >= 8 ? args[7] : "44khz_16bit_mono";
if (args[5].startsWith("@")) {
String inputFileName = args[5].substring(1);
File inputFile = new File(inputFileName);
if (!inputFile.exists()) {
usage();
- System.err.println("File " + inputFileName + " not found");
+ PrintStream printStream = System.err;
+ if (printStream != null) {
+ printStream.println("File " + inputFileName + " not found");
+ }
return RC_INPUT_FILE_NOT_FOUND;
}
- generateCacheForFile(apiKey, cacheDir, locale, voice, inputFileName);
+ generateCacheForFile(apiKey, cacheDir, locale, voice, codec, format, inputFileName);
} else {
String text = args[5];
- generateCacheForMessage(apiKey, cacheDir, locale, voice, text);
+ generateCacheForMessage(apiKey, cacheDir, locale, voice, codec, format, text);
}
return RC_OK;
}
private void usage() {
- System.out.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache ");
- System.out.println("Arguments: --api-key { | @inputfile }");
- System.out.println(" key the VoiceRSS API Key, e.g. \"123456789\"");
- System.out.println(" cache-dir is directory where the files will be stored, e.g. \"voicerss-cache\"");
- System.out.println(" locale the language locale, has to be valid, e.g. \"en-us\", \"de-de\"");
- System.out.println(" voice the voice, \"default\" for the default voice");
- System.out.println(" text the text to create audio file for, e.g. \"Hello World\"");
- System.out.println(
+ PrintStream printStream = System.out;
+ if (printStream == null) {
+ return;
+ }
+ printStream.println("Usage: java org.openhab.voice.voicerss.tool.CreateTTSCache ");
+ printStream.println(
+ "Arguments: --api-key { | @inputfile } [ ]");
+ printStream.println(" key the VoiceRSS API Key, e.g. \"123456789\"");
+ printStream.println(" cache-dir is directory where the files will be stored, e.g. \"voicerss-cache\"");
+ printStream.println(" locale the language locale, has to be valid, e.g. \"en-us\", \"de-de\"");
+ printStream.println(" voice the voice, \"default\" for the default voice");
+ printStream.println(" text the text to create audio file for, e.g. \"Hello World\"");
+ printStream.println(
" inputfile a name of a file, where all lines will be translatet to text, e.g. \"@message.txt\"");
- System.out.println();
- System.out.println(
- "Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US @messages.txt");
- System.out.println();
+ printStream.println(" codec the audio codec, \"MP3\", \"WAV\", \"OGG\" or \"AAC\", \"MP3\" by default");
+ printStream.println(" format the audio format, \"44khz_16bit_mono\" by default");
+ printStream.println();
+ printStream.println(
+ "Sample: java org.openhab.voice.voicerss.tool.CreateTTSCache --api-key 1234567890 cache en-US default @messages.txt");
+ printStream.println();
}
- private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String inputFileName)
- throws IOException {
+ private void generateCacheForFile(String apiKey, String cacheDir, String locale, String voice, String codec,
+ String format, String inputFileName) throws IOException {
File inputFile = new File(inputFileName);
try (BufferedReader br = new BufferedReader(new FileReader(inputFile))) {
String line;
while ((line = br.readLine()) != null) {
// process the line.
- generateCacheForMessage(apiKey, cacheDir, locale, voice, line);
+ generateCacheForMessage(apiKey, cacheDir, locale, voice, codec, format, line);
}
}
}
- private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String msg)
- throws IOException {
- if (msg == null) {
- System.err.println("Ignore msg=null");
- return;
- }
+ private void generateCacheForMessage(String apiKey, String cacheDir, String locale, String voice, String codec,
+ String format, String msg) throws IOException {
+ PrintStream printStream;
String trimmedMsg = msg.trim();
if (trimmedMsg.length() == 0) {
- System.err.println("Ignore msg=''");
+ printStream = System.err;
+ if (printStream != null) {
+ printStream.println("Ignore msg=''");
+ }
return;
}
- CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir);
- File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, "MP3", null);
- System.out.println(
- "Created cached audio for locale='" + locale + "', msg='" + trimmedMsg + "' to file=" + cachedFile);
+ try {
+ CachedVoiceRSSCloudImpl impl = new CachedVoiceRSSCloudImpl(cacheDir, false);
+ File cachedFile = impl.getTextToSpeechAsFile(apiKey, trimmedMsg, locale, voice, codec, format);
+ printStream = System.out;
+ if (printStream != null) {
+ printStream.println("Created cached audio for locale='" + locale + "', voice='" + voice + "', msg='"
+ + trimmedMsg + "' to file=" + cachedFile);
+ }
+ } catch (IllegalStateException | IOException ex) {
+ printStream = System.err;
+ if (printStream != null) {
+ printStream.println("Failed to create cached audio for locale='" + locale + "', voice='" + voice
+ + "',msg='" + trimmedMsg + "'");
+ }
+ }
}
}