From 93d17d18ecc449a82d2960a46cf6e0e5c8cf3d09 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 14 Jun 2023 20:02:33 +0100 Subject: [PATCH 001/146] [growatt] initial contribution Signed-off-by: Andrew Fiddian-Green --- CODEOWNERS | 1 + bundles/org.openhab.binding.growatt/NOTICE | 13 +++ bundles/org.openhab.binding.growatt/README.md | 94 ++++++++++++++++ bundles/org.openhab.binding.growatt/pom.xml | 17 +++ .../src/main/feature/feature.xml | 9 ++ .../internal/growattBindingConstants.java | 34 ++++++ .../internal/growattConfiguration.java | 31 ++++++ .../growatt/internal/growattHandler.java | 104 ++++++++++++++++++ .../internal/growattHandlerFactory.java | 55 +++++++++ .../src/main/resources/OH-INF/addon/addon.xml | 10 ++ .../resources/OH-INF/i18n/growatt.properties | 3 + .../resources/OH-INF/thing/thing-types.xml | 48 ++++++++ bundles/pom.xml | 3 +- 13 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.binding.growatt/NOTICE create mode 100644 bundles/org.openhab.binding.growatt/README.md create mode 100644 bundles/org.openhab.binding.growatt/pom.xml create mode 100644 bundles/org.openhab.binding.growatt/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattConfiguration.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandlerFactory.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties create mode 100644 bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index a2ee3fcf0100c..9b6080bfe4609 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -127,6 +127,7 @@ /bundles/org.openhab.binding.gree/ @markus7017 /bundles/org.openhab.binding.groheondus/ @FlorianSW /bundles/org.openhab.binding.groupepsa/ @arjanmels +/bundles/org.openhab.binding.growatt/ @andrewfg /bundles/org.openhab.binding.guntamatic/ @MikeTheTux /bundles/org.openhab.binding.haassohnpelletstove/ @chingon007 /bundles/org.openhab.binding.harmonyhub/ @digitaldan diff --git a/bundles/org.openhab.binding.growatt/NOTICE b/bundles/org.openhab.binding.growatt/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md new file mode 100644 index 0000000000000..22bcbfb3ef937 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/README.md @@ -0,0 +1,94 @@ +# growatt Binding + +_Give some details about what this binding is meant for - a protocol, system, specific device._ + +_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._ +_You can place such resources into a `doc` folder next to this README.md._ + +_Put each sentence in a separate line to improve readability of diffs._ + +## Supported Things + +_Please describe the different supported things / devices including their ThingTypeUID within this section._ +_Which different types are supported, which models were tested etc.?_ +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +- `bridge`: Short description of the Bridge, if any +- `sample`: Short description of the Thing with the ThingTypeUID `sample` + +## Discovery + +_Describe the available auto-discovery features here._ +_Mention for what it works and what needs to be kept in mind when using it._ + +## Binding Configuration + +_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ +_In this section, you should link to this file and provide some information about the options._ +_The file could e.g. look like:_ + +``` +# Configuration for the growatt Binding +# +# Default secret key for the pairing of the growatt Thing. +# It has to be between 10-40 (alphanumeric) characters. +# This may be changed by the user for security reasons. +secret=openHABSecret +``` + +_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._ + +_If your binding does not offer any generic configurations, you can remove this section completely._ + +## Thing Configuration + +_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ +_This should be mainly about its mandatory and optional configuration parameters._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +### `sample` Thing Configuration + +| Name | Type | Description | Default | Required | Advanced | +|-----------------|---------|---------------------------------------|---------|----------|----------| +| hostname | text | Hostname or IP address of the device | N/A | yes | no | +| password | text | Password to access the device | N/A | yes | no | +| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes | + +## Channels + +_Here you should provide information about available channel types, what their meaning is and how they can be used._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +| Channel | Type | Read/Write | Description | +|---------|--------|------------|-----------------------------| +| control | Switch | RW | This is the control channel | + +## Full Example + +_Provide a full usage example based on textual configuration files._ +_*.things, *.items examples are mandatory as textual configuration is well used by many users._ +_*.sitemap examples are optional._ + +### Thing Configuration + +```java +Example thing configuration goes here. +``` +### Item Configuration + +```java +Example item configuration goes here. +``` + +### Sitemap Configuration + +```perl +Optional Sitemap configuration goes here. +Remove this section, if not needed. +``` + +## Any custom content here! + +_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.growatt/pom.xml b/bundles/org.openhab.binding.growatt/pom.xml new file mode 100644 index 0000000000000..6b54df216c2f6 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.0.0-SNAPSHOT + + + org.openhab.binding.growatt + + openHAB Add-ons :: Bundles :: Growatt Binding + + diff --git a/bundles/org.openhab.binding.growatt/src/main/feature/feature.xml b/bundles/org.openhab.binding.growatt/src/main/feature/feature.xml new file mode 100644 index 0000000000000..75e9fd1cccd54 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.growatt/${project.version} + + diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java new file mode 100644 index 0000000000000..a29f6f255d738 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link growattBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class growattBindingConstants { + + private static final String BINDING_ID = "growatt"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "sample"); + + // List of all Channel ids + public static final String CHANNEL_1 = "channel1"; +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattConfiguration.java new file mode 100644 index 0000000000000..c6d5b3b61029e --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link growattConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class growattConfiguration { + + /** + * Sample configuration parameters. Replace with your own. + */ + public String hostname = ""; + public String password = ""; + public int refreshInterval = 600; +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java new file mode 100644 index 0000000000000..5069d66376fed --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal; + +import static org.openhab.binding.growatt.internal.growattBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link growattHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class growattHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(growattHandler.class); + + private @Nullable growattConfiguration config; + + public growattHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (CHANNEL_1.equals(channelUID.getId())) { + if (command instanceof RefreshType) { + // TODO: handle data refresh + } + + // TODO: handle command + + // Note: if communication with thing fails for some reason, + // indicate that by setting the status with detail information: + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + // "Could not control device at IP address x.x.x.x"); + } + } + + @Override + public void initialize() { + config = getConfigAs(growattConfiguration.class); + + // TODO: Initialize the handler. + // The framework requires you to return from this method quickly, i.e. any network access must be done in + // the background initialization below. + // Also, before leaving this method a thing status from one of ONLINE, OFFLINE or UNKNOWN must be set. This + // might already be the real thing status in case you can decide it directly. + // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN + // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the + // background. + + // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. + // the framework is then able to reuse the resources from the thing handler initialization. + // we set this upfront to reliably check status updates in unit tests. + updateStatus(ThingStatus.UNKNOWN); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + // when done do: + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + }); + + // These logging types should be primarily used by bindings + // logger.trace("Example trace message"); + // logger.debug("Example debug message"); + // logger.warn("Example warn message"); + // + // Logging to INFO should be avoided normally. + // See https://www.openhab.org/docs/developer/guidelines.html#f-logging + + // Note: When initialization can NOT be done set the status with more details for further + // analysis. See also class ThingStatusDetail for all available status details. + // Add a description to give user information to understand why thing does not work as expected. E.g. + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + // "Can not access device as username and/or password are invalid"); + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandlerFactory.java new file mode 100644 index 0000000000000..61676d444dea7 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandlerFactory.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal; + +import static org.openhab.binding.growatt.internal.growattBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link growattHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.growatt", service = ThingHandlerFactory.class) +public class growattHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAMPLE); + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_SAMPLE.equals(thingTypeUID)) { + return new growattHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..884355e63ad9e --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,10 @@ + + + + binding + growatt Binding + This is the binding for growatt. + + diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties new file mode 100644 index 0000000000000..0c2f44015a92d --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -0,0 +1,3 @@ +# FIXME: please add all English translations to this file so the texts can be translated using Crowdin +# FIXME: to generate the content of this file run: mvn i18n:generate-default-translations +# FIXME: see also: https://www.openhab.org/docs/developer/utils/i18n.html diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..97d23be7aec24 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,48 @@ + + + + + + + + + Sample thing for growatt Binding + + + + + + + + network-address + + Hostname or IP address of the device + + + password + + Password to access the device + + + + Interval the device is polled in sec. + 600 + true + + + + + + + Number:Temperature + + Sample channel for growatt Binding + + diff --git a/bundles/pom.xml b/bundles/pom.xml index b58688b52dbfa..92362c8ce8ca3 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -157,8 +157,9 @@ org.openhab.binding.globalcache org.openhab.binding.gpstracker org.openhab.binding.gree - org.openhab.binding.groupepsa org.openhab.binding.groheondus + org.openhab.binding.groupepsa + org.openhab.binding.growatt org.openhab.binding.guntamatic org.openhab.binding.haassohnpelletstove org.openhab.binding.harmonyhub From f0eceed6993141910a68daa07dc2ce5f532fc0d0 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 16 Jun 2023 17:22:00 +0100 Subject: [PATCH 002/146] [growatt] beta Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 153 ++++++----- .../doc/growatt.png | Bin 0 -> 12018 bytes .../internal/GrowattBindingConstants.java | 110 ++++++++ .../GrowattInverterConfiguration.java} | 13 +- .../growatt/internal/dto/GrottDevice.java | 46 ++++ .../growatt/internal/dto/GrottValues.java | 76 ++++++ .../GrowattHandlerFactory.java} | 30 ++- .../internal/growattBindingConstants.java | 34 --- .../growatt/internal/growattHandler.java | 104 -------- .../handler/GrowattBridgeHandler.java | 135 ++++++++++ .../handler/GrowattInverterHandler.java | 153 +++++++++++ .../resources/OH-INF/thing/thing-types.xml | 248 +++++++++++++++--- .../binding/growatt/test/GrowattTest.java | 119 +++++++++ .../src/test/resources/simple.json | 36 +++ 14 files changed, 1014 insertions(+), 243 deletions(-) create mode 100644 bundles/org.openhab.binding.growatt/doc/growatt.png create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java rename bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/{growattConfiguration.java => config/GrowattInverterConfiguration.java} (58%) create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java rename bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/{growattHandlerFactory.java => factory/GrowattHandlerFactory.java} (57%) delete mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java delete mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java create mode 100644 bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java create mode 100644 bundles/org.openhab.binding.growatt/src/test/resources/simple.json diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 22bcbfb3ef937..ffe7aa9220d25 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -1,94 +1,129 @@ -# growatt Binding +# Growatt Binding -_Give some details about what this binding is meant for - a protocol, system, specific device._ +![Growatt](doc/growatt.png) -_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._ -_You can place such resources into a `doc` folder next to this README.md._ - -_Put each sentence in a separate line to improve readability of diffs._ +This binding supports the integration of Growatt solar inverters. +It depends on the independent [Grott](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor) proxy server application to intercept the data transmissions between the inverter and the Growatt cloud server. ## Supported Things -_Please describe the different supported things / devices including their ThingTypeUID within this section._ -_Which different types are supported, which models were tested etc.?_ -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ +The binding supports two types of things: -- `bridge`: Short description of the Bridge, if any -- `sample`: Short description of the Thing with the ThingTypeUID `sample` +- `bridge`: The bridge is the interface to the Grott application; it receives the data from all inverters. +- `inverter`: The inverter thing contains channels which are updated with solor production and consumption data. ## Discovery -_Describe the available auto-discovery features here._ -_Mention for what it works and what needs to be kept in mind when using it._ - -## Binding Configuration +There is no automatic discovery of the bridge or inverter things. -_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ -_In this section, you should link to this file and provide some information about the options._ -_The file could e.g. look like:_ +## Grott Application -``` -# Configuration for the growatt Binding -# -# Default secret key for the pairing of the growatt Thing. -# It has to be between 10-40 (alphanumeric) characters. -# This may be changed by the user for security reasons. -secret=openHABSecret -``` +The Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. +It intercepts and decoded the data packets sent from the inverter to the cloud server. +And it uses the `grottext.py` application extension to send a copy of the intercepted data also to your OpenHAB system. +The data is transmitted via an HTTP POST to the 'http://:8080/growatt' end point with a JSON pay-load. -_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._ +You need to install the Grott application either on the same computer as OpenHAB or on another computer. -_If your binding does not offer any generic configurations, you can remove this section completely._ +_**NOTE**: make sure that the Grott application is fully operational for your inveter **BEFORE** you create any things in OpenHAB!_ -## Thing Configuration +You should configure the Grott application via its `grott.ini` file. +Configure Grott to match your inverter according to the [instructions](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor). +To operate with OpenHAB the recommended Grott configuration is as follows: -_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ -_This should be mainly about its mandatory and optional configuration parameters._ +- Configure Grott to run in proxy mode. +- Configure Grott to start as a service. +- Configure your inverter type in Grott. +- Install the `grottext.py` application extension in the Grott home folder. +- Configure the `grottext` extension's ip address, port, and path via `grott.ini` as follows: -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ +| Entry | Configuration Entry Value | +|---------|--------------------------------------------------| +| ip | IP address of your OpenHab computer. | +| port | Port of your OpenHab core server (usually 8080). | +| path | 'growatt' (fixed value). | -### `sample` Thing Configuration +## Thing Configuration -| Name | Type | Description | Default | Required | Advanced | -|-----------------|---------|---------------------------------------|---------|----------|----------| -| hostname | text | Hostname or IP address of the device | N/A | yes | no | -| password | text | Password to access the device | N/A | yes | no | -| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes | +The `bridge` thing requires no configuration. -## Channels +The `inverter` thing requires configuration of its serial number resp. `deviceId`: -_Here you should provide information about available channel types, what their meaning is and how they can be used._ +| Name | Type | Description | Required | +|-----------|---------|-------------------------------------------------------------------------------------------|----------| +| deviceId | text | Device serial number or id as configuted in the Growatt cloud, and the Grott application. | yes | -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ +## Channels -| Channel | Type | Read/Write | Description | -|---------|--------|------------|-----------------------------| -| control | Switch | RW | This is the control channel | +The `bridge` thing has no channels. + +The `inverter` thing supports many possible channels relating to solar generation and consumption. +All channels are read only. +Depending on the inverter model, and it configuration, not all of the channels will be present. +The list of all possible channels is as follows: + +| Channel | Type | Description | +|--------------------|--------------------|-----------------------------------------------------| +| pvstatus | Number | Status of the inverter (0=ready, 1=online, 2=fault) | +| pvpowerin | Number:power | Total solar input power. | +| pv1voltage | Number:voltage | Voltage from solar panel string #1. | +| pv1current | Number:current | Current from solar panel string #1. | +| pv1watt | Number:power | Power from solar panel string #1. | +| pv2voltage | Number:voltage | Voltage of solar panel string #2. | +| pv2current | Number:current | Current from solar panel string #2. | +| pv2watt | Number:power | Power from solar panel string #2. | +| pvpowerout | Number:power | Total solar output power. | +| pvfrequency | Number:frequency | Frequency of the grid supply. | +| pvgridvoltage | Number:voltage | Voltage of the grid supply. | +| pvgridcurrent | Number:current | Current delivered to the grid supply. | +| pvgridpower | Number:power | Power delivered to the grid supply. | +| pvgridvoltage2 | Number:voltage | Voltage of the grid supply #2. | +| pvgridcurrent2 | Number:current | Current delivered to the grid supply #2. | +| pvgridpower2 | Number:power | Power delivered to the grid supply #2. | +| pvgridvoltage3 | Number:voltage | Voltage of the grid supply #3. | +| pvgridcurrent3 | Number:current | Current delivered to the grid supply #3. | +| pvgridpower3 | Number:power | Power delivered to the grid supply #3. | +| pvenergytoday | Number:energy | Solar energy collected today. | +| pvenergytotal | Number:energy | Total solar energy collected. | +| totworktime | Number:time | Total uptime of the inverter. | +| epv1today | Number:energy | Energy from solar panel string #1 today. | +| epv1total | Number:energy | Total energy from solar panel string #1. | +| epv2today | Number:energy | Energy from solar panel string #2 today. | +| epv2total | Number:energy | Total energy from solar panel string #2. | +| epvtotal | Number:energy | Total energy from all solar panels. | +| pvtemperature | Number:temperature | Temperature of the solar panels. | +| pvipmtemperature | Number:temperature | Temperature of the IPM. | +| pvboosttemperature | Number:temperature | Boost temperature. | +| temp4 | Number:temperature | Temperature #4. | +| Vac_RS | Number:voltage | AC voltage R-S. | +| Vac_ST | Number:voltage | AC voltage S-T. | +| Vac_TR | Number:voltage | AC voltage T-R. | +| uwBatVolt_DSP | Number:voltage | Battery voltage DSP. | +| pbusvolt | Number:voltage | P Bus voltage. | +| nbusvolt | Number:voltage | N Bus voltage. | +| eacCharToday | Number:energy | AC charge energy today. | +| eacCharTotal | Number:energy | Total AC charge energy. | +| ebatDischarToday | Number:energy | Battery discharge energy today. | +| ebatDischarTotal | Number:energy | Total battery discharge energy. | +| eacDischarToday | Number:energy | AC discharge energy today. | +| eacDischarTotal | Number:energy | Total AC discharge energy. | +| ACCharCurr | Number:current | AC charge current. | +| ACDischarWatt | Number:power | AC discharge power. | +| ACDischarVA | Number:va | AC discharge VA. | +| BatDischarWatt | Number:power | Battery discharge power. | +| BatDischarVA | Number:va | Battery VA. | +| BatWatt | Number:power | Battery power. | ## Full Example -_Provide a full usage example based on textual configuration files._ -_*.things, *.items examples are mandatory as textual configuration is well used by many users._ -_*.sitemap examples are optional._ - ### Thing Configuration ```java Example thing configuration goes here. ``` + ### Item Configuration ```java Example item configuration goes here. ``` - -### Sitemap Configuration - -```perl -Optional Sitemap configuration goes here. -Remove this section, if not needed. -``` - -## Any custom content here! - -_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.growatt/doc/growatt.png b/bundles/org.openhab.binding.growatt/doc/growatt.png new file mode 100644 index 0000000000000000000000000000000000000000..0512c4e02603b2d808c76ec4478839b446171cb5 GIT binary patch literal 12018 zcmdtIXE>Z)7cY)sq6R_qlBm(!5PkF>Jp_Z%yXc*amPhY{L3ANRuhEH!&geZlQKLtj zGtc|}|KHB1bDi_yoDVbiz4vwRy~w|$H2fKQdLpV#lXN!LcjmS z1)~3dP&99#ADEuH%5oT0V|4rI2DY87rYr_VZ6d)v6bIeLd$01&69ePve}0%^78K>^ z&L>`~nuid};>?fH!T zkM&`eMo^B>fY5-|orbjwT)}-t?kl-(@-x- z2q-{5UPc4ubjTGIpAki%dt+E=a20jPDO80?VhTApQ|y_@6?ODkCc)@0%Tx+s+URDo z!vCIu$RtqGlVIQN)o?D4ii4DfW#06u3`2FZ6~YA*Fttu2CNIuCd5aW8gM&)qwpvuu65Uk5|S9&Fqkp{1Euqt5M}B{=ws$ck5vl-3!LN zTgt2qRGy8`YG}+zVXsVjyxpVb)0uWwI}^j^uunW+&qs?pm}0!G5;>UgP{=45>553+YbEkiQ9WRU9=v<$G|kM z45=%W=E5LMo-&!I+CuLjS5|slt?t!B<+ltE{ik+oUTv_I)%8n$A zgd=|(S@wj)^dI02zO#dUj|?bU^ukE_2OI)D+S+syAP5cug3b!yQP0p&3ZtryRf2|VY0WMv={7QgnhM5A42L75D8NF%x*-y&!=1<=b<~>=3D=N6})A7@9|#_W%c1zNADgRP4xLpc>H+ z`ShH2x1XP4i;9aXdQhckN&8FywoDztxgOtaT3>y2!u0byP2=WzbaFW6Nc;HE@hJKk zJ<%r`62SiN{iOSE#G0=l#O-~tHvOT@6fPzXFUb{&oNQLW$5Jh9cVB)CE)!Lz}D)IY^SC0%oyrt}c2tSeF7bk)_N%H}Z48EN2`( z9?D?pdSn(0_Afyj)D;CUM1sRcQ+-?EhU^GU-|Fm#~1W#HlLL=_!k^j`mJJz9*H-N z45I*I-u^gF3}_<|h#3XfJfHqFlQYw*co-i_oN0Z=tO>*ET3eKv9M7_@EsHAHmz@nKPUTsM&JMhtaovMp~%5uHhTe zwKNIe;~V9uX4jQ(+bBxKNc2<-dVgamwKpMjq_I4%;&%iN`y2t>CWD0wSa$`6rVHG| zY9Tj`_fV_yx>8oNQLEF;fxeH#B^7N;TOCVxsD!2F?VfOZ5Ht43Rl?KO!0W2e?{93M zCi%K-v67r68HJr39e9oz+Sz>`9Cta9Xcc+o9XR*#Vv{OwP-X}gG%^qYs% zxZ{;JyGPoA(=}hZwSKVQ&QG$lC@;Z56Z19s^Q9V1W($+LB1k{n#4b6kAmc_WvEzL@?gGt(k@-mhnqmKp*)e{Syj~CkLu>`nID8R>q)H*w)sT z%IaY1k0j@5B+~LsTR%cdmm!Ps_(wdg%~F%gPFF#1uiQeNP3&JcT9B~tPh^R@W{C9t zsr=yZ@HxtoeHkFEm@6J^{I=T6rA# zcLgc^?24^|wAps974&`F{vJ(MXVHU|B@@~?Xqe^8xI3E7Uf*$%NH1agz$@pM=YH~@ zF}brJ9DH}BYG{}NJz4Fj3q^&TUvmuo z1m7}V4t?UBmiA))r}sWgCM_%~3po=w+*+K(BXxV9yc|5imS@mz!G`yIcAVaee#dWN zHRxQE41)ysM?5_jpCqbX@_cD+Xy9P(E>Deb*$vw_P0n39`&wv{n*1gxUCugsijAzdMmQq62OzV_3wk;|~3 zv~KM$s^h4j$i|t>$)auF)xTElcY=#|(p-jLZ`EFL=bC2MI669xh%uY{_itV1`mPF~ zLcEt>AM6y|-UfI+o{vkh*Gn-gp9UQ-*lCJ?{rdIs^Ty1A=kC_!oVn=S&p%;@c-0dh zcI}FS1Fy4uf&NkN5-wNiC5zv8<|U%#?O~ztPYO$o~vJPp9+B^~)a6JZj*X)`rc{d`&79akE7=b}c zl@kB{=~%9$f8BI|tZm1&6`1Mo#gtZ;E=Y@=3iqsL6{$iN>7*<`g5B+DrX3Y_lDHTh z59cv=mF}|aS34&>_Vq#ZVkFoMgC`o z{#j5~H+VJhL~1I>p+W&5`}OcPp7zna*ZX`&Gu1a|X@1`Nm}}Mlo_2gtiJqSR$Gp4N zWLo4RE+L)+IVI(=YIV{Fr+H~+&19nD>a~hT(>S++?>YBwgMYpavO$I*(ZDc9X0w)9 z1B0D4+!hfzS`q13Pgg%aex&`W+ACQa5nx!;xq%rT<1U?8fPSfFt}UX6gi!iSZ7Q$> z$*K0a?A<&rt4c8rS5FvjF&z(yD40er73;Ndpfb4p|Vl=SMcTE~qmdnX!CcL}e5&7rE$?jOL z=zvl{BpL1cHLTP=C!Eb@nqNo$dzVg|Y(2jQDb7Gyj5Cwyv0>JrCxTi5is~1z^5UR+6GD(0ke1OoyQM1lU@ef)YOadr1sjJ zkmK|kmpk$abJ)T79}QegPK*++B{p2DpB2@eoIIpqd{y-QU|aUIXAmN6P464YTPG69 zD!*NvaaJeb2IPsRVGD4T)?}SJKP~qB%a@8#iEzYALhS36rUhPp5^_PTb%~AEwze-4WBBCNARPaBq?2gE0M- z>t7-R?v9!tv`Q#Hjk;HpgbAOv$7XavW04=lbm_bi6@Tgy!TB&QV3xY{c(083>KfWiCu*7PYn~Vn`efw)-kXF0zo@ZZ|&Uphp3{C`)9i(}9RpuVl z6xb{vvaEhj8_wwr*xlqOnNB!e>S?`^Vm@ifySZL$Jh<12Bp5B*%kK5RIWD6wbR$DW z{mDpA|C<+Assi-*T*1@i&!`^rsQcTEq3-FMv?}zLs!`ZyJj&kVmoM*AN^H0L@FD)U zlp4F0QlbF;uToTx5ZRJl1mliwLvsKqVSCun&tTtGY`dduPelP-GfEuMVDY+asOWdX zn0+zK!p`JU36K1`bf(}zoTRg_%?z|Q3wGp#2=3-j36X@9y^cy&66sB(>%khwkO{f> zooLA90rb?5j*p85i`rW%)HHQ;aqw%L<1>`G6k5lklz;ab{Y!Fld*~4yBL_6xyDhg;YE^25(ux%i-gK$i@1sSds2(x_JAkyB0Y)e#kUw6 z$&P7n@&2rCNMrjCaPThenB$%-lb>A2E}}5UzbCQZ%iO}*G!fQHwl;+-K;6|Q0rI{iyI84qM#D?-S|{UJ!NzLrT!uaUb^Y>)*NNtBw^KVGFCA|%|#el-E@j<{9aTz_E;jY&hM z(ZFZeE{^v2AQHkAMRQWW;lL_;ph*LVXbPULe$$pS6Fuazu9W+n!nd~5Kym4F$QS)m zITQ}Ud%`Q}NS8AmQV`McT6AtCR951qIGRk8@tGo#h#0NP(#n*>+{e1@%_$V8Z`*=9 z!{>>S4MFi}n&n<3eMWZnDF0h&JxBhb1u#DC&X=fPSXTsmPGcZiZFF#>q@tcC8Q6)=D5(od^M4apZv8nc6!mb&O9(bbGgYUlo zUXuVm8dU(@Y*{n0AFyTTVu7q`w(HL*OR7UpoxlG(nPO@x?C>i?IU0$N|QHbN|u7EQ*E;1REF_FL|a zY0Tg=hsUO;+bS9z^3+;xtJP;;Of)CwMsLE1(PwRd2~O6CCbch8*HvtkyinXuKSH8@ zx@7QIhDVk(kQ%udHP|Po8S3BIg+k_6p9fZ0F~2&rjxq>I?RmE8j;HQw)#|fFaOaDi z&A)KA>^Yv*!CYg*4rgv&>FRrnc6#5Q6alY7_Vx!QTh{;D@3Yri|FJArIp=nXOb_Yn zWcT?e?q%i+5zM|F6{oq%o7H!6JyM#4sxX&}c1K+Zrp9wBbN$B|P)-sh}b!-9y z>p$bK8oi@65p&w@C$fw>dW(agBix!z5>C>3gEAc*u-MrvQ5|bc`o6vF(hXGPxMU^Z z(?bV|?dv%1R1rR|U^ZabhpTws}$%^3i%!FJU6(wK$= zq`c7l;@v!0RoxsAXj){Q7hOXvD#D9I65mV{ycUr&v)Cxpbe*q}T@Ad3)aR+rOKjtd zX!iMhHZBV`b zrob>FJ8kjrPaPM<2CXfb=38(jj-a1~d5&7ZAKrC^bl^SB!p%Oh-*@^Y(b!H-rq}RHh0a35a><`yIe(3%2NfOv(FC|>G{V5{ zalM?5Iedw9*>-}YL<;PaU~Sr#fJ?;w94-MU}B+SLT@d8vMVa~?M!vk8vb8>L9Xw$nOQlPjMnFuN?q5p6_+>}^;--`o^cMhk~ zH!}+<-qk=;O@+}_`a{rS(tQb#@L+c*p9Fh7BPw3t;?epftomRoZc2phufP}yp9O!o zXOQ&c*HxCl;=V%8B4;wiy}yK(wrPj0)`Wy3T*ZA3=hr{~;`MU;{QCeOte57p|L9JQ z3nsIL$`<621x|vOx2>7_b95pO0JIFWv;)Z`KK+1u(IU-{?$ocep*^lV zyuMlRM0AJqLfy~Yt?__4YLG{9`{SaMZNzNF=8r)xW+ep?=B>M}=+uzo!^o#7fgZ0_o*?)h4;k*FJ9}cfhPj7{an>GQ@km`Jc_9ve*x^j8D~ffp9OTxf96+uP$OqWUu2PH`W__6B{YpR#V;hQwds zOAI=&gkOuNFIc?>?ewyQFs!YKF@B zP!h3#&{$(mfGL2F4@-YTfEG|*U7hiVRD9&mSy9)^l?e^6pXS$C<7luUmk58&ZY)P^ z3@vt+uDX`hP8_vuVt}FIej?V*8=W18{J;2~Tn)mQpY9$3PwusrUQL3Nswvwoq&LHf zi|2(1sfn1>_YH$8m0%PFnnttyZnM~;M zvKaOH%(upa5=(U4Xs#*`m>aadX-|F734JZ zm(KP-v%TgqZ_<#yNPzzH3PFj)Gw{mzK>J~bb42w$=`BdZ^J9vcc$3@O@n;M6w9G~? zVT-VtnVH8dpKrmt37*|**sM#(u6GD5gdhJ$7Rdf{oOmw7svEocSNtTxick+r7l>pa zM!_o{vCw_+ltZbUXF1%5)8eN)!Kl*>#C}d)QMTX?jAbZB`?GUMz;VL!Mf2Wx2|CJ^ z=|7ACZaiJIoAlCNo3KDUONlI{i#wO0Ka+QGboP20Ri;WOefa2_uh8amWbFT=mMsh~ zmA4Ul+Ni-x7q%ms@uFlPkuh~Vm$V*!>33TP<7b$`c1%Xq3`N^4A7Do4u9=_R$MNokjs}`mecAv zb5Lxx9u~RNra-#mXmJq_cjdG&qEfNoN}K;IAzWhN8t+1e32*v2I%+Ugi?jCR+}Szu z>0Qlw+g_?W1;S1+m%1A7M}#c7u{5a0)wuLPY7(qFya29R;}Qko@|?ETwCr{L{5nP> zGjoV`yPhKWR`X6n78`3#BbyB7k`>CnT0p;RL?BY(KH}16VeLnY?{MSnO=gjk-S*r| z$zc1`kduzgkCq+!T}1#y5rAhGW2h#tr}XWOl;IINmT<~FhK^(kFmI+i3w-SLhD)gZqMETW zIrxUg2<^+Il4IcyVn5~6*4qXx@JN@EL3*Dx^`>yUIYh~Uz}Tb=)u-Wv_Dl=u^)N7N z#;%=Y0m%s8O~Uk=Ez~pC~U(@_?WT zp9Ryn<^EGb?c}qnSNG66<#7UA!X>6e>pEQ3bgv=4eh0JQTuP0HDS^WI#r=Cz^)q|% zIu|a{V2aqoJ5F?fPqSDJ*i@ejiY#T}PP8;R7Yo^l$n$VqmoBMbE&O}5S9|#U)Kn2L zSzRt*PlRpoz_S}#4(#HfEB9X`uv~B$TJ$*q%D5I!v{t0c@l zrA*qK!I;8}YJA^=b-9e;^|q)9l&SFl)BiSc8%=XxSX1*$tKE3iU1r;F2C2j1EqXOD z7BwZK*QHKJ7y--QDnELXQm&o7&>Y$oFzRIi$ZhdClTRhEJwr1v@R#7KzD37`2HI-O zJ91&9qAGp(3A!Kn(?^hgHPQ@V&;&KzJ(=)F}np~P?1aaVZF`o^9G3{U1OtnsX5Su z(rIq^xp2}UCo=#RL%SO&ds1GF)1v|Qe7h3wmn|$vdc}v1deojj4>25dTb6V{V`EY3 zsPOnR0{m&G529O>Z)ERG1^E&1 z*|(l%g*AxUtG^00AqM8=I{*_4#7#)k63YM)5lb676L2ik!po)p_=YGV>w|6KZ>LFL zqgeG^V(~4773+8UH&URygiRVerow);6)nlz}is%ci{K&C;+y48OOq z=TcojRJOfgVKQ*}naz?8q19iO%w9X9jk|+zPn5F9+2+A+?K+w|q2||U|JFD(@#&=N zlO;PTYyat=I<|?i3ec=1uT(&7gM_1a^q0!YhE+yDpaTm&h+#y1QkwSk@$O{!@<8-n zs1UFIWYY-Fs)s%Y4Y+~Gm8BPLT;}@WP`XNqOS1BoZ;Amxd%PQol~LWrc9rJ?g71*@ zS1{`;4xr!x^L4P}1hM5(RibUqR_Hd+~6{7lCYyDm-RpaEBiuV-Q{)bT^lCq53BZZJMLj%Qa|>Sb!l@83`N_nfahQ;f@8Ok0CxITuBS3lMWQeOaNj6Hh&8qv~M!*xziu zZ<$Kmg?Ch)s06=2*y&~Sy$y7dA9y2is|?WCb2~)BxAaIxc1n|TrPXsT0NOwEsYjN- zs)qidCdEyEot}`EA%~DnyReCLdZ_omr~eiqc28?jy{a@isSe_|jTQ6PQhoE-JaYK{ za3MiDmB$WsZ8#rvMOjyOaWc9#xt=)g>@- zb{_!~;{6FkZDqckxDm#x4lfHO#$Ecj+>{!C{LUx7F`g$={V0{(5qNc5-o_;(W3DMg zQRd&mYcBtv%V>*`g@lc&Dh_&b!Jb~=%tR8E`|VgO!D6p26Dkg6^jI47!QA6|Ph<2H8=i7^V4KL%EZ3Yk$9nk-U;#`qDg~k|69)Pes8j9rt3)%e{jIow)zBW zUC{YDwxD%kN(96LOpAOttb~4!<;M5fjR*l_z5FEP}kV_ zcOG@#Cp4<<=ysUC+$!mA^<g}Z2R}yiEqn8lb4DKQY z*M_9QJGqS9XKj6zgmZbk?M2j{AV%HOMe7N6L!apE{a2-H2A=0v%VCHI)v8O z$19Hu3wpCe*fV#fSE+S7(=ru3DM7jG7EFG}P1%e%`H-ONPg1ic!;bj0rC(~_ulK6- zaO>Q3a>4lwr+l|Z|G3dGH;}?vRYg;VpHDONB1VvSLgNrDIuVjsU8C+V-+47{68pgX zOf0VJ4<6;~FvZr^|59YpJ~(>jQ)wbdM~K}U7PQzn1#5Co6D-25uY@3yF_g5R8^(0K zypitMp29r~_(C@jCsUg9ozZi>xKiVjRjZfz@FpN)y2i7(g8zNDO`GN6!n2ZUR-Gzf z7{Xp;%aLh5U1*7h9{>wC>oR<`G@M60EpIP`X|lo_1MOd3w`8Jve(+6HTs(48dUa}3 zcOT6>6iX^MrKiq-b) zBVMo`Rb$k68;!8irp>pp#f$x^nFc)==|5CZxT>bH-h8Rm;4jGciQg^f$z_;AN#GSJ zUzX~SANkqs8Lzn19?cx9j^I+1%3nf#p)FLnVM_`05;>wzu>0k65wu_LDS7C3$F4@YzQ3rv*5@Vgj%ymEu2q6vsmhLX^}e6%#sA} z+6LiXzaE0Zy~JUKVK|T8)SD-W7n??tuXUP+Z`i|n70WY-$$!-sV2YBIRd<evfu!K*CB(#5{L0S$hQ9 zMcgCELkMp~)vEWL6Y5Z6oiaY;aZYEJOqC%Okn!i-DfW7tTGO4u$AJ^$; z$`gU^>%Q7Aza3HdKH}*3D;0b21}K~p3!$(hT0Plo4}dr?cpuY z<;x}B&3lMA%HT#JEw|#FQu{!BREk7(Eo@gsiSkr~JK`wYe6S0dgmCZH+bUxlxK^!! zx_ayst`LzVBe!5o`j_xPVVfaxG@EiYjOkG6_byw2aDp)39jTKwol7Gk?BBujtQivc zTT`B0Dd=LNGEDCp0a28RN2O1SX`9J$dPLW))n`}`1oxjz``tzAES{u9^&us7cU+0n zR>_YF{4sdLeQo%f;3_gvfKtGmWOf78Kp$#0FhcEmw=VNksr|lA?;HuJhJW+eRs((2 zcn>0(h8Inr^->{$&&qb!gB`wk>o!4dMnAaFAN;vJH2)R*kj8s#_{@vpv|_9up69j=3%i58QjwTS zN7Xf$o<96m(y54q3K)0>1gZ_?GX%hr66dU_gp+^P@F#(Bb3k)4sSgI5BMeN&AL#Ah zray+$eGS$^EcGc5On{LG^R=6`C=BNwcpa51!sTeCO;lHCmS!Bg5TCwZ2T^Mw>F%_) z8vMIa5C_J%v$neWVv}C^n)MmlIOFtVtulSU?Zm=FrI-v(V!qG>F%g`w?L5UeVUJbeQ~X?p#rZXz`Hj{9EW+$oW&JJYwcA zu>4ogBg0=uIfj=3!!TL)nmrb>1nB#a;AfE*i+<wCS#~Yp)|&<5L})cC7iqJ zYN<=Ig?E3#(nPk0(i7}WDv{vB2wTc~+!{@XX)p;eWN*Io>OPX-(csCODtTi8OhGc} zUiE>!Fm`^+PB#mIra#rqY2oIVkwK=!vMkn<$%IGva)0c07A_mb3>b@IYj;35Ho=hz z784t5rv-ZdRR{P%1JHsaE7Y02)L=g_53lbQsz=- z+hQi$V|_@;!CvO2=!xNQ_-f$eOkp%|qcpqOb?}eXfFG3qz8$QK?2eJi_UWaySGiK6C5;bp!fTxAXS15-1$8dj ze`)uQx=wPZwsH2Kto{wI^(kvw<-J>MgrG~aeu!yts9I7?Y_h*Gf(dMEYHB_%BpTjS z{ghE6D|lP?^G-G&J5g1esxP4*hwPdXHu;N6d}|<%+6wO6eUIm5mf?4e0pl`Zbus}# zIxAPN^_LypuE*o5W}8GnNBS02o;fF-Y{kTYZqKajROXY;^91Ucm|aS8DvO)>SnLu9 z?3wN1jO@FQc2sbg5vLE-O#fIeq%a-j|2_0IL%Ulr8}P)FC`mf$p!vh1I zE^EB?cB6uGjG{i3{(Q+FyG>6~uSI494cGkdT(X1$o3booOMC?>&S17sa;D}$hnFsd z6h8=qg4|MxyWB>$`tC%}VC^r*mpa<5stojr-T?}tnk}&6KlKzuM1B!}8?#a$*u35i z1MC>7sz$%Uv!qUSej>-o08OJtkpHfzschh$*Z= zCmhz3pU6=~G^~r43YbJihS7C0UX{o)@JG-CWAFmqM{bD8@PTZMtJg}>?}N7he|y}3 ze_LqWJ6f&(k5t(Huh;YvqHCLR-Ie}dEPwu=m3AYUyRK1~Prs8$GJdrgMwg^xs4Biu JsFJe?`ybdK(8B-# literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java new file mode 100644 index 0000000000000..9e6588ad5b731 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal; + +import java.util.AbstractMap; +import java.util.Map; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link GrowattBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattBindingConstants { + + private static final String BINDING_ID = "growatt"; + + /** + * List of Thing Type UIDs + */ + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); + public static final ThingTypeUID THING_TYPE_INVERTER = new ThingTypeUID(BINDING_ID, "inverter"); + + /** + * Class encapsulating units of measure and scale information. + */ + public static class UoM { + public final Unit units; + public final float divisor; + + public UoM(Unit units, float divisor) { + this.units = units; + this.divisor = divisor; + } + } + + /** + * Map of supported channel ids and respective UoM + */ + public static final Map CHANNEL_ID_UOM_MAP = Map.ofEntries( + new AbstractMap.SimpleEntry("pvstatus", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("pvpowerin", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pv1voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv1current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pv1watt", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pv2voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv2current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pv2watt", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pvpowerout", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pvfrequency", new UoM(Units.HERTZ, 100)), + new AbstractMap.SimpleEntry("pvgridvoltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pvgridcurrent", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pvgridpower", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pvgridvoltage2", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pvgridcurrent2", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pvgridpower2", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pvgridvoltage3", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pvgridcurrent3", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pvgridpower3", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pvenergytoday", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pvenergytotal", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("totworktime", new UoM(Units.SECOND, 2)), + new AbstractMap.SimpleEntry("epv1today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("epv1total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("epv2today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("epv2total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("epvtotal", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pvtemperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pvipmtemperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pvboosttemperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("temp4", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("Vac_RS", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("Vac_ST", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("Vac_TR", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("uwBatVolt_DSP", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pbusvolt", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("nbusvolt", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("eacCharToday", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("eacCharTotal", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("ebatDischarToday", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("ebatDischarTotal", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("eacDischarToday", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("eacDischarTotal", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("ACCharCurr", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("ACDischarWatt", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("ACDischarVA", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("BatDischarWatt", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("BatDischarVA", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("BatWatt", new UoM(Units.WATT, 10)) + // + ); +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java similarity index 58% rename from bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattConfiguration.java rename to bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java index c6d5b3b61029e..0bc78de7aa21e 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattConfiguration.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java @@ -10,22 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.growatt.internal; +package org.openhab.binding.growatt.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link growattConfiguration} class contains fields mapping thing configuration parameters. + * The {@link GrowattInverterConfiguration} class contains fields mapping thing configuration parameters. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public class growattConfiguration { +public class GrowattInverterConfiguration { - /** - * Sample configuration parameters. Replace with your own. - */ - public String hostname = ""; - public String password = ""; - public int refreshInterval = 600; + public String deviceId = ""; } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java new file mode 100644 index 0000000000000..f473cc8f37720 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto; + +import java.lang.reflect.Type; +import java.util.ArrayList; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +/** + * The {@link GrottDevice} is a DTO containing data fields received from the Grott application. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrottDevice { + + // @formatter:off + public static final Type GROTT_DEVICE_ARRAY = new TypeToken>() {}.getType(); + // @formatter:on + + private @SerializedName("device") String deviceId = ""; + private @Nullable GrottValues values; + + public String getDeviceId() { + return deviceId; + } + + public @Nullable GrottValues getValues() { + return values; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java new file mode 100644 index 0000000000000..4518d6ca220f3 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link GrottValues} is a DTO containing inverter value fields received from the Grott application. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrottValues { + public @Nullable Integer pvstatus; + public @Nullable Integer pvpowerin; + public @Nullable Integer pv1voltage; + public @Nullable Integer pv1current; + public @Nullable Integer pv1watt; + public @Nullable Integer pv2voltage; + public @Nullable Integer pv2current; + public @Nullable Integer pv2watt; + public @Nullable Integer pvpowerout; + public @Nullable @SerializedName("pvfrequentie") Integer pvfrequency; + public @Nullable Integer pvgridvoltage; + public @Nullable Integer pvgridcurrent; + public @Nullable Integer pvgridpower; + public @Nullable Integer pvgridvoltage2; + public @Nullable Integer pvgridcurrent2; + public @Nullable Integer pvgridpower2; + public @Nullable Integer pvgridvoltage3; + public @Nullable Integer pvgridcurrent3; + public @Nullable Integer pvgridpower3; + public @Nullable Integer pvenergytoday; + public @Nullable Integer pvenergytotal; + public @Nullable Integer totworktime; + public @Nullable Integer epv1today; + public @Nullable Integer epv1total; + public @Nullable Integer epv2today; + public @Nullable Integer epv2total; + public @Nullable Integer epvtotal; + public @Nullable Integer pvtemperature; + public @Nullable Integer pvipmtemperature; + public @Nullable @SerializedName("pvboottemperature") Integer pvboosttemperature; + public @Nullable Integer temp4; + public @Nullable Integer Vac_RS; + public @Nullable Integer Vac_ST; + public @Nullable Integer Vac_TR; + public @Nullable Integer uwBatVolt_DSP; + public @Nullable Integer pbusvolt; + public @Nullable Integer nbusvolt; + public @Nullable Integer eacCharToday; + public @Nullable Integer eacCharTotal; + public @Nullable Integer ebatDischarToday; + public @Nullable Integer ebatDischarTotal; + public @Nullable Integer eacDischarToday; + public @Nullable Integer eacDischarTotal; + public @Nullable Integer ACCharCurr; + public @Nullable Integer ACDischarWatt; + public @Nullable Integer ACDischarVA; + public @Nullable Integer BatDischarWatt; + public @Nullable Integer BatDischarVA; + public @Nullable Integer BatWatt; +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java similarity index 57% rename from bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandlerFactory.java rename to bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index 61676d444dea7..ecee03a252fa9 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -10,32 +10,44 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.growatt.internal; +package org.openhab.binding.growatt.internal.factory; -import static org.openhab.binding.growatt.internal.growattBindingConstants.*; +import static org.openhab.binding.growatt.internal.GrowattBindingConstants.*; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.growatt.internal.handler.GrowattBridgeHandler; +import org.openhab.binding.growatt.internal.handler.GrowattInverterHandler; +import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.HttpService; /** - * The {@link growattHandlerFactory} is responsible for creating things and thing + * The {@link GrowattHandlerFactory} is responsible for creating things and thing * handlers. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault @Component(configurationPid = "binding.growatt", service = ThingHandlerFactory.class) -public class growattHandlerFactory extends BaseThingHandlerFactory { +public class GrowattHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAMPLE); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_INVERTER); + private final HttpService httpService; + + @Activate + public GrowattHandlerFactory(@Reference HttpService httpService) { + this.httpService = httpService; + } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -46,8 +58,12 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (THING_TYPE_SAMPLE.equals(thingTypeUID)) { - return new growattHandler(thing); + if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return new GrowattBridgeHandler((Bridge) thing, httpService); + } + + if (THING_TYPE_INVERTER.equals(thingTypeUID)) { + return new GrowattInverterHandler(thing); } return null; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java deleted file mode 100644 index a29f6f255d738..0000000000000 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattBindingConstants.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.growatt.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.ThingTypeUID; - -/** - * The {@link growattBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class growattBindingConstants { - - private static final String BINDING_ID = "growatt"; - - // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "sample"); - - // List of all Channel ids - public static final String CHANNEL_1 = "channel1"; -} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java deleted file mode 100644 index 5069d66376fed..0000000000000 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/growattHandler.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.growatt.internal; - -import static org.openhab.binding.growatt.internal.growattBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link growattHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class growattHandler extends BaseThingHandler { - - private final Logger logger = LoggerFactory.getLogger(growattHandler.class); - - private @Nullable growattConfiguration config; - - public growattHandler(Thing thing) { - super(thing); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (CHANNEL_1.equals(channelUID.getId())) { - if (command instanceof RefreshType) { - // TODO: handle data refresh - } - - // TODO: handle command - - // Note: if communication with thing fails for some reason, - // indicate that by setting the status with detail information: - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - // "Could not control device at IP address x.x.x.x"); - } - } - - @Override - public void initialize() { - config = getConfigAs(growattConfiguration.class); - - // TODO: Initialize the handler. - // The framework requires you to return from this method quickly, i.e. any network access must be done in - // the background initialization below. - // Also, before leaving this method a thing status from one of ONLINE, OFFLINE or UNKNOWN must be set. This - // might already be the real thing status in case you can decide it directly. - // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN - // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the - // background. - - // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. - // the framework is then able to reuse the resources from the thing handler initialization. - // we set this upfront to reliably check status updates in unit tests. - updateStatus(ThingStatus.UNKNOWN); - - // Example for background initialization: - scheduler.execute(() -> { - boolean thingReachable = true; // - // when done do: - if (thingReachable) { - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.OFFLINE); - } - }); - - // These logging types should be primarily used by bindings - // logger.trace("Example trace message"); - // logger.debug("Example debug message"); - // logger.warn("Example warn message"); - // - // Logging to INFO should be avoided normally. - // See https://www.openhab.org/docs/developer/guidelines.html#f-logging - - // Note: When initialization can NOT be done set the status with more details for further - // analysis. See also class ThingStatusDetail for all available status details. - // Add a description to give user information to understand why thing does not work as expected. E.g. - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - // "Can not access device as username and/or password are invalid"); - } -} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java new file mode 100644 index 0000000000000..2d1e670b5ea80 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.handler; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.growatt.internal.dto.GrottDevice; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.types.Command; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link GrowattBridgeHandler} is a bridge handler for accessing Growatt inverters via the Grott application. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattBridgeHandler extends BaseBridgeHandler { + + private static final String GROWATT_SERVLET_PATH_ALIAS = "/growatt"; + + /** + * Inner servlet instance class to handle POST data from the Grott application. + */ + private class GrottServlet extends HttpServlet { + + private static final long serialVersionUID = 36178542423191036L; + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.setStatus(HttpServletResponse.SC_OK); + handleGrottContent(request.getContentLength() <= 0 ? "" + : new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); + } + } + + private final Logger logger = LoggerFactory.getLogger(GrowattBridgeHandler.class); + private final Gson gson = new Gson(); + private final HttpService httpService; + + public GrowattBridgeHandler(Bridge bridge, HttpService httpService) { + super(bridge); + this.httpService = httpService; + } + + @Override + public void dispose() { + httpService.unregister(GROWATT_SERVLET_PATH_ALIAS); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // everything is read only so do nothing + } + + /** + * Process JSON content posted by the Grott application to our servlet. + */ + @SuppressWarnings("null") + protected void handleGrottContent(String json) { + logger.trace("handleGrottContent() json:{}", json); + JsonElement jsonElement; + try { + jsonElement = JsonParser.parseString(json); + } catch (JsonSyntaxException e) { + logger.debug("onContent() invalid JSON string '{}'", json, e); + return; + } + List grottDevices = new ArrayList<>(); + try { + if (jsonElement.isJsonObject()) { + GrottDevice device = gson.fromJson(jsonElement, GrottDevice.class); + if (device != null) { + grottDevices.add(device); + } + } else if (jsonElement.isJsonArray()) { + List devices = gson.fromJson(jsonElement, GrottDevice.GROTT_DEVICE_ARRAY); + if (devices != null) { + grottDevices.addAll(devices); + } + } else { + throw new JsonSyntaxException("Unsupported element type"); + } + } catch (JsonSyntaxException e) { + logger.debug("onContent() error parsing JSON '{}'", json, e); + return; + } + getThing().getThings().stream().map(thing -> thing.getHandler()) + .filter(handler -> (handler instanceof GrowattInverterHandler)) + .forEach(handler -> ((GrowattInverterHandler) handler).handleGrottDevices(grottDevices)); + } + + @Override + public void initialize() { + try { + httpService.registerServlet(GROWATT_SERVLET_PATH_ALIAS, new GrottServlet(), null, null); + updateStatus(ThingStatus.ONLINE); + } catch (ServletException | NamespaceException e) { + logger.debug("serverStart() exception '{}'", e.getMessage(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + } + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java new file mode 100644 index 0000000000000..2ebcea0c342ac --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.handler; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.growatt.internal.GrowattBindingConstants; +import org.openhab.binding.growatt.internal.GrowattBindingConstants.UoM; +import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; +import org.openhab.binding.growatt.internal.dto.GrottDevice; +import org.openhab.binding.growatt.internal.dto.GrottValues; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link GrowattInverterHandler} is a thing handler for Growatt inverters. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattInverterHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(GrowattInverterHandler.class); + + private String deviceId = "unknown"; + + public GrowattInverterHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // everything is read only so do nothing + } + + /** + * Receives a GrottDevice object containing data for this thing. Process the respective data values and update the + * channels accordingly. + * + * @param grottDevice a GrottDevice object containing the new status values. + */ + public void handleGrottDevice(GrottDevice grottDevice) { + GrottValues grottValues = grottDevice.getValues(); + if (grottValues == null) { + logger.debug("handleValues() device '{}' contained no values", grottDevice.getDeviceId()); + return; + } + + Map channelStates = new HashMap<>(); + List missingFields = new ArrayList<>(); + + // read channel states from DTO + for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { + String channelId = entry.getKey(); + Field field; + try { + field = GrottValues.class.getField(channelId); + } catch (NoSuchFieldException e) { + missingFields.add(channelId); + continue; + } catch (SecurityException e) { + logger.debug("handleValues() security exception field '{}'", channelId); + continue; + } + Object value; + try { + value = field.get(grottValues); + } catch (IllegalArgumentException | IllegalAccessException e) { + logger.debug("handleValues() error reading field '{}' value", channelId); + continue; + } + if (value != null && (value instanceof Integer)) { + UoM uom = entry.getValue(); + channelStates.put(channelId, + QuantityType.valueOf(((Integer) value).doubleValue() / uom.divisor, uom.units)); + } + } + + // warn if fields missing from DTO + if (!missingFields.isEmpty() && logger.isWarnEnabled()) { + logger.warn("handleValues() please notify maintainers: GrottValues.class is missing fields: {}", + missingFields.stream().collect(Collectors.joining(","))); + } + + // remove unused channels + List unusedChannels = thing.getChannels().stream() + .filter(channel -> !channelStates.containsKey(channel.getUID().getId())).collect(Collectors.toList()); + if (!unusedChannels.isEmpty()) { + updateThing(editThing().withoutChannels(unusedChannels).build()); + logger.debug("handleValues() removed {} unused channels", unusedChannels.size()); + } + + // update channel states + List channelIds = thing.getChannels().stream().map(channel -> channel.getUID().getId()) + .collect(Collectors.toList()); + channelStates.forEach((channelId, state) -> { + if (channelIds.contains(channelId)) { + updateState(channelId, state); + } else { + logger.debug("handleValues() channel '{}' not implemented in thing", channelId); + } + }); + } + + /** + * Receives a list of GrottDevice objects containing potential data for this thing. If the list contains any entry + * matching the things's deviceId then process it further. Otherwise go offline with a configuration error. + * + * @param grottDevices list of GrottDevice objects. + */ + public void handleGrottDevices(List grottDevices) { + grottDevices.stream().filter(grottDevice -> deviceId.equals(grottDevice.getDeviceId())).findAny() + .ifPresentOrElse(grottDevice -> { + updateStatus(ThingStatus.ONLINE); + handleGrottDevice(grottDevice); + }, () -> { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + }); + } + + @Override + public void initialize() { + GrowattInverterConfiguration config = getConfigAs(GrowattInverterConfiguration.class); + deviceId = config.deviceId; + updateStatus(ThingStatus.UNKNOWN); + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 97d23be7aec24..5df6cf461c046 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,45 +4,233 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - - + + + + Bridge Thing for Growatt Binding + - - Sample thing for growatt Binding + + + + + + + Inverter Thing for Growatt Binding + + - + + + + Total solar input power. + + + + Voltage from solar panel string #1. + + + + Current from solar panel string #1. + + + + Power from solar panel string #1. + + + + Voltage of solar panel string #2. + + + + Current from solar panel string #2. + + + + Power from solar panel string #2. + + + + Total solar output power. + + + + + Voltage of the grid supply. + + + + Current delivered to the grid supply. + + + + Power delivered to the grid supply. + + + + Voltage of the grid supply #2. + + + + Current delivered to the grid supply #2. + + + + Power delivered to the grid supply #2. + + + + Voltage of the grid supply #3. + + + + Current delivered to the grid supply #3. + + + + Power delivered to the grid supply #3. + + + + Solar energy collected today. + + + + Total solar energy collected. + + + + + Energy from solar panel string #1 today. + + + + Total energy from solar panel string #1. + + + + Energy from solar panel string #2 today. + + + + Total energy from solar panel string #2. + + + + Total energy from all solar panels. + + + + Temperature of the solar panels. + + + + Temperature of the IPM. + + + + Boost temperature. + + + + Temperature #4. + + + + AC voltage R-S. + + + + AC voltage S-T. + + + + AC voltage T-R. + + + + Battery voltage DSP. + + + + P Bus voltage. + + + + N Bus voltage. + + + + AC charge energy today. + + + + Total AC charge energy. + + + + Battery discharge energy today. + + + + Total battery discharge energy. + + + + AC discharge energy today. + + + + Total AC discharge energy. + + + + AC charge current. + + + + AC discharge power. + + + + AC discharge VA. + + + + Battery discharge power. + + + + Battery VA. + + + + Battery power. + - - network-address - - Hostname or IP address of the device - - - password - - Password to access the device - - - - Interval the device is polled in sec. - 600 - true + + + Id of the inverter. - - - Number:Temperature - - Sample channel for growatt Binding + + + Number:Dimensionless + + Status code of the inverter (0=ready, 1=online, 2=fault). + + + + Number:Frequency + + Frequency of the grid supply. + + + diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java new file mode 100644 index 0000000000000..03441142bf01f --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.growatt.internal.GrowattBindingConstants; +import org.openhab.binding.growatt.internal.GrowattBindingConstants.UoM; +import org.openhab.binding.growatt.internal.dto.GrottDevice; +import org.openhab.binding.growatt.internal.dto.GrottValues; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; + +import com.google.gson.Gson; + +/** + * The {@link GrowattTest} is a JUnit test suite for the Growatt binding. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattTest { + + private final Gson gson = new Gson(); + + /** + * load a string from a file + */ + private String load(String fileName) { + try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName)); + BufferedReader reader = new BufferedReader(file)) { + StringBuilder builder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + builder.append(line).append("\n"); + } + return builder.toString(); + } catch (IOException e) { + fail(e.getMessage()); + } + return ""; + } + + @Test + void testCreateGrottDeviceFromJson() { + String json = load("simple"); + GrottDevice device = gson.fromJson(json, GrottDevice.class); + + assertNotNull(device); + GrottValues grottValues = device.getValues(); + assertNotNull(grottValues); + + Map channelStates = new HashMap<>(); + for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { + String channelId = entry.getKey(); + Field field; + try { + field = GrottValues.class.getField(channelId); + } catch (NoSuchFieldException e) { + fail(e.getMessage()); + continue; + } catch (SecurityException e) { + fail(e.getMessage()); + continue; + } + Object value; + try { + value = field.get(grottValues); + } catch (IllegalArgumentException | IllegalAccessException e) { + fail(e.getMessage()); + continue; + } + if (value != null && (value instanceof Integer)) { + UoM uom = entry.getValue(); + channelStates.put(channelId, + QuantityType.valueOf(((Integer) value).doubleValue() / uom.divisor, uom.units)); + } + } + + assertEquals(29, channelStates.size()); + + channelStates.forEach((channelId, state) -> { + assertTrue(state instanceof QuantityType); + }); + + assertEquals(QuantityType.ONE, channelStates.get("pvstatus")); + assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("pvgridvoltage")); + assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("pvgridcurrent")); + assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("pvgridpower")); + assertEquals(QuantityType.valueOf(49.97, Units.HERTZ), channelStates.get("pvfrequency")); + assertEquals(QuantityType.valueOf(32751939, Units.SECOND), channelStates.get("totworktime")); + assertEquals(QuantityType.valueOf(27.3, SIUnits.CELSIUS), channelStates.get("pvtemperature")); + assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("epvtotal")); + + assertNull(channelStates.get("BatWatt")); + } +} diff --git a/bundles/org.openhab.binding.growatt/src/test/resources/simple.json b/bundles/org.openhab.binding.growatt/src/test/resources/simple.json new file mode 100644 index 0000000000000..22b5e4f143809 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/test/resources/simple.json @@ -0,0 +1,36 @@ +{ + "device": "INVERTID", + "time": "2021-02-13T16:34:28", + "buffered": "no", + "values": { + "pvstatus": 1, + "pvpowerin": 1622, + "pv1voltage": 2475, + "pv1current": 6, + "pv1watt": 1622, + "pv2voltage": 0, + "pv2current": 0, + "pv2watt": 0, + "pvpowerout": 1460, + "pvfrequentie": 4997, + "pvgridvoltage": 2353, + "pvgridcurrent": 7, + "pvgridpower": 1460, + "pvgridvoltage2": 0, + "pvgridcurrent2": 0, + "pvgridpower2": 0, + "pvgridvoltage3": 0, + "pvgridcurrent3": 0, + "pvgridpower3": 0, + "pvenergytoday": 87, + "pvenergytotal": 43265, + "totworktime": 65503878, + "pvtemperature": 273, + "pvipmtemperature": 0, + "epv1today": 90, + "epv1total": 45453, + "epv2today": 0, + "epv2total": 0, + "epvtotal": 45453 + } +} From 9a5688d4ebc5b4659d87249597ae1ec0be73a8c8 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 16 Jun 2023 17:45:49 +0100 Subject: [PATCH 003/146] [growatt] fix doc typos Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index ffe7aa9220d25..741c1ec05f448 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -19,9 +19,9 @@ There is no automatic discovery of the bridge or inverter things. ## Grott Application The Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. -It intercepts and decoded the data packets sent from the inverter to the cloud server. +It intercepts and decodes the data packets sent from the inverter to the cloud server. And it uses the `grottext.py` application extension to send a copy of the intercepted data also to your OpenHAB system. -The data is transmitted via an HTTP POST to the 'http://:8080/growatt' end point with a JSON pay-load. +The data is transmitted via an HTTP POST to the 'http://openhab-ip-address:8080/growatt' end point with a JSON pay-load. You need to install the Grott application either on the same computer as OpenHAB or on another computer. From 4d472b57532d0d6ee58b4bc257ce94cd2f7fa0f8 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 16 Jun 2023 17:59:03 +0100 Subject: [PATCH 004/146] [growatt] fix logger prefixes Signed-off-by: Andrew Fiddian-Green --- .../internal/handler/GrowattBridgeHandler.java | 6 +++--- .../internal/handler/GrowattInverterHandler.java | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 2d1e670b5ea80..611a371274c68 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -95,7 +95,7 @@ protected void handleGrottContent(String json) { try { jsonElement = JsonParser.parseString(json); } catch (JsonSyntaxException e) { - logger.debug("onContent() invalid JSON string '{}'", json, e); + logger.debug("handleGrottContent() invalid JSON string '{}'", json, e); return; } List grottDevices = new ArrayList<>(); @@ -114,7 +114,7 @@ protected void handleGrottContent(String json) { throw new JsonSyntaxException("Unsupported element type"); } } catch (JsonSyntaxException e) { - logger.debug("onContent() error parsing JSON '{}'", json, e); + logger.debug("handleGrottContent() error parsing JSON '{}'", json, e); return; } getThing().getThings().stream().map(thing -> thing.getHandler()) @@ -128,7 +128,7 @@ public void initialize() { httpService.registerServlet(GROWATT_SERVLET_PATH_ALIAS, new GrottServlet(), null, null); updateStatus(ThingStatus.ONLINE); } catch (ServletException | NamespaceException e) { - logger.debug("serverStart() exception '{}'", e.getMessage(), e); + logger.debug("initialize() exception '{}'", e.getMessage(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 2ebcea0c342ac..24ed95b5f865e 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -68,7 +68,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { public void handleGrottDevice(GrottDevice grottDevice) { GrottValues grottValues = grottDevice.getValues(); if (grottValues == null) { - logger.debug("handleValues() device '{}' contained no values", grottDevice.getDeviceId()); + logger.debug("handleGrottDevice() device '{}' contained no values", grottDevice.getDeviceId()); return; } @@ -85,14 +85,14 @@ public void handleGrottDevice(GrottDevice grottDevice) { missingFields.add(channelId); continue; } catch (SecurityException e) { - logger.debug("handleValues() security exception field '{}'", channelId); + logger.debug("handleGrottDevice() security exception field '{}'", channelId); continue; } Object value; try { value = field.get(grottValues); } catch (IllegalArgumentException | IllegalAccessException e) { - logger.debug("handleValues() error reading field '{}' value", channelId); + logger.debug("handleGrottDevice() error reading field '{}' value", channelId); continue; } if (value != null && (value instanceof Integer)) { @@ -104,7 +104,7 @@ public void handleGrottDevice(GrottDevice grottDevice) { // warn if fields missing from DTO if (!missingFields.isEmpty() && logger.isWarnEnabled()) { - logger.warn("handleValues() please notify maintainers: GrottValues.class is missing fields: {}", + logger.warn("handleGrottDevice() please notify maintainers: GrottValues.class is missing fields: {}", missingFields.stream().collect(Collectors.joining(","))); } @@ -113,7 +113,7 @@ public void handleGrottDevice(GrottDevice grottDevice) { .filter(channel -> !channelStates.containsKey(channel.getUID().getId())).collect(Collectors.toList()); if (!unusedChannels.isEmpty()) { updateThing(editThing().withoutChannels(unusedChannels).build()); - logger.debug("handleValues() removed {} unused channels", unusedChannels.size()); + logger.debug("handleGrottDevice() removed {} unused channels", unusedChannels.size()); } // update channel states @@ -123,7 +123,7 @@ public void handleGrottDevice(GrottDevice grottDevice) { if (channelIds.contains(channelId)) { updateState(channelId, state); } else { - logger.debug("handleValues() channel '{}' not implemented in thing", channelId); + logger.debug("handleGrottDevice() channel '{}' not implemented in thing", channelId); } }); } From aa49c9d81694235e0b5236a6f497460c28752dc7 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 19 Jun 2023 16:42:19 +0100 Subject: [PATCH 005/146] [growatt] normalise channels Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 150 +++-- .../internal/GrowattBindingConstants.java | 206 +++++-- .../growatt/internal/dto/GrottDevice.java | 5 +- .../growatt/internal/dto/GrottValues.java | 257 +++++++-- .../handler/GrowattBridgeHandler.java | 19 + .../handler/GrowattInverterHandler.java | 70 +-- .../resources/OH-INF/thing/thing-types.xml | 517 +++++++++++++----- .../binding/growatt/test/GrowattTest.java | 96 +++- 8 files changed, 973 insertions(+), 347 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 741c1ec05f448..c779291bdf7e0 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -62,57 +62,105 @@ All channels are read only. Depending on the inverter model, and it configuration, not all of the channels will be present. The list of all possible channels is as follows: -| Channel | Type | Description | -|--------------------|--------------------|-----------------------------------------------------| -| pvstatus | Number | Status of the inverter (0=ready, 1=online, 2=fault) | -| pvpowerin | Number:power | Total solar input power. | -| pv1voltage | Number:voltage | Voltage from solar panel string #1. | -| pv1current | Number:current | Current from solar panel string #1. | -| pv1watt | Number:power | Power from solar panel string #1. | -| pv2voltage | Number:voltage | Voltage of solar panel string #2. | -| pv2current | Number:current | Current from solar panel string #2. | -| pv2watt | Number:power | Power from solar panel string #2. | -| pvpowerout | Number:power | Total solar output power. | -| pvfrequency | Number:frequency | Frequency of the grid supply. | -| pvgridvoltage | Number:voltage | Voltage of the grid supply. | -| pvgridcurrent | Number:current | Current delivered to the grid supply. | -| pvgridpower | Number:power | Power delivered to the grid supply. | -| pvgridvoltage2 | Number:voltage | Voltage of the grid supply #2. | -| pvgridcurrent2 | Number:current | Current delivered to the grid supply #2. | -| pvgridpower2 | Number:power | Power delivered to the grid supply #2. | -| pvgridvoltage3 | Number:voltage | Voltage of the grid supply #3. | -| pvgridcurrent3 | Number:current | Current delivered to the grid supply #3. | -| pvgridpower3 | Number:power | Power delivered to the grid supply #3. | -| pvenergytoday | Number:energy | Solar energy collected today. | -| pvenergytotal | Number:energy | Total solar energy collected. | -| totworktime | Number:time | Total uptime of the inverter. | -| epv1today | Number:energy | Energy from solar panel string #1 today. | -| epv1total | Number:energy | Total energy from solar panel string #1. | -| epv2today | Number:energy | Energy from solar panel string #2 today. | -| epv2total | Number:energy | Total energy from solar panel string #2. | -| epvtotal | Number:energy | Total energy from all solar panels. | -| pvtemperature | Number:temperature | Temperature of the solar panels. | -| pvipmtemperature | Number:temperature | Temperature of the IPM. | -| pvboosttemperature | Number:temperature | Boost temperature. | -| temp4 | Number:temperature | Temperature #4. | -| Vac_RS | Number:voltage | AC voltage R-S. | -| Vac_ST | Number:voltage | AC voltage S-T. | -| Vac_TR | Number:voltage | AC voltage T-R. | -| uwBatVolt_DSP | Number:voltage | Battery voltage DSP. | -| pbusvolt | Number:voltage | P Bus voltage. | -| nbusvolt | Number:voltage | N Bus voltage. | -| eacCharToday | Number:energy | AC charge energy today. | -| eacCharTotal | Number:energy | Total AC charge energy. | -| ebatDischarToday | Number:energy | Battery discharge energy today. | -| ebatDischarTotal | Number:energy | Total battery discharge energy. | -| eacDischarToday | Number:energy | AC discharge energy today. | -| eacDischarTotal | Number:energy | Total AC discharge energy. | -| ACCharCurr | Number:current | AC charge current. | -| ACDischarWatt | Number:power | AC discharge power. | -| ACDischarVA | Number:va | AC discharge VA. | -| BatDischarWatt | Number:power | Battery discharge power. | -| BatDischarVA | Number:va | Battery VA. | -| BatWatt | Number:power | Battery power. | +| Channel | Type | Description | +|--------------------------------- |-------------------------------|-----------------------------------------------------------| +| status | Number:Dimensionless | Status code of the inverter (0=ready, 1=online, 2=fault). | +| pv-power-in | Number:Power | Total solar input power. | +| pv-power-out | Number:Power | Total solar output power. | +| pv1-potential | Number:ElectricPotential | Voltage from solar panel string #1. | +| pv2-potential | Number:ElectricPotential | Voltage of solar panel string #2. | +| pv1-current | Number:ElectricCurrent | Current from solar panel string #1. | +| pv2-current | Number:ElectricCurrent | Current from solar panel string #2. | +| pv1-power | Number:Power | Power from solar panel string #1. | +| pv2-power | Number:Power | Power from solar panel string #2. | +| grid-frequency | Number:Frequency | Frequency of the grid. | +| grid-potential | Number:ElectricPotential | Voltage of the grid (phase #R). | +| grid-potential-s | Number:ElectricPotential | Voltage of the grid phase #S. | +| grid-potential-t | Number:ElectricPotential | Voltage of the grid phase #T. | +| grid-potential-rs | Number:ElectricPotential | Voltage of the grid phases #RS. | +| grid-potential-st | Number:ElectricPotential | Voltage of the grid phases #ST. | +| grid-potential-tr | Number:ElectricPotential | Voltage of the grid phases #TR. | +| grid-current | Number:ElectricCurrent | Current delivered to the grid (phase #R). | +| grid-current-s | Number:ElectricCurrent | Current delivered to the grid phase #S. | +| grid-current-t | Number:ElectricCurrent | Current delivered to the grid phase #T. | +| grid-power | Number:Power | Power delivered to the grid (phase #R). | +| grid-power-s | Number:Power | Power delivered to the grid phase #S. | +| grid-power-t | Number:Power | Power delivered to the grid phase #T. | +| grid-va | Number:Power | VA delivered to the grid. | +| grid-charge-current | Number:ElectricCurrent | Grid current to charge battery. | +| grid-charge-power | Number:Power | Grid power to charge battery. | +| grid-charge-va | Number:Power | Grid VA to charge battery. | +| grid-discharge-power | Number:Power | Grid power from discharge of battery. | +| grid-discharge-va | Number:Power | Grid VA from discharge of battery. | +| battery-charge-power | Number:Power | Battery charge power. | +| battery-discharge-power | Number:Power | Battery discharge power. | +| battery-discharge-va | Number:Power | Battery discharge VA. | +| to-grid-power | Number:Power | Power supplied to grid. | +| to-grid-power-r | Number:Power | Power supplied to grid phase #R. | +| to-grid-power-s | Number:Power | Power supplied to grid phase #S. | +| to-grid-power-t | Number:Power | Power supplied to grid phase #T. | +| to-user-power | Number:Power | Power supplied to user. | +| to-user-power-r | Number:Power | Power supplied to user phase #R. | +| to-user-power-s | Number:Power | Power supplied to user phase #S. | +| to-user-power-t | Number:Power | Power supplied to user phase #T. | +| to-local-power | Number:Power | Power supplied to local. | +| to-local-power-r | Number:Power | Power supplied to local phase #R. | +| to-local-power-s | Number:Power | Power supplied to local phase #S. | +| to-local-power-t | Number:Power | Power supplied to local phase #T. | +| pv-energy-today | Number:Energy | Solar energy collected today. | +| pv-energy-total | Number:Energy | Total solar energy collected. | +| pv-grid-energy-today | Number:Energy | Solar energy supplied to grid today. | +| pv1-grid-energy-today | Number:Energy | Solar energy supplied by string #1 to grid today. | +| pv2-grid-energy-today | Number:Energy | Solar energy supplied by string #2 to grid today. | +| pv-grid-energy-total | Number:Energy | Total solar energy supplied to grid. | +| pv1-grid-energy-total | Number:Energy | Total solar energy supplied by string #1 to grid . | +| pv2-grid-energy-total | Number:Energy | Total solar energy supplied by string #2 to grid. | +| to-grid-energy-today | Number:Energy | Energy supplied to grid today. | +| to-grid-energy-total | Number:Energy | Total energy supplied to grid. | +| to-user-energy-today | Number:Energy | Energy supplied to user today. | +| to-user-energy-total | Number:Energy | Total energy supplied to user. | +| to-local-energy-today | Number:Energy | Energy supplied to local today. | +| to-local-energy-total | Number:Energy | Total energy supplied to local. | +| grid-charge-energy-today | Number:Energy | Energy used to charge battery today. | +| grid-charge-energy-total | Number:Energy | Total energy used to charge battery. | +| grid-discharge-energy-today | Number:Energy | Grid energy produced from battery today. | +| grid-discharge-energy-total | Number:Energy | Total grid energy produced from battery. | +| battery-discharge-energy-today | Number:Energy | Energy consumed from battery. | +| battery-discharge-energy-total | Number:Energy | Total energy consumed from battery. | +| total-work-time | Number:Time | Total work time of the system. | +| p-bus-potential | Number:ElectricPotential | P Bus voltage. | +| n-bus-potential | Number:ElectricPotential | N Bus voltage. | +| sp-bus-potential | Number:ElectricPotential | N Bus voltage. | +| pv-temperature | Number:Temperature | Temperature of the solar panels (string #1). | +| pv-ipm-temperature | Number:Temperature | Temperature of the IPM. | +| pv-boost-temperature | Number:Temperature | Boost temperature. | +| temperature-4 | Number:Temperature | Temperature #4. | +| pv2-temperature | Number:Temperature | Temperature of the solar panels (string #2). | +| battery-type | Number:Dimensionless | Type code of the battery. | +| battery-temperature | Number:Temperature | Battery temperature. | +| battery-potential | Number:ElectricPotential | Battery voltage. | +| battery-display | Number:Dimensionless | Battery display code. | +| battery-soc | Number:Dimensionless | Battery SOC code. | +| system-fault-0 | Number:Dimensionless | System fault code #0. | +| system-fault-1 | Number:Dimensionless | System fault code #1. | +| system-fault-2 | Number:Dimensionless | System fault code #2. | +| system-fault-3 | Number:Dimensionless | System fault code #3. | +| system-fault-4 | Number:Dimensionless | System fault code #4. | +| system-fault-5 | Number:Dimensionless | System fault code #5. | +| system-fault-6 | Number:Dimensionless | System fault code #6. | +| system-fault-7 | Number:Dimensionless | System fault code #7. | +| system-work-mode | Number:Dimensionless | System work mode code. | +| sp-display-status | Number:Dimensionless | Solar panel display status code. | +| constant-power-ok | Number:Dimensionless | Constant power OK code. | +| rac | Number:Dimensionless | RAC code. | +| erac-today | Number:Dimensionless | ERAC count today. | +| erac-total | Number:Dimensionless | Total ERAC count. | +| output-potential | Number:ElectricPotential | Output voltage. | +| output-frequency | Number:Frequency | Output frequency. | +| load-percent | Number:Dimensionless | Percent of full load. | +| inverter-current | Number:ElectricCurrent | Inverter current. | +| grid-input-power | Number:Power | Grid input power. | +| grid-input-va | Number:Power | Grid input VA. | ## Full Example diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java index 9e6588ad5b731..8912fe5b6e3e4 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -56,55 +56,163 @@ public UoM(Unit units, float divisor) { * Map of supported channel ids and respective UoM */ public static final Map CHANNEL_ID_UOM_MAP = Map.ofEntries( - new AbstractMap.SimpleEntry("pvstatus", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("pvpowerin", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pv1voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pv1current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("pv1watt", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pv2voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pv2current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("pv2watt", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pvpowerout", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pvfrequency", new UoM(Units.HERTZ, 100)), - new AbstractMap.SimpleEntry("pvgridvoltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pvgridcurrent", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("pvgridpower", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pvgridvoltage2", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pvgridcurrent2", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("pvgridpower2", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pvgridvoltage3", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pvgridcurrent3", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("pvgridpower3", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pvenergytoday", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pvenergytotal", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("totworktime", new UoM(Units.SECOND, 2)), - new AbstractMap.SimpleEntry("epv1today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("epv1total", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("epv2today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("epv2total", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("epvtotal", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pvtemperature", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("pvipmtemperature", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("pvboosttemperature", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("temp4", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("Vac_RS", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("Vac_ST", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("Vac_TR", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("uwBatVolt_DSP", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pbusvolt", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("nbusvolt", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("eacCharToday", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("eacCharTotal", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("ebatDischarToday", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("ebatDischarTotal", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("eacDischarToday", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("eacDischarTotal", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("ACCharCurr", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("ACDischarWatt", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("ACDischarVA", new UoM(Units.VOLT_AMPERE, 10)), - new AbstractMap.SimpleEntry("BatDischarWatt", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("BatDischarVA", new UoM(Units.VOLT_AMPERE, 10)), - new AbstractMap.SimpleEntry("BatWatt", new UoM(Units.WATT, 10)) + // inverter state + new AbstractMap.SimpleEntry("status", new UoM(Units.ONE, 1)), + + // solar generation + new AbstractMap.SimpleEntry("pv-power-in", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("pv-power-out", new UoM(Units.WATT, 10)), + + // electric data for strings #1 and #2 + new AbstractMap.SimpleEntry("pv1-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv1-current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pv1-power", new UoM(Units.WATT, 10)), + + new AbstractMap.SimpleEntry("pv2-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv2-current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pv2-power", new UoM(Units.WATT, 10)), + + // grid electric data (1-phase resp. 3-phase) + new AbstractMap.SimpleEntry("grid-frequency", new UoM(Units.HERTZ, 100)), + + new AbstractMap.SimpleEntry("grid-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-potential-2", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-potential-3", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-potential-rs", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-potential-st", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-potential-tr", new UoM(Units.VOLT, 10)), + + // solar power to grid + new AbstractMap.SimpleEntry("grid-current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("grid-current-2", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("grid-current-3", new UoM(Units.AMPERE, 10)), + + new AbstractMap.SimpleEntry("grid-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("grid-power-2", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("grid-power-3", new UoM(Units.WATT, 10)), + + new AbstractMap.SimpleEntry("grid-va", new UoM(Units.VOLT_AMPERE, 10)), + + // grid power to battery + new AbstractMap.SimpleEntry("grid-charge-current", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("grid-charge-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("grid-charge-va", new UoM(Units.VOLT_AMPERE, 10)), + + // grid power from battery + new AbstractMap.SimpleEntry("grid-discharge-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("grid-discharge-va", new UoM(Units.VOLT_AMPERE, 10)), + + // battery discharge / charge power + new AbstractMap.SimpleEntry("battery-charge-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("battery-discharge-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("battery-discharge-va", new UoM(Units.VOLT_AMPERE, 10)), + + // power to grid + new AbstractMap.SimpleEntry("to-grid-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-grid-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-grid-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-grid-power-t", new UoM(Units.WATT, 10)), + + // power to user + new AbstractMap.SimpleEntry("to-user-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-user-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-user-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-user-power-t", new UoM(Units.WATT, 10)), + + // power to local + new AbstractMap.SimpleEntry("to-local-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-local-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-local-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("to-local-power-t", new UoM(Units.WATT, 10)), + + // pv energy + new AbstractMap.SimpleEntry("pv-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy taken from solar + new AbstractMap.SimpleEntry("pv-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv1-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv2-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + + new AbstractMap.SimpleEntry("pv-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv1-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv2-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy supplied to grid + new AbstractMap.SimpleEntry("to-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("to-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy supplied to user + new AbstractMap.SimpleEntry("to-user-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("to-user-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy supplied to local + new AbstractMap.SimpleEntry("to-local-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("to-local-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy taken from grid to charge + new AbstractMap.SimpleEntry("grid-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("grid-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy supplied to grid from discharge of battery + new AbstractMap.SimpleEntry("grid-discharge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("grid-discharge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy taken from battery + new AbstractMap.SimpleEntry("battery-discharge-energy-today", + new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("battery-discharge-energy-total", + new UoM(Units.KILOWATT_HOUR, 10)), + + // inverter up time + new AbstractMap.SimpleEntry("total-work-time", new UoM(Units.HOUR, 7200)), + + // bus voltages + new AbstractMap.SimpleEntry("p-bus-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("n-bus-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("sp-bus-potential", new UoM(Units.VOLT, 10)), + + // temperatures + new AbstractMap.SimpleEntry("pv-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pv-ipm-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pv-boost-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("temperature-4", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pv2-temperature", new UoM(SIUnits.CELSIUS, 10)), + + // battery data + new AbstractMap.SimpleEntry("battery-type", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("battery-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("battery-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("battery-display", new UoM(Units.ONE, 10)), + new AbstractMap.SimpleEntry("battery-soc", new UoM(Units.ONE, 100)), + + // fault codes + new AbstractMap.SimpleEntry("system-fault-0", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-1", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-2", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-3", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-4", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-5", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-6", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-7", new UoM(Units.ONE, 1)), + + // miscellaneous + new AbstractMap.SimpleEntry("system-work-mode", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("sp-display-status", new UoM(Units.ONE, 10)), + new AbstractMap.SimpleEntry("constant-power-ok", new UoM(Units.ONE, 1)), + + // rac ?? + new AbstractMap.SimpleEntry("rac", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("erac-today", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("erac-total", new UoM(Units.ONE, 1)), + + // duplicates ?? + new AbstractMap.SimpleEntry("output-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("output-frequency", new UoM(Units.HERTZ, 100)), + new AbstractMap.SimpleEntry("load-percent", new UoM(Units.ONE, 10)), + new AbstractMap.SimpleEntry("inverter-current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("grid-input-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("grid-input-va", new UoM(Units.VOLT_AMPERE, 10)) // ); } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java index f473cc8f37720..2a3d4beeecbfb 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java @@ -33,11 +33,12 @@ public class GrottDevice { public static final Type GROTT_DEVICE_ARRAY = new TypeToken>() {}.getType(); // @formatter:on - private @SerializedName("device") String deviceId = ""; + private @Nullable @SerializedName("device") String deviceId; private @Nullable GrottValues values; public String getDeviceId() { - return deviceId; + String deviceId = this.deviceId; + return deviceId != null ? deviceId : "unknown"; } public @Nullable GrottValues getValues() { diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 4518d6ca220f3..254d95ed8cb01 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -12,8 +12,16 @@ */ package org.openhab.binding.growatt.internal.dto; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.growatt.internal.GrowattBindingConstants; +import org.openhab.binding.growatt.internal.GrowattBindingConstants.UoM; +import org.openhab.core.library.types.QuantityType; import com.google.gson.annotations.SerializedName; @@ -24,53 +32,204 @@ */ @NonNullByDefault public class GrottValues { - public @Nullable Integer pvstatus; - public @Nullable Integer pvpowerin; - public @Nullable Integer pv1voltage; - public @Nullable Integer pv1current; - public @Nullable Integer pv1watt; - public @Nullable Integer pv2voltage; - public @Nullable Integer pv2current; - public @Nullable Integer pv2watt; - public @Nullable Integer pvpowerout; - public @Nullable @SerializedName("pvfrequentie") Integer pvfrequency; - public @Nullable Integer pvgridvoltage; - public @Nullable Integer pvgridcurrent; - public @Nullable Integer pvgridpower; - public @Nullable Integer pvgridvoltage2; - public @Nullable Integer pvgridcurrent2; - public @Nullable Integer pvgridpower2; - public @Nullable Integer pvgridvoltage3; - public @Nullable Integer pvgridcurrent3; - public @Nullable Integer pvgridpower3; - public @Nullable Integer pvenergytoday; - public @Nullable Integer pvenergytotal; - public @Nullable Integer totworktime; - public @Nullable Integer epv1today; - public @Nullable Integer epv1total; - public @Nullable Integer epv2today; - public @Nullable Integer epv2total; - public @Nullable Integer epvtotal; - public @Nullable Integer pvtemperature; - public @Nullable Integer pvipmtemperature; - public @Nullable @SerializedName("pvboottemperature") Integer pvboosttemperature; - public @Nullable Integer temp4; - public @Nullable Integer Vac_RS; - public @Nullable Integer Vac_ST; - public @Nullable Integer Vac_TR; - public @Nullable Integer uwBatVolt_DSP; - public @Nullable Integer pbusvolt; - public @Nullable Integer nbusvolt; - public @Nullable Integer eacCharToday; - public @Nullable Integer eacCharTotal; - public @Nullable Integer ebatDischarToday; - public @Nullable Integer ebatDischarTotal; - public @Nullable Integer eacDischarToday; - public @Nullable Integer eacDischarTotal; - public @Nullable Integer ACCharCurr; - public @Nullable Integer ACDischarWatt; - public @Nullable Integer ACDischarVA; - public @Nullable Integer BatDischarWatt; - public @Nullable Integer BatDischarVA; - public @Nullable Integer BatWatt; + + /** + * Convert Java field name to openHAB channel id + */ + public static String getChannelId(String fieldName) { + return fieldName.replace("_", "-"); + } + + /** + * Convert openHAB channel id to Java field name + */ + public static String getFieldName(String channelId) { + return channelId.replace("-", "_"); + } + + // @formatter:off + + // inverter state + public @Nullable @SerializedName(value = "pvstatus") Integer status; + + // solar generation + public @Nullable @SerializedName(value = "pvpowerin") Integer pv_power_in; + public @Nullable @SerializedName(value = "pvpowerout") Integer pv_power_out; + + // electric data for strings #1 and #2 + public @Nullable @SerializedName(value = "pv1voltage", alternate = { "vpv1" }) Integer pv1_potential; + public @Nullable @SerializedName(value = "pv1current", alternate = { "buck1curr" }) Integer pv1_current; + public @Nullable @SerializedName(value = "pv1watt", alternate = { "ppv1" }) Integer pv1_power; + + public @Nullable @SerializedName(value = "pv2voltage", alternate = { "vpv2" }) Integer pv2_potential; + public @Nullable @SerializedName(value = "pv2current", alternate = { "buck2curr" }) Integer pv2_current; + public @Nullable @SerializedName(value = "pv2watt", alternate = { "ppv2" }) Integer pv2_power; + + // grid electric data (1-phase resp. 3-phase) + public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq" }) Integer grid_frequency; + + public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt" }) Integer grid_potential; + public @Nullable @SerializedName(value = "pvgridvoltage2") Integer grid_potential_2; + public @Nullable @SerializedName(value = "pvgridvoltage3") Integer grid_potential_3; + public @Nullable @SerializedName(value = "Vac_RS") Integer grid_potential_rs; + public @Nullable @SerializedName(value = "Vac_ST") Integer grid_potential_st; + public @Nullable @SerializedName(value = "Vac_TR") Integer grid_potential_tr; + + // solar power to grid + public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr" }) Integer grid_current; + public @Nullable @SerializedName(value = "pvgridcurrent2") Integer grid_current_2; + public @Nullable @SerializedName(value = "pvgridcurrent3") Integer grid_current_3; + + public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt" }) Integer grid_power; + public @Nullable @SerializedName(value = "pvgridpower2") Integer grid_power_2; + public @Nullable @SerializedName(value = "pvgridpower3") Integer grid_power_3; + + public @Nullable @SerializedName(value = "op_va") Integer grid_va; + + // grid power to battery + public @Nullable @SerializedName(value = "ACCharCurr") Integer grid_charge_current; + public @Nullable @SerializedName(value = "acchr_watt") Integer grid_charge_power; + public @Nullable @SerializedName(value = "acchar_VA") Integer grid_charge_va; + + // grid power from battery + // TODO current ?? + public @Nullable @SerializedName(value = "ACDischarWatt") Integer grid_discharge_power; + public @Nullable @SerializedName(value = "ACDischarVA") Integer grid_discharge_va; + + // battery discharge / charge power + // TODO current ?? + public @Nullable @SerializedName(value = "p1charge1", alternate = { "BatWatt" }) Integer battery_charge_power; + public @Nullable @SerializedName(value = "pdischarge1", alternate = { "BatDischarWatt" }) Integer battery_discharge_power; + public @Nullable @SerializedName(value = "BatDischarVA") Integer battery_discharge_va; + + // power to grid + public @Nullable @SerializedName(value = "pacttogridtotal") Integer to_grid_power; + public @Nullable @SerializedName(value = "pacttogridr") Integer to_grid_power_r; + public @Nullable @SerializedName(value = "pacttogrids") Integer to_grid_power_s; + public @Nullable @SerializedName(value = "pacttogridt") Integer to_grid_power_t; + + // power to user + public @Nullable @SerializedName(value = "pacttousertotal") Integer to_user_power; + public @Nullable @SerializedName(value = "pacttouserr") Integer to_user_power_r; + public @Nullable @SerializedName(value = "pacttousers") Integer to_user_power_s; + public @Nullable @SerializedName(value = "pacttousert") Integer to_user_power_t; + + // power to local + public @Nullable @SerializedName(value = "plocalloadtotal") Integer to_local_power; + public @Nullable @SerializedName(value = "plocalloadr") Integer to_local_power_r; + public @Nullable @SerializedName(value = "plocalloads") Integer to_local_power_s; + public @Nullable @SerializedName(value = "plocalloadt") Integer to_local_power_t; + + // pv energy + public @Nullable @SerializedName(value = "pvenergytoday") Integer pv_energy_today; + public @Nullable @SerializedName(value = "pvenergytotal") Integer pv_energy_total; + + // energy taken from solar + public @Nullable @SerializedName(value = "epvtoday") Integer pv_grid_energy_today; + public @Nullable @SerializedName(value = "epv1today", alternate = { "epv1tod" }) Integer pv1_grid_energy_today; + public @Nullable @SerializedName(value = "epv2today", alternate = { "epv2tod" }) Integer pv2_grid_energy_today; + + public @Nullable @SerializedName(value = "epvtotal") Integer pv_grid_energy_total; + public @Nullable @SerializedName(value = "epv1total", alternate = { "epv1tot" }) Integer pv1_grid_energy_total; + public @Nullable @SerializedName(value = "epv2total", alternate = { "epv2tot" }) Integer pv2_grid_energy_total; + + // energy supplied to grid + public @Nullable @SerializedName(value = "etogrid_tod", alternate = { "eactoday" }) Integer to_grid_energy_today; + public @Nullable @SerializedName(value = "etogrid_tot", alternate = { "eactotal" }) Integer to_grid_energy_total; + + // energy supplied to user + public @Nullable @SerializedName(value = "etouser_tod") Integer to_user_energy_today; + public @Nullable @SerializedName(value = "etouser_tot") Integer to_user_energy_total; + + // energy supplied to local load + public @Nullable @SerializedName(value = "elocalload_tod") Integer to_local_energy_today; + public @Nullable @SerializedName(value = "elocalloadr_tot") Integer to_local_energy_total; + + // energy taken from grid to charge + public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday" }) Integer grid_charge_energy_today; + public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal" }) Integer grid_charge_energy_total; + + // energy supplied to grid from discharge of battery + public @Nullable @SerializedName(value = "edischarge1_tod", alternate = { "eacDischarToday" }) Integer grid_discharge_energy_today; + public @Nullable @SerializedName(value = "edischarge1_tot", alternate = { "eacDischarTotal" }) Integer grid_discharge_energy_total; + + // energy taken from battery + public @Nullable @SerializedName(value = "ebatDischarToday") Integer battery_discharge_energy_today; + public @Nullable @SerializedName(value = "ebatDischarTotal") Integer battery_discharge_energy_total; + + // inverter up time + public @Nullable @SerializedName(value = "totworktime") Integer total_work_time; + + // bus voltages + public @Nullable @SerializedName(value = "pbusvolt", alternate = { "bus_volt" }) Integer p_bus_potential; + public @Nullable @SerializedName(value = "nbusvolt") Integer n_bus_potential; + public @Nullable @SerializedName(value = "spbusvolt") Integer sp_bus_potential; + + // temperatures + public @Nullable @SerializedName(value = "pvtemperature", alternate = { "dcdctemp", "buck1_ntc" }) Integer pv_temperature; + public @Nullable @SerializedName(value = "pvipmtemperature", alternate = { "invtemp" }) Integer pv_ipm_temperature; + public @Nullable @SerializedName(value = "pvboosttemp", alternate = { "pvboottemperature" }) Integer pv_boost_temperature; + public @Nullable @SerializedName(value = "temp4") Integer temperature_4; + public @Nullable @SerializedName(value = "buck2_ntc") Integer pv2_temperature; + + // battery data + public @Nullable @SerializedName(value = "batteryType") Integer battery_type; + public @Nullable @SerializedName(value = "batttemp") Integer battery_temperature; + public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt" }) Integer battery_potential; + public @Nullable @SerializedName(value = "bat_dsp") Integer battery_display; + public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC" }) Integer battery_soc; + + // fault codes + public @Nullable @SerializedName(value = "systemfaultword0", alternate = { "isof", "faultBit" }) Integer system_fault_0; + public @Nullable @SerializedName(value = "systemfaultword1", alternate = { "gfcif", "faultValue" }) Integer system_fault_1; + public @Nullable @SerializedName(value = "systemfaultword2", alternate = { "dcif", "warningBit" }) Integer system_fault_2; + public @Nullable @SerializedName(value = "systemfaultword3", alternate = { "vpvfault", "warningValue" }) Integer system_fault_3; + public @Nullable @SerializedName(value = "systemfaultword4", alternate = { "vacfault" }) Integer system_fault_4; + public @Nullable @SerializedName(value = "systemfaultword5", alternate = { "facfault" }) Integer system_fault_5; + public @Nullable @SerializedName(value = "systemfaultword6", alternate = { "tempfault" }) Integer system_fault_6; + public @Nullable @SerializedName(value = "systemfaultword7", alternate = { "faultcode" }) Integer system_fault_7; + + // miscellaneous + public @Nullable @SerializedName(value = "uwsysworkmode") Integer system_work_mode; + public @Nullable @SerializedName(value = "spdspstatus") Integer sp_display_status; + public @Nullable @SerializedName(value = "constantPowerOK") Integer constant_power_ok; + + // rac ?? + public @Nullable @SerializedName(value = "rac") Integer rac; + public @Nullable @SerializedName(value = "eractoday") Integer erac_today; + public @Nullable @SerializedName(value = "eractotal") Integer erac_total; + + // duplicates ?? + public @Nullable @SerializedName(value = "outputvolt") Integer output_potential; + public @Nullable @SerializedName(value = "outputfreq") Integer output_frequency; + public @Nullable @SerializedName(value = "loadpercent") Integer load_percent; + public @Nullable @SerializedName(value = "Inv_Curr") Integer inverter_current; + public @Nullable @SerializedName(value = "AC_InWatt") Integer grid_input_power; + public @Nullable @SerializedName(value = "AC_InVA") Integer grid_input_va; + + // @formatter:on + + /** + * Return the valid values from this DTO in a map between channel id and respective QuantityType states. + * + * @return a map of channel ids and respective QuantityType state values. + * @throws NoSuchFieldException should not occur since we specifically tested this in JUnit tests. + * @throws SecurityException should not occur since all fields are public. + * @throws IllegalAccessException should not occur since all fields are public. + * @throws IllegalArgumentException should not occur since we are specifically working with this class. + */ + public Map> getChannelStates() + throws NoSuchFieldException, SecurityException, IllegalAccessException, IllegalArgumentException { + Map> map = new HashMap<>(); + for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { + String channelId = entry.getKey(); + UoM uom = entry.getValue(); + Field field = getClass().getField(getFieldName(channelId)); + Object obj = field.get(this); + if (obj instanceof Integer) { + map.put(channelId, QuantityType.valueOf(((Integer) obj).doubleValue() / uom.divisor, uom.units)); + } + } + return map; + } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 611a371274c68..97c818cff3a39 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -21,6 +21,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.growatt.internal.dto.GrottDevice; @@ -50,6 +51,17 @@ public class GrowattBridgeHandler extends BaseBridgeHandler { private static final String GROWATT_SERVLET_PATH_ALIAS = "/growatt"; + private static final String SERVLET_ONLINE_HTML = "" + // @formatter:off + + "" + + "" + + "

Growatt Binding

" + + "

 

" + + "

Servlet status: ONLINE

" + + "" + + ""; + // @formatter:off + /** * Inner servlet instance class to handle POST data from the Grott application. */ @@ -64,6 +76,13 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) handleGrottContent(request.getContentLength() <= 0 ? "" : new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType(MediaType.TEXT_HTML); + response.getWriter().write(SERVLET_ONLINE_HTML); + } } private final Logger logger = LoggerFactory.getLogger(GrowattBridgeHandler.class); diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 24ed95b5f865e..1d12fb2995b6c 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -12,17 +12,11 @@ */ package org.openhab.binding.growatt.internal.handler; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.growatt.internal.GrowattBindingConstants; -import org.openhab.binding.growatt.internal.GrowattBindingConstants.UoM; import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; @@ -34,7 +28,6 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; -import org.openhab.core.types.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,62 +61,42 @@ public void handleCommand(ChannelUID channelUID, Command command) { public void handleGrottDevice(GrottDevice grottDevice) { GrottValues grottValues = grottDevice.getValues(); if (grottValues == null) { - logger.debug("handleGrottDevice() device '{}' contained no values", grottDevice.getDeviceId()); + logger.warn("handleGrottDevice() device '{}' contains no values", grottDevice.getDeviceId()); return; } - Map channelStates = new HashMap<>(); - List missingFields = new ArrayList<>(); - - // read channel states from DTO - for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { - String channelId = entry.getKey(); - Field field; - try { - field = GrottValues.class.getField(channelId); - } catch (NoSuchFieldException e) { - missingFields.add(channelId); - continue; - } catch (SecurityException e) { - logger.debug("handleGrottDevice() security exception field '{}'", channelId); - continue; - } - Object value; - try { - value = field.get(grottValues); - } catch (IllegalArgumentException | IllegalAccessException e) { - logger.debug("handleGrottDevice() error reading field '{}' value", channelId); - continue; - } - if (value != null && (value instanceof Integer)) { - UoM uom = entry.getValue(); - channelStates.put(channelId, - QuantityType.valueOf(((Integer) value).doubleValue() / uom.divisor, uom.units)); - } + // get channel states + Map> channelStates; + try { + channelStates = grottValues.getChannelStates(); + } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) { + // should never happen since previously tested in JUnit tests + logger.warn("handleGrottDevice() unexpected exception:{}, message:{}", e.getClass().getName(), + e.getMessage(), e); + return; } - // warn if fields missing from DTO - if (!missingFields.isEmpty() && logger.isWarnEnabled()) { - logger.warn("handleGrottDevice() please notify maintainers: GrottValues.class is missing fields: {}", - missingFields.stream().collect(Collectors.joining(","))); - } + // find unused channels + List actualChannels = thing.getChannels(); + List unusedChannels = actualChannels.stream() + .filter(channel -> !channelStates.containsKey(channel.getUID().getId())).collect(Collectors.toList()); // remove unused channels - List unusedChannels = thing.getChannels().stream() - .filter(channel -> !channelStates.containsKey(channel.getUID().getId())).collect(Collectors.toList()); if (!unusedChannels.isEmpty()) { updateThing(editThing().withoutChannels(unusedChannels).build()); - logger.debug("handleGrottDevice() removed {} unused channels", unusedChannels.size()); + logger.debug("handleGrottDevice() channel count {} reduced by {} to {}", actualChannels.size(), + unusedChannels.size(), thing.getChannels().size()); } - // update channel states - List channelIds = thing.getChannels().stream().map(channel -> channel.getUID().getId()) + List thingChannelIds = thing.getChannels().stream().map(channel -> channel.getUID().getId()) .collect(Collectors.toList()); + + // update channel states channelStates.forEach((channelId, state) -> { - if (channelIds.contains(channelId)) { + if (thingChannelIds.contains(channelId)) { updateState(channelId, state); } else { - logger.debug("handleGrottDevice() channel '{}' not implemented in thing", channelId); + logger.debug("handleGrottDevice() channel '{}' not found in thing", channelId); } }); } @@ -149,5 +122,6 @@ public void initialize() { GrowattInverterConfiguration config = getConfigAs(GrowattInverterConfiguration.class); deviceId = config.deviceId; updateStatus(ThingStatus.UNKNOWN); + logger.debug("initialize() thing has {} channels", thing.getChannels().size()); } } diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 5df6cf461c046..a753fd67137e3 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -21,193 +21,450 @@ - - + + + Status code of the inverter (0=ready, 1=online, 2=fault). + + + + Total solar input power. - + + + Total solar output power. + + + + Voltage from solar panel string #1. - - - Current from solar panel string #1. - - - - Power from solar panel string #1. - - + Voltage of solar panel string #2. - + + + + Current from solar panel string #1. + + Current from solar panel string #2. - + + + + Power from solar panel string #1. + + Power from solar panel string #2. - - - Total solar output power. + + + + + Frequency of the grid. + + + + Voltage of the grid (phase #R). - - + - Voltage of the grid supply. + Voltage of the grid phase #S. - + + + Voltage of the grid phase #T. + + + + Voltage of the grid phases #RS. + + + + Voltage of the grid phases #ST. + + + + Voltage of the grid phases #TR. + + + + - Current delivered to the grid supply. + Current delivered to the grid (phase #R). - + + + Current delivered to the grid phase #S. + + + + Current delivered to the grid phase #T. + + + + + Power delivered to the grid (phase #R). + + + + Power delivered to the grid phase #S. + + - Power delivered to the grid supply. + Power delivered to the grid phase #T. + + + + + VA delivered to the grid. + + + + + + Grid current to charge battery. + + + + Grid power to charge battery. + + + + Grid VA to charge battery. + + + + + + Grid power from discharge of battery. + + + + Grid VA from discharge of battery. + + + + + + Battery charge power. + + + + Battery discharge power. + + + + Battery discharge VA. - - - Voltage of the grid supply #2. + + + + + Power supplied to grid. + + + + Power supplied to grid phase #R. + + + + Power supplied to grid phase #S. + + + + Power supplied to grid phase #T. - - - Current delivered to the grid supply #2. + + + + + Power supplied to user. + + + + Power supplied to user phase #R. - - - Power delivered to the grid supply #2. + + + Power supplied to user phase #S. - - - Voltage of the grid supply #3. + + + Power supplied to user phase #T. + + + + + + Power supplied to local. - - - Current delivered to the grid supply #3. + + + Power supplied to local phase #R. - - - Power delivered to the grid supply #3. + + + Power supplied to local phase #S. - + + + Power supplied to local phase #T. + + + + Solar energy collected today. - + Total solar energy collected. - - - - Energy from solar panel string #1 today. + + + + + Solar energy supplied to grid today. + + + + Solar energy supplied by string #1 to grid today. - - - Total energy from solar panel string #1. + + + Solar energy supplied by string #2 to grid today. - - - Energy from solar panel string #2 today. + + + + Total solar energy supplied to grid. - - - Total energy from solar panel string #2. + + + Total solar energy supplied by string #1 to grid . - - - Total energy from all solar panels. + + + Total solar energy supplied by string #2 to grid. + + + + + + Energy supplied to grid today. + + + + Total energy supplied to grid. + + + + + + Energy supplied to user today. + + + + Total energy supplied to user. + + + + + + Energy supplied to local today. + + + + Total energy supplied to local. + + + + + + Energy used to charge battery today. + + + + Total energy used to charge battery. + + + + + + Grid energy produced from battery today. + + + + Total grid energy produced from battery. + + + + + + Energy consumed from battery. + + + + Total energy consumed from battery. + + + + + + + + + P Bus voltage. + + + + N Bus voltage. + + + + N Bus voltage. - + + + - Temperature of the solar panels. + Temperature of the solar panels (string #1). - + Temperature of the IPM. - + Boost temperature. - + Temperature #4. - - - AC voltage R-S. + + + Temperature of the solar panels (string #2). - - - AC voltage S-T. + + + + + Type code of the battery. - - - AC voltage T-R. + + + Battery temperature. - - - Battery voltage DSP. + + + Battery voltage. - - - P Bus voltage. + + + Battery display code. - - - N Bus voltage. + + + Battery SOC code. - - - AC charge energy today. + + + + + System fault code #0. - - - Total AC charge energy. + + + System fault code #1. - - - Battery discharge energy today. + + + System fault code #2. - - - Total battery discharge energy. + + + System fault code #3. - - - AC discharge energy today. + + + System fault code #4. - - - Total AC discharge energy. + + + System fault code #5. - - - AC charge current. + + + System fault code #6. - - - AC discharge power. + + + System fault code #7. - - - AC discharge VA. + + + + + System work mode code. - - - Battery discharge power. + + + Solar panel display status code. + + + + Constant power OK code. - - - Battery VA. + + + + + + RAC code. + + + + ERAC count today. + + + + Total ERAC count. + + + + + + Output voltage. + + + + Output frequency. + + + + Percent of full load. - - - Battery power. + + + Inverter current. + + + Grid input power. + + + + Grid input VA. + + @@ -219,18 +476,28 @@
- + Number:Dimensionless - - Status code of the inverter (0=ready, 1=online, 2=fault). + + + Number:Dimensionless + + + + Number:Frequency - - Frequency of the grid supply. - + + + + + + Number:Power + + diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 03441142bf01f..6e8753538f17c 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -18,9 +18,11 @@ import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Field; -import java.util.HashMap; +import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; @@ -63,21 +65,47 @@ private String load(String fileName) { return ""; } - @Test - void testCreateGrottDeviceFromJson() { - String json = load("simple"); + /** + * Load a GrottValues class from a JSON payload. + * + * @param fileName the file containing the json payload. + * @return a GrottValues dto. + */ + private GrottValues loadGrottValues(String fileName) { + String json = load(fileName); GrottDevice device = gson.fromJson(json, GrottDevice.class); - assertNotNull(device); GrottValues grottValues = device.getValues(); assertNotNull(grottValues); + return grottValues; + } + + /** + * Test that GrottValues implements the same fields as thye GrowattBindingConstants.CHANNEL_ID_UOM_MAP. + * Test that all fields can be accessed and that they are either null or an Integer instance. + */ + @Test + void testGrottValuesAccessibility() { + GrottValues grottValues = loadGrottValues("simple"); + + List fields = Arrays.asList(GrottValues.class.getFields()).stream().map(f -> f.getName()) + .collect(Collectors.toList()); + + for (String channel : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.keySet()) { + assertTrue(fields.contains(GrottValues.getFieldName(channel))); + } + + for (String field : fields) { + assertTrue(GrowattBindingConstants.CHANNEL_ID_UOM_MAP.containsKey(GrottValues.getChannelId(field))); + } + + assertEquals(fields.size(), GrowattBindingConstants.CHANNEL_ID_UOM_MAP.size()); - Map channelStates = new HashMap<>(); for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { String channelId = entry.getKey(); Field field; try { - field = GrottValues.class.getField(channelId); + field = GrottValues.class.getField(GrottValues.getFieldName(channelId)); } catch (NoSuchFieldException e) { fail(e.getMessage()); continue; @@ -85,35 +113,57 @@ void testCreateGrottDeviceFromJson() { fail(e.getMessage()); continue; } - Object value; try { - value = field.get(grottValues); + Object value = field.get(grottValues); + assertTrue(value == null || (value instanceof Integer)); } catch (IllegalArgumentException | IllegalAccessException e) { fail(e.getMessage()); continue; } - if (value != null && (value instanceof Integer)) { - UoM uom = entry.getValue(); - channelStates.put(channelId, - QuantityType.valueOf(((Integer) value).doubleValue() / uom.divisor, uom.units)); - } + } + } + + /** + * Test that GrottValues is loaded with the correct contents from a JSON file. + */ + @Test + void testGrottValuesContents() { + GrottValues grottValues = loadGrottValues("simple"); + Map> channelStates = null; + + try { + channelStates = grottValues.getChannelStates(); + } catch (NoSuchFieldException e) { + fail(e.getMessage()); + } catch (SecurityException e) { + fail(e.getMessage()); + } catch (IllegalAccessException e) { + fail(e.getMessage()); + } catch (IllegalArgumentException e) { + fail(e.getMessage()); } + assertNotNull(channelStates); assertEquals(29, channelStates.size()); channelStates.forEach((channelId, state) -> { assertTrue(state instanceof QuantityType); }); - assertEquals(QuantityType.ONE, channelStates.get("pvstatus")); - assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("pvgridvoltage")); - assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("pvgridcurrent")); - assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("pvgridpower")); - assertEquals(QuantityType.valueOf(49.97, Units.HERTZ), channelStates.get("pvfrequency")); - assertEquals(QuantityType.valueOf(32751939, Units.SECOND), channelStates.get("totworktime")); - assertEquals(QuantityType.valueOf(27.3, SIUnits.CELSIUS), channelStates.get("pvtemperature")); - assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("epvtotal")); + assertEquals(QuantityType.ONE, channelStates.get("status")); + assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("grid-potential")); + assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("grid-current")); + assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("grid-power")); + assertEquals(QuantityType.valueOf(49.97, Units.HERTZ), channelStates.get("grid-frequency")); + assertEquals(QuantityType.valueOf(27.3, SIUnits.CELSIUS), channelStates.get("pv-temperature")); + assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("pv-grid-energy-total")); - assertNull(channelStates.get("BatWatt")); + State state = channelStates.get("total-work-time"); + assertTrue(state instanceof QuantityType); + if (state instanceof QuantityType quantity) { + QuantityType seconds = quantity.toUnit(Units.SECOND); + assertNotNull(seconds); + assertEquals(QuantityType.valueOf(32751939, Units.SECOND).doubleValue(), seconds.doubleValue(), 0.1); + } } } From f9440e44d42ebda06e97bf673e71c81e95b1bf41 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 19 Jun 2023 16:56:16 +0100 Subject: [PATCH 006/146] [growatt] typo --- bundles/org.openhab.binding.growatt/README.md | 2 +- .../src/main/resources/OH-INF/thing/thing-types.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index c779291bdf7e0..7c8692058f2fe 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -68,7 +68,7 @@ The list of all possible channels is as follows: | pv-power-in | Number:Power | Total solar input power. | | pv-power-out | Number:Power | Total solar output power. | | pv1-potential | Number:ElectricPotential | Voltage from solar panel string #1. | -| pv2-potential | Number:ElectricPotential | Voltage of solar panel string #2. | +| pv2-potential | Number:ElectricPotential | Voltage from solar panel string #2. | | pv1-current | Number:ElectricCurrent | Current from solar panel string #1. | | pv2-current | Number:ElectricCurrent | Current from solar panel string #2. | | pv1-power | Number:Power | Power from solar panel string #1. | diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index a753fd67137e3..78b2dab27d3db 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -43,7 +43,7 @@
- Voltage of solar panel string #2. + Voltage from solar panel string #2. From ce5b1e8b933b61c4a43ee0f60749a05da19698b7 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 19 Jun 2023 18:13:15 +0100 Subject: [PATCH 007/146] [growatt] fix channel names Signed-off-by: Andrew Fiddian-Green --- .../internal/GrowattBindingConstants.java | 12 +-- .../growatt/internal/dto/GrottValues.java | 12 +-- .../handler/GrowattInverterHandler.java | 2 + .../resources/OH-INF/thing/thing-types.xml | 93 ++++++++++--------- .../binding/growatt/test/GrowattTest.java | 4 + 5 files changed, 69 insertions(+), 54 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java index 8912fe5b6e3e4..eb7b2e11bdc92 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -76,20 +76,20 @@ public UoM(Unit units, float divisor) { new AbstractMap.SimpleEntry("grid-frequency", new UoM(Units.HERTZ, 100)), new AbstractMap.SimpleEntry("grid-potential", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-potential-2", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-potential-3", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-potential-s", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-potential-t", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("grid-potential-rs", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("grid-potential-st", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("grid-potential-tr", new UoM(Units.VOLT, 10)), // solar power to grid new AbstractMap.SimpleEntry("grid-current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("grid-current-2", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("grid-current-3", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("grid-current-s", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("grid-current-t", new UoM(Units.AMPERE, 10)), new AbstractMap.SimpleEntry("grid-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-power-2", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-power-3", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("grid-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("grid-power-t", new UoM(Units.WATT, 10)), new AbstractMap.SimpleEntry("grid-va", new UoM(Units.VOLT_AMPERE, 10)), diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 254d95ed8cb01..b9c848026369d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -69,20 +69,20 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq" }) Integer grid_frequency; public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt" }) Integer grid_potential; - public @Nullable @SerializedName(value = "pvgridvoltage2") Integer grid_potential_2; - public @Nullable @SerializedName(value = "pvgridvoltage3") Integer grid_potential_3; + public @Nullable @SerializedName(value = "pvgridvoltage2") Integer grid_potential_s; + public @Nullable @SerializedName(value = "pvgridvoltage3") Integer grid_potential_t; public @Nullable @SerializedName(value = "Vac_RS") Integer grid_potential_rs; public @Nullable @SerializedName(value = "Vac_ST") Integer grid_potential_st; public @Nullable @SerializedName(value = "Vac_TR") Integer grid_potential_tr; // solar power to grid public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr" }) Integer grid_current; - public @Nullable @SerializedName(value = "pvgridcurrent2") Integer grid_current_2; - public @Nullable @SerializedName(value = "pvgridcurrent3") Integer grid_current_3; + public @Nullable @SerializedName(value = "pvgridcurrent2") Integer grid_current_s; + public @Nullable @SerializedName(value = "pvgridcurrent3") Integer grid_current_t; public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt" }) Integer grid_power; - public @Nullable @SerializedName(value = "pvgridpower2") Integer grid_power_2; - public @Nullable @SerializedName(value = "pvgridpower3") Integer grid_power_3; + public @Nullable @SerializedName(value = "pvgridpower2") Integer grid_power_s; + public @Nullable @SerializedName(value = "pvgridpower3") Integer grid_power_t; public @Nullable @SerializedName(value = "op_va") Integer grid_va; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 1d12fb2995b6c..8932d1755df50 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -76,6 +76,8 @@ public void handleGrottDevice(GrottDevice grottDevice) { return; } + logger.debug("handleGrottDevice() channelStates size:{}", channelStates.size()); + // find unused channels List actualChannels = thing.getChannels(); List unusedChannels = actualChannels.stream() diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 78b2dab27d3db..2ddc8b8e2fc7d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -37,11 +37,11 @@ - + Voltage from solar panel string #1. - + Voltage from solar panel string #2. @@ -69,28 +69,28 @@ Frequency of the grid. - + Voltage of the grid (phase #R). - - + + Voltage of the grid phase #S. - - + + Voltage of the grid phase #T. - + Voltage of the grid phases #RS. - - + + Voltage of the grid phases #ST. - - + + Voltage of the grid phases #TR. @@ -100,11 +100,11 @@ Current delivered to the grid (phase #R). - + Current delivered to the grid phase #S. - + Current delivered to the grid phase #T. @@ -113,11 +113,11 @@ Power delivered to the grid (phase #R). - + Power delivered to the grid phase #S. - + Power delivered to the grid phase #T. @@ -219,115 +219,118 @@ - + Solar energy collected today. - + Total solar energy collected. - + Solar energy supplied to grid today. - + Solar energy supplied by string #1 to grid today. - + Solar energy supplied by string #2 to grid today. - + Total solar energy supplied to grid. - + Total solar energy supplied by string #1 to grid . - + Total solar energy supplied by string #2 to grid. - + Energy supplied to grid today. - + Total energy supplied to grid. - + Energy supplied to user today. - + Total energy supplied to user. - + Energy supplied to local today. - + Total energy supplied to local. - + Energy used to charge battery today. - + Total energy used to charge battery. - + Grid energy produced from battery today. - + Total grid energy produced from battery. - + Energy consumed from battery. - + Total energy consumed from battery. - + + + Total inverter working time. + - + P Bus voltage. - + N Bus voltage. - + N Bus voltage. @@ -363,7 +366,7 @@ Battery temperature. - + Battery voltage. @@ -440,7 +443,7 @@ - + Output voltage. @@ -500,4 +503,10 @@ + + Number:Time + + + + diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 6e8753538f17c..fad2b66cc53b1 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -158,6 +158,10 @@ void testGrottValuesContents() { assertEquals(QuantityType.valueOf(27.3, SIUnits.CELSIUS), channelStates.get("pv-temperature")); assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("pv-grid-energy-total")); + assertEquals(QuantityType.valueOf(0, Units.VOLT), channelStates.get("pv2-potential")); + assertEquals(QuantityType.valueOf(0, Units.AMPERE), channelStates.get("pv2-current")); + assertEquals(QuantityType.valueOf(0, Units.WATT), channelStates.get("pv2-power")); + State state = channelStates.get("total-work-time"); assertTrue(state instanceof QuantityType); if (state instanceof QuantityType quantity) { From 8f6e8ebe8849acebd3d9b0a99911e9d9ac3d710a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 19 Jun 2023 18:51:42 +0100 Subject: [PATCH 008/146] [growatt] xml and properties Signed-off-by: Andrew Fiddian-Green --- .../src/main/resources/OH-INF/addon/addon.xml | 4 +- .../resources/OH-INF/i18n/growatt.properties | 221 +++++++++++++++++- .../resources/OH-INF/thing/thing-types.xml | 10 +- 3 files changed, 225 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml index 884355e63ad9e..c848fe79b96ba 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd"> binding - growatt Binding - This is the binding for growatt. + Growatt Binding + This is the binding for Growatt solar inverters. diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 0c2f44015a92d..e18522a7a848d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -1,3 +1,218 @@ -# FIXME: please add all English translations to this file so the texts can be translated using Crowdin -# FIXME: to generate the content of this file run: mvn i18n:generate-default-translations -# FIXME: see also: https://www.openhab.org/docs/developer/utils/i18n.html +# add-on + +addon.growatt.name = Growatt Binding +addon.growatt.description = This is the binding for Growatt solar inverters. + +# thing types + +thing-type.growatt.bridge.label = Growatt Bridge Thing +thing-type.growatt.bridge.description = Bridge Thing for Growatt Binding +thing-type.growatt.inverter.label = Growatt Inverter Thing +thing-type.growatt.inverter.description = Inverter Thing for Growatt Binding +thing-type.growatt.inverter.channel.battery-charge-power.label = Battery Charge Power +thing-type.growatt.inverter.channel.battery-charge-power.description = Battery charge power. +thing-type.growatt.inverter.channel.battery-discharge-energy-today.label = Battery Discharge Energy Today +thing-type.growatt.inverter.channel.battery-discharge-energy-today.description = Energy consumed from battery. +thing-type.growatt.inverter.channel.battery-discharge-energy-total.label = Total Battery Discharge Energy +thing-type.growatt.inverter.channel.battery-discharge-energy-total.description = Total energy consumed from battery. +thing-type.growatt.inverter.channel.battery-discharge-power.label = Battery Discharge Power +thing-type.growatt.inverter.channel.battery-discharge-power.description = Battery discharge power. +thing-type.growatt.inverter.channel.battery-discharge-va.label = Battery Discharge VA +thing-type.growatt.inverter.channel.battery-discharge-va.description = Battery discharge VA. +thing-type.growatt.inverter.channel.battery-display.label = Battery Display +thing-type.growatt.inverter.channel.battery-display.description = Battery display code. +thing-type.growatt.inverter.channel.battery-potential.label = Battery Voltage +thing-type.growatt.inverter.channel.battery-potential.description = Battery voltage. +thing-type.growatt.inverter.channel.battery-soc.label = Battery SOC +thing-type.growatt.inverter.channel.battery-soc.description = Battery SOC code. +thing-type.growatt.inverter.channel.battery-temperature.label = Battery Temperature +thing-type.growatt.inverter.channel.battery-temperature.description = Battery temperature. +thing-type.growatt.inverter.channel.battery-type.label = Battery Type +thing-type.growatt.inverter.channel.battery-type.description = Type code of the battery. +thing-type.growatt.inverter.channel.constant-power-ok.label = Constant Power OK +thing-type.growatt.inverter.channel.constant-power-ok.description = Constant power OK code. +thing-type.growatt.inverter.channel.erac-today.label = ERAC Today +thing-type.growatt.inverter.channel.erac-today.description = ERAC count today. +thing-type.growatt.inverter.channel.erac-total.label = ERAC Total +thing-type.growatt.inverter.channel.erac-total.description = Total ERAC count. +thing-type.growatt.inverter.channel.grid-charge-current.label = Charge Current +thing-type.growatt.inverter.channel.grid-charge-current.description = Grid current to charge battery. +thing-type.growatt.inverter.channel.grid-charge-energy-today.label = Energy to Charge Battery Today +thing-type.growatt.inverter.channel.grid-charge-energy-today.description = Energy used to charge battery today. +thing-type.growatt.inverter.channel.grid-charge-energy-total.label = Total Energy to Charge Battery +thing-type.growatt.inverter.channel.grid-charge-energy-total.description = Total energy used to charge battery. +thing-type.growatt.inverter.channel.grid-charge-power.label = Charge Power +thing-type.growatt.inverter.channel.grid-charge-power.description = Grid power to charge battery. +thing-type.growatt.inverter.channel.grid-charge-va.label = Charge VA +thing-type.growatt.inverter.channel.grid-charge-va.description = Grid VA to charge battery. +thing-type.growatt.inverter.channel.grid-current.label = Grid Current +thing-type.growatt.inverter.channel.grid-current.description = Current delivered to the grid (phase #R). +thing-type.growatt.inverter.channel.grid-current-s.label = Grid Current #S +thing-type.growatt.inverter.channel.grid-current-s.description = Current delivered to the grid phase #S. +thing-type.growatt.inverter.channel.grid-current-t.label = Grid Current #T +thing-type.growatt.inverter.channel.grid-current-t.description = Current delivered to the grid phase #T. +thing-type.growatt.inverter.channel.grid-discharge-energy-today.label = Grid Energy from Battery Today +thing-type.growatt.inverter.channel.grid-discharge-energy-today.description = Grid energy produced from battery today. +thing-type.growatt.inverter.channel.grid-discharge-energy-total.label = Total Grid Energy from Battery +thing-type.growatt.inverter.channel.grid-discharge-energy-total.description = Total grid energy produced from battery. +thing-type.growatt.inverter.channel.grid-discharge-power.label = Discharge Power +thing-type.growatt.inverter.channel.grid-discharge-power.description = Grid power from discharge of battery. +thing-type.growatt.inverter.channel.grid-discharge-va.label = Discharge VA +thing-type.growatt.inverter.channel.grid-discharge-va.description = Grid VA from discharge of battery. +thing-type.growatt.inverter.channel.grid-frequency.label = Grid Frequency +thing-type.growatt.inverter.channel.grid-frequency.description = Frequency of the grid. +thing-type.growatt.inverter.channel.grid-input-power.label = Grid Input Power +thing-type.growatt.inverter.channel.grid-input-power.description = Grid input power. +thing-type.growatt.inverter.channel.grid-input-va.label = Grid Input VA +thing-type.growatt.inverter.channel.grid-input-va.description = Grid input VA. +thing-type.growatt.inverter.channel.grid-potential.label = Grid Voltage +thing-type.growatt.inverter.channel.grid-potential.description = Voltage of the grid (phase #R). +thing-type.growatt.inverter.channel.grid-potential-rs.label = Grid Voltage #RS +thing-type.growatt.inverter.channel.grid-potential-rs.description = Voltage of the grid phases #RS. +thing-type.growatt.inverter.channel.grid-potential-s.label = Grid Voltage #S +thing-type.growatt.inverter.channel.grid-potential-s.description = Voltage of the grid phase #S. +thing-type.growatt.inverter.channel.grid-potential-st.label = Grid Voltage #ST +thing-type.growatt.inverter.channel.grid-potential-st.description = Voltage of the grid phases #ST. +thing-type.growatt.inverter.channel.grid-potential-t.label = Grid Voltage #T +thing-type.growatt.inverter.channel.grid-potential-t.description = Voltage of the grid phase #T. +thing-type.growatt.inverter.channel.grid-potential-tr.label = Grid Voltage #TR +thing-type.growatt.inverter.channel.grid-potential-tr.description = Voltage of the grid phases #TR. +thing-type.growatt.inverter.channel.grid-power.label = Grid Power +thing-type.growatt.inverter.channel.grid-power.description = Power delivered to the grid (phase #R). +thing-type.growatt.inverter.channel.grid-power-s.label = Grid Power #S +thing-type.growatt.inverter.channel.grid-power-s.description = Power delivered to the grid phase #S. +thing-type.growatt.inverter.channel.grid-power-t.label = Grid Power #T +thing-type.growatt.inverter.channel.grid-power-t.description = Power delivered to the grid phase #T. +thing-type.growatt.inverter.channel.grid-va.label = Grid VA +thing-type.growatt.inverter.channel.grid-va.description = VA delivered to the grid. +thing-type.growatt.inverter.channel.inverter-current.label = Inverter Current +thing-type.growatt.inverter.channel.inverter-current.description = Inverter current. +thing-type.growatt.inverter.channel.load-percent.label = Load Percent +thing-type.growatt.inverter.channel.load-percent.description = Percent of full load. +thing-type.growatt.inverter.channel.n-bus-potential.label = N Bus Voltage +thing-type.growatt.inverter.channel.n-bus-potential.description = N Bus voltage. +thing-type.growatt.inverter.channel.output-frequency.label = Output Frequency +thing-type.growatt.inverter.channel.output-frequency.description = Output frequency. +thing-type.growatt.inverter.channel.output-potential.label = Output Voltage +thing-type.growatt.inverter.channel.output-potential.description = Output voltage. +thing-type.growatt.inverter.channel.p-bus-potential.label = P Bus Voltage +thing-type.growatt.inverter.channel.p-bus-potential.description = P Bus voltage. +thing-type.growatt.inverter.channel.pv-boost-temperature.label = Boost Temperature +thing-type.growatt.inverter.channel.pv-boost-temperature.description = Boost temperature. +thing-type.growatt.inverter.channel.pv-energy-today.label = Solar Energy Today +thing-type.growatt.inverter.channel.pv-energy-today.description = Solar energy collected today. +thing-type.growatt.inverter.channel.pv-energy-total.label = Solar Energy Total +thing-type.growatt.inverter.channel.pv-energy-total.description = Total solar energy collected. +thing-type.growatt.inverter.channel.pv-grid-energy-today.label = Solar Energy to Grid Today +thing-type.growatt.inverter.channel.pv-grid-energy-today.description = Solar energy supplied to grid today. +thing-type.growatt.inverter.channel.pv-grid-energy-total.label = Total Solar Energy to Grid +thing-type.growatt.inverter.channel.pv-grid-energy-total.description = Total solar energy supplied to grid. +thing-type.growatt.inverter.channel.pv-ipm-temperature.label = Solar IPM Temperature +thing-type.growatt.inverter.channel.pv-ipm-temperature.description = Temperature of the IPM. +thing-type.growatt.inverter.channel.pv-power-in.label = Solar Input Power +thing-type.growatt.inverter.channel.pv-power-in.description = Total solar input power. +thing-type.growatt.inverter.channel.pv-power-out.label = Solar Output Power +thing-type.growatt.inverter.channel.pv-power-out.description = Total solar output power. +thing-type.growatt.inverter.channel.pv-temperature.label = Solar Panel Temperature +thing-type.growatt.inverter.channel.pv-temperature.description = Temperature of the solar panels (string #1). +thing-type.growatt.inverter.channel.pv1-current.label = String #1 Current +thing-type.growatt.inverter.channel.pv1-current.description = Current from solar panel string #1. +thing-type.growatt.inverter.channel.pv1-grid-energy-today.label = Solar Energy #1 to Grid Today +thing-type.growatt.inverter.channel.pv1-grid-energy-today.description = Solar energy supplied by string #1 to grid today. +thing-type.growatt.inverter.channel.pv1-grid-energy-total.label = Total Solar Energy #1 to Grid +thing-type.growatt.inverter.channel.pv1-grid-energy-total.description = Total solar energy supplied by string #1 to grid . +thing-type.growatt.inverter.channel.pv1-potential.label = String #1 Voltage +thing-type.growatt.inverter.channel.pv1-potential.description = Voltage from solar panel string #1. +thing-type.growatt.inverter.channel.pv1-power.label = String #1 Power +thing-type.growatt.inverter.channel.pv1-power.description = Power from solar panel string #1. +thing-type.growatt.inverter.channel.pv2-current.label = String #2 Current +thing-type.growatt.inverter.channel.pv2-current.description = Current from solar panel string #2. +thing-type.growatt.inverter.channel.pv2-grid-energy-today.label = Solar Energy #2 to Grid Today +thing-type.growatt.inverter.channel.pv2-grid-energy-today.description = Solar energy supplied by string #2 to grid today. +thing-type.growatt.inverter.channel.pv2-grid-energy-total.label = Total Solar Energy #2 to Grid +thing-type.growatt.inverter.channel.pv2-grid-energy-total.description = Total solar energy supplied by string #2 to grid. +thing-type.growatt.inverter.channel.pv2-potential.label = String #2 Voltage +thing-type.growatt.inverter.channel.pv2-potential.description = Voltage from solar panel string #2. +thing-type.growatt.inverter.channel.pv2-power.label = String #2 Power +thing-type.growatt.inverter.channel.pv2-power.description = Power from solar panel string #2. +thing-type.growatt.inverter.channel.pv2-temperature.label = Solar Panel Temperature #2 +thing-type.growatt.inverter.channel.pv2-temperature.description = Temperature of the solar panels (string #2). +thing-type.growatt.inverter.channel.rac.label = RAC +thing-type.growatt.inverter.channel.rac.description = RAC code. +thing-type.growatt.inverter.channel.sp-bus-potential.label = N Bus Voltage +thing-type.growatt.inverter.channel.sp-bus-potential.description = N Bus voltage. +thing-type.growatt.inverter.channel.sp-display-status.label = Solar Panel Display +thing-type.growatt.inverter.channel.sp-display-status.description = Solar panel display status code. +thing-type.growatt.inverter.channel.status.label = Inverter Status +thing-type.growatt.inverter.channel.status.description = Status code of the inverter (0=ready, 1=online, 2=fault). +thing-type.growatt.inverter.channel.system-fault-0.label = Fault Code #0 +thing-type.growatt.inverter.channel.system-fault-0.description = System fault code #0. +thing-type.growatt.inverter.channel.system-fault-1.label = Fault Code #1 +thing-type.growatt.inverter.channel.system-fault-1.description = System fault code #1. +thing-type.growatt.inverter.channel.system-fault-2.label = Fault Code #2 +thing-type.growatt.inverter.channel.system-fault-2.description = System fault code #2. +thing-type.growatt.inverter.channel.system-fault-3.label = Fault Code #3 +thing-type.growatt.inverter.channel.system-fault-3.description = System fault code #3. +thing-type.growatt.inverter.channel.system-fault-4.label = Fault Code #4 +thing-type.growatt.inverter.channel.system-fault-4.description = System fault code #4. +thing-type.growatt.inverter.channel.system-fault-5.label = Fault Code #5 +thing-type.growatt.inverter.channel.system-fault-5.description = System fault code #5. +thing-type.growatt.inverter.channel.system-fault-6.label = Fault Code #6 +thing-type.growatt.inverter.channel.system-fault-6.description = System fault code #6. +thing-type.growatt.inverter.channel.system-fault-7.label = Fault Code #7 +thing-type.growatt.inverter.channel.system-fault-7.description = System fault code #7. +thing-type.growatt.inverter.channel.system-work-mode.label = System Work Mode +thing-type.growatt.inverter.channel.system-work-mode.description = System work mode code. +thing-type.growatt.inverter.channel.temperature-4.label = Temp #4 +thing-type.growatt.inverter.channel.temperature-4.description = Temperature #4. +thing-type.growatt.inverter.channel.to-grid-energy-today.label = Energy to Grid Today +thing-type.growatt.inverter.channel.to-grid-energy-today.description = Energy supplied to grid today. +thing-type.growatt.inverter.channel.to-grid-energy-total.label = Total Energy to Grid +thing-type.growatt.inverter.channel.to-grid-energy-total.description = Total energy supplied to grid. +thing-type.growatt.inverter.channel.to-grid-power.label = Power to Grid +thing-type.growatt.inverter.channel.to-grid-power.description = Power supplied to grid. +thing-type.growatt.inverter.channel.to-grid-power-r.label = Power to Grid #R +thing-type.growatt.inverter.channel.to-grid-power-r.description = Power supplied to grid phase #R. +thing-type.growatt.inverter.channel.to-grid-power-s.label = Power to Grid #S +thing-type.growatt.inverter.channel.to-grid-power-s.description = Power supplied to grid phase #S. +thing-type.growatt.inverter.channel.to-grid-power-t.label = Power to Grid #T +thing-type.growatt.inverter.channel.to-grid-power-t.description = Power supplied to grid phase #T. +thing-type.growatt.inverter.channel.to-local-energy-today.label = Energy to Local Today +thing-type.growatt.inverter.channel.to-local-energy-today.description = Energy supplied to local today. +thing-type.growatt.inverter.channel.to-local-energy-total.label = Total Energy to Local +thing-type.growatt.inverter.channel.to-local-energy-total.description = Total energy supplied to local. +thing-type.growatt.inverter.channel.to-local-power.label = Power to Local +thing-type.growatt.inverter.channel.to-local-power.description = Power supplied to local. +thing-type.growatt.inverter.channel.to-local-power-r.label = Power to Local #R +thing-type.growatt.inverter.channel.to-local-power-r.description = Power supplied to local phase #R. +thing-type.growatt.inverter.channel.to-local-power-s.label = Power to Local #S +thing-type.growatt.inverter.channel.to-local-power-s.description = Power supplied to local phase #S. +thing-type.growatt.inverter.channel.to-local-power-t.label = Power to Local #T +thing-type.growatt.inverter.channel.to-local-power-t.description = Power supplied to local phase #T. +thing-type.growatt.inverter.channel.to-user-energy-today.label = Energy to User Today +thing-type.growatt.inverter.channel.to-user-energy-today.description = Energy supplied to user today. +thing-type.growatt.inverter.channel.to-user-energy-total.label = Total Energy to User +thing-type.growatt.inverter.channel.to-user-energy-total.description = Total energy supplied to user. +thing-type.growatt.inverter.channel.to-user-power.label = Power to User +thing-type.growatt.inverter.channel.to-user-power.description = Power supplied to user. +thing-type.growatt.inverter.channel.to-user-power-r.label = Power to User #R +thing-type.growatt.inverter.channel.to-user-power-r.description = Power supplied to user phase #R. +thing-type.growatt.inverter.channel.to-user-power-s.label = Power to User #S +thing-type.growatt.inverter.channel.to-user-power-s.description = Power supplied to user phase #S. +thing-type.growatt.inverter.channel.to-user-power-t.label = Power to User #T +thing-type.growatt.inverter.channel.to-user-power-t.description = Power supplied to user phase #T. +thing-type.growatt.inverter.channel.total-work-time.label = Total Working Time +thing-type.growatt.inverter.channel.total-work-time.description = Total inverter working time. + +# thing types config + +thing-type.config.growatt.inverter.deviceId.label = Device Id +thing-type.config.growatt.inverter.deviceId.description = Id of the inverter. + +# channel types + +channel-type.growatt.code.label = n +channel-type.growatt.frequency.label = Hz +channel-type.growatt.percent.label = Percent +channel-type.growatt.va.label = VA +channel-type.growatt.work-time.label = h diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 2ddc8b8e2fc7d..58482e6c80f0d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -317,8 +317,8 @@ - - Total inverter working time. + + Total inverter working time. @@ -481,7 +481,7 @@ Number:Dimensionless - + @@ -493,7 +493,7 @@ Number:Frequency - + @@ -505,7 +505,7 @@ Number:Time - + From 8780b0e018fdd0c71d0fc34494c86791f6ac6b0f Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 20 Jun 2023 17:26:57 +0100 Subject: [PATCH 009/146] [growatt] initialization and discovery Signed-off-by: Andrew Fiddian-Green --- .../config/GrowattInverterConfiguration.java | 2 + .../discovery/GrowattDiscoveryService.java | 63 ++++++++++ .../factory/GrowattHandlerFactory.java | 76 ++++++++++-- .../handler/GrowattBridgeHandler.java | 110 ++++++------------ .../handler/GrowattInverterHandler.java | 58 +++++---- .../internal/servlet/GrottHttpServlet.java | 86 ++++++++++++++ .../resources/OH-INF/i18n/growatt.properties | 4 + 7 files changed, 284 insertions(+), 115 deletions(-) create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java index 0bc78de7aa21e..b5621fe0905f1 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java @@ -22,5 +22,7 @@ @NonNullByDefault public class GrowattInverterConfiguration { + public static final String DEVICE_ID = "deviceId"; + public String deviceId = ""; } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java new file mode 100644 index 0000000000000..4c2c1d08d53c2 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.discovery; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.growatt.internal.GrowattBindingConstants; +import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingUID; + +/** + * The {@link GrowattDiscoveryService} does discovery for Growatt inverters. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattDiscoveryService extends AbstractDiscoveryService { + + private final Map> bridgeInverterIds = new ConcurrentHashMap<>(); + + public GrowattDiscoveryService() throws IllegalArgumentException { + super(Set.of(GrowattBindingConstants.THING_TYPE_INVERTER), 5, false); + } + + public void putInverters(ThingUID bridgeUID, Set inverterIds) { + if (inverterIds.isEmpty()) { + bridgeInverterIds.remove(bridgeUID); + } else { + bridgeInverterIds.put(bridgeUID, inverterIds); + startScan(); + } + } + + @Override + protected void startScan() { + bridgeInverterIds.forEach((bridgeUID, inverterIds) -> { + inverterIds.forEach(inverterId -> { + DiscoveryResult inverter = DiscoveryResultBuilder + .create(new ThingUID(GrowattBindingConstants.THING_TYPE_INVERTER, bridgeUID, inverterId)) + .withBridge(bridgeUID).withLabel("@text/discovery.growatt-inverter.label") + .withProperty(GrowattInverterConfiguration.DEVICE_ID, inverterId) + .withRepresentationProperty(GrowattInverterConfiguration.DEVICE_ID).build(); + thingDiscovered(inverter); + }); + }); + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index ecee03a252fa9..f592a8a5ae328 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -14,22 +14,36 @@ import static org.openhab.binding.growatt.internal.GrowattBindingConstants.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Hashtable; import java.util.Set; +import javax.servlet.ServletException; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.handler.GrowattBridgeHandler; import org.openhab.binding.growatt.internal.handler.GrowattInverterHandler; +import org.openhab.binding.growatt.internal.servlet.GrottHttpServlet; +import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link GrowattHandlerFactory} is responsible for creating things and thing @@ -42,16 +56,22 @@ public class GrowattHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_INVERTER); - private final HttpService httpService; + + private final Logger logger = LoggerFactory.getLogger(GrowattHandlerFactory.class); + + private final GrottHttpServlet httpServlet = new GrottHttpServlet(); + private final GrowattDiscoveryService discoveryService = new GrowattDiscoveryService(); + private final Set bridges = Collections.synchronizedSet(new HashSet<>()); + + private @Nullable ServiceRegistration discoveryServiceRegistration; @Activate public GrowattHandlerFactory(@Reference HttpService httpService) { - this.httpService = httpService; - } - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + try { + httpService.registerServlet(GrottHttpServlet.PATH, httpServlet, null, null); + } catch (ServletException | NamespaceException e) { + logger.warn("GrowattHandlerFactory() failed to register servlet", e); + } } @Override @@ -59,7 +79,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return new GrowattBridgeHandler((Bridge) thing, httpService); + discoveryRegister(); + bridges.add(thing.getUID()); + return new GrowattBridgeHandler((Bridge) thing, httpServlet, discoveryService); } if (THING_TYPE_INVERTER.equals(thingTypeUID)) { @@ -68,4 +90,42 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return null; } + + @Override + protected void deactivate(ComponentContext componentContext) { + discoveryUnregister(); + super.deactivate(componentContext); + } + + private void discoveryRegister() { + ServiceRegistration temp = discoveryServiceRegistration; + if (temp == null) { + temp = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()); + discoveryServiceRegistration = temp; + } + } + + private void discoveryUnregister() { + ServiceRegistration temp = discoveryServiceRegistration; + if (temp != null) { + temp.unregister(); + } + discoveryServiceRegistration = null; + } + + @Override + protected void removeHandler(ThingHandler thingHandler) { + if (thingHandler instanceof GrowattBridgeHandler) { + bridges.remove(thingHandler.getThing().getUID()); + if (bridges.isEmpty()) { + discoveryUnregister(); + } + } + super.removeHandler(thingHandler); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 97c818cff3a39..60b6b14747ea1 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -12,27 +12,18 @@ */ package org.openhab.binding.growatt.internal.handler; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.MediaType; +import java.util.HashMap; +import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.dto.GrottDevice; +import org.openhab.binding.growatt.internal.servlet.GrottHttpServlet; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.types.Command; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,54 +40,23 @@ @NonNullByDefault public class GrowattBridgeHandler extends BaseBridgeHandler { - private static final String GROWATT_SERVLET_PATH_ALIAS = "/growatt"; - - private static final String SERVLET_ONLINE_HTML = "" - // @formatter:off - + "" - + "" - + "

Growatt Binding

" - + "

 

" - + "

Servlet status: ONLINE

" - + "" - + ""; - // @formatter:off - - /** - * Inner servlet instance class to handle POST data from the Grott application. - */ - private class GrottServlet extends HttpServlet { - - private static final long serialVersionUID = 36178542423191036L; - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - response.setStatus(HttpServletResponse.SC_OK); - handleGrottContent(request.getContentLength() <= 0 ? "" - : new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { - response.setStatus(HttpServletResponse.SC_OK); - response.setContentType(MediaType.TEXT_HTML); - response.getWriter().write(SERVLET_ONLINE_HTML); - } - } - private final Logger logger = LoggerFactory.getLogger(GrowattBridgeHandler.class); private final Gson gson = new Gson(); - private final HttpService httpService; + private final GrowattDiscoveryService discoveryService; + private final Map inverters = new HashMap<>(); + private final GrottHttpServlet httpServlet; - public GrowattBridgeHandler(Bridge bridge, HttpService httpService) { + public GrowattBridgeHandler(Bridge bridge, GrottHttpServlet httpServlet, GrowattDiscoveryService discoveryService) { super(bridge); - this.httpService = httpService; + this.httpServlet = httpServlet; + this.discoveryService = discoveryService; } @Override public void dispose() { - httpService.unregister(GROWATT_SERVLET_PATH_ALIAS); + inverters.clear(); + httpServlet.handlerRemove(this); + discoveryService.putInverters(thing.getUID(), inverters.keySet()); } @Override @@ -105,50 +65,50 @@ public void handleCommand(ChannelUID channelUID, Command command) { } /** - * Process JSON content posted by the Grott application to our servlet. + * Process JSON content posted to the Grott application servlet. */ @SuppressWarnings("null") - protected void handleGrottContent(String json) { + public void handleGrottContent(String json) { logger.trace("handleGrottContent() json:{}", json); JsonElement jsonElement; try { jsonElement = JsonParser.parseString(json); + if (!jsonElement.isJsonObject()) { + throw new JsonSyntaxException("Unsupported element type"); + } } catch (JsonSyntaxException e) { logger.debug("handleGrottContent() invalid JSON string '{}'", json, e); return; } - List grottDevices = new ArrayList<>(); try { - if (jsonElement.isJsonObject()) { - GrottDevice device = gson.fromJson(jsonElement, GrottDevice.class); - if (device != null) { - grottDevices.add(device); - } - } else if (jsonElement.isJsonArray()) { - List devices = gson.fromJson(jsonElement, GrottDevice.GROTT_DEVICE_ARRAY); - if (devices != null) { - grottDevices.addAll(devices); - } - } else { - throw new JsonSyntaxException("Unsupported element type"); + GrottDevice inverter = gson.fromJson(jsonElement, GrottDevice.class); + if (inverter == null) { + throw new JsonSyntaxException("Inverter object is null"); } + putInverter(inverter); } catch (JsonSyntaxException e) { logger.debug("handleGrottContent() error parsing JSON '{}'", json, e); return; } getThing().getThings().stream().map(thing -> thing.getHandler()) .filter(handler -> (handler instanceof GrowattInverterHandler)) - .forEach(handler -> ((GrowattInverterHandler) handler).handleGrottDevices(grottDevices)); + .forEach(handler -> ((GrowattInverterHandler) handler).handleInverters(inverters.values())); } @Override public void initialize() { - try { - httpService.registerServlet(GROWATT_SERVLET_PATH_ALIAS, new GrottServlet(), null, null); - updateStatus(ThingStatus.ONLINE); - } catch (ServletException | NamespaceException e) { - logger.debug("initialize() exception '{}'", e.getMessage(), e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + httpServlet.handlerAdd(this); + updateStatus(ThingStatus.ONLINE); + } + + /** + * Put the given GrottDevice in our inverters map, and notify the discovery service if it was not already there. + * + * @param inverter a GrottDevice inverter object. + */ + private void putInverter(GrottDevice inverter) { + if (inverters.put(inverter.getDeviceId(), inverter) == null) { + discoveryService.putInverters(thing.getUID(), inverters.keySet()); } } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 8932d1755df50..06940d9bf0d69 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.growatt.internal.handler; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -53,31 +54,40 @@ public void handleCommand(ChannelUID channelUID, Command command) { } /** - * Receives a GrottDevice object containing data for this thing. Process the respective data values and update the - * channels accordingly. + * Receives a collection of GrottDevice inverter objects containing potential data for this thing. If the collection + * contains an entry matching the things's deviceId, and it contains GrottValues, then process it further. Otherwise + * go offline with a configuration error. * - * @param grottDevice a GrottDevice object containing the new status values. + * @param inverters collection of GrottDevice objects. */ - public void handleGrottDevice(GrottDevice grottDevice) { - GrottValues grottValues = grottDevice.getValues(); - if (grottValues == null) { - logger.warn("handleGrottDevice() device '{}' contains no values", grottDevice.getDeviceId()); - return; - } + public void handleInverters(Collection inverters) { + inverters.stream().filter(inverter -> deviceId.equals(inverter.getDeviceId())) + .map(inverter -> inverter.getValues()).filter(values -> values != null).findAny() + .ifPresentOrElse(values -> { + updateStatus(ThingStatus.ONLINE); + handleInverterValues(values); + }, () -> { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + }); + } + /** + * Receives a GrottValues object containing state values for this thing. Process the respective values and update + * the channels accordingly. + * + * @param inverter a GrottDevice object containing the new status values. + */ + public void handleInverterValues(GrottValues inverterValues) { // get channel states Map> channelStates; try { - channelStates = grottValues.getChannelStates(); + channelStates = inverterValues.getChannelStates(); } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) { - // should never happen since previously tested in JUnit tests - logger.warn("handleGrottDevice() unexpected exception:{}, message:{}", e.getClass().getName(), + logger.warn("handleInverterValues() unexpected exception:{}, message:{}", e.getClass().getName(), e.getMessage(), e); return; } - logger.debug("handleGrottDevice() channelStates size:{}", channelStates.size()); - // find unused channels List actualChannels = thing.getChannels(); List unusedChannels = actualChannels.stream() @@ -86,7 +96,7 @@ public void handleGrottDevice(GrottDevice grottDevice) { // remove unused channels if (!unusedChannels.isEmpty()) { updateThing(editThing().withoutChannels(unusedChannels).build()); - logger.debug("handleGrottDevice() channel count {} reduced by {} to {}", actualChannels.size(), + logger.debug("handleInverterValues() channel count {} reduced by {} to {}", actualChannels.size(), unusedChannels.size(), thing.getChannels().size()); } @@ -98,27 +108,11 @@ public void handleGrottDevice(GrottDevice grottDevice) { if (thingChannelIds.contains(channelId)) { updateState(channelId, state); } else { - logger.debug("handleGrottDevice() channel '{}' not found in thing", channelId); + logger.debug("handleInverterValues() channel '{}' not found; try re-creating the thing", channelId); } }); } - /** - * Receives a list of GrottDevice objects containing potential data for this thing. If the list contains any entry - * matching the things's deviceId then process it further. Otherwise go offline with a configuration error. - * - * @param grottDevices list of GrottDevice objects. - */ - public void handleGrottDevices(List grottDevices) { - grottDevices.stream().filter(grottDevice -> deviceId.equals(grottDevice.getDeviceId())).findAny() - .ifPresentOrElse(grottDevice -> { - updateStatus(ThingStatus.ONLINE); - handleGrottDevice(grottDevice); - }, () -> { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); - }); - } - @Override public void initialize() { GrowattInverterConfiguration config = getConfigAs(GrowattInverterConfiguration.class); diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java new file mode 100644 index 0000000000000..6231cf4783fcd --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.servlet; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.growatt.internal.handler.GrowattBridgeHandler; + +/** + * The {@link GrottHttpServlet} is an HttpServlet to handle data posted by the Grott application. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrottHttpServlet extends HttpServlet { + + public static final String PATH = "/growatt"; + + private static final String HTML = "" + // @formatter:off + + "" + + "" + + "

Growatt Binding Servlet

" + + "

 

" + + "

Status: %s

" + + "" + + ""; + // @formatter:on + + private static final String COLOR_READY = "ff6600"; + private static final String COLOR_ONLINE = "339966"; + private static final String MESSAGE_READY = "Ready"; + private static final String MESSAGE_ONLINE = "Bridge Online"; + + private static final long serialVersionUID = 36178542423191036L; + + private final Set handlers = Collections.synchronizedSet(new HashSet<>()); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType(MediaType.TEXT_HTML); + boolean online = handlers.size() > 0; + response.getWriter().write( + String.format(HTML, online ? COLOR_ONLINE : COLOR_READY, online ? MESSAGE_ONLINE : MESSAGE_READY)); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.setStatus(HttpServletResponse.SC_OK); + if (request.getContentLength() > 0) { + String content = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + handlers.forEach(handler -> handler.handleGrottContent(content)); + } + } + + public void handlerAdd(GrowattBridgeHandler handler) { + handlers.add(handler); + } + + public void handlerRemove(GrowattBridgeHandler handler) { + handlers.remove(handler); + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index e18522a7a848d..c503a9c2e51dd 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -216,3 +216,7 @@ channel-type.growatt.frequency.label = Hz channel-type.growatt.percent.label = Percent channel-type.growatt.va.label = VA channel-type.growatt.work-time.label = h + +# discovery + +discovery.growatt-inverter.label = Growatt inverter From 9b0203d3d47b7f7f8ac5f2eb141caed518ac7894 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 20 Jun 2023 17:52:50 +0100 Subject: [PATCH 010/146] [growatt] fix translation issue Signed-off-by: Andrew Fiddian-Green --- .../internal/discovery/GrowattDiscoveryService.java | 7 ++++++- .../growatt/internal/factory/GrowattHandlerFactory.java | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java index 4c2c1d08d53c2..aafd277d48cb5 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java @@ -22,6 +22,8 @@ import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.thing.ThingUID; /** @@ -34,8 +36,11 @@ public class GrowattDiscoveryService extends AbstractDiscoveryService { private final Map> bridgeInverterIds = new ConcurrentHashMap<>(); - public GrowattDiscoveryService() throws IllegalArgumentException { + public GrowattDiscoveryService(TranslationProvider i18nProvider, LocaleProvider localeProvider) + throws IllegalArgumentException { super(Set.of(GrowattBindingConstants.THING_TYPE_INVERTER), 5, false); + this.i18nProvider = i18nProvider; + this.localeProvider = localeProvider; } public void putInverters(ThingUID bridgeUID, Set inverterIds) { diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index f592a8a5ae328..6c2aaa2b6dac8 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -28,6 +28,8 @@ import org.openhab.binding.growatt.internal.handler.GrowattInverterHandler; import org.openhab.binding.growatt.internal.servlet.GrottHttpServlet; import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -60,13 +62,15 @@ public class GrowattHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(GrowattHandlerFactory.class); private final GrottHttpServlet httpServlet = new GrottHttpServlet(); - private final GrowattDiscoveryService discoveryService = new GrowattDiscoveryService(); + private final GrowattDiscoveryService discoveryService; private final Set bridges = Collections.synchronizedSet(new HashSet<>()); private @Nullable ServiceRegistration discoveryServiceRegistration; @Activate - public GrowattHandlerFactory(@Reference HttpService httpService) { + public GrowattHandlerFactory(@Reference TranslationProvider i18nProvider, @Reference LocaleProvider localeProvider, + @Reference HttpService httpService) { + discoveryService = new GrowattDiscoveryService(i18nProvider, localeProvider); try { httpService.registerServlet(GrottHttpServlet.PATH, httpServlet, null, null); } catch (ServletException | NamespaceException e) { From f9e6871d8d49df4adbb9b9ef1ae7e8bc2d7e86cc Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 20 Jun 2023 18:06:23 +0100 Subject: [PATCH 011/146] [growatt] tweaks to thing xml Signed-off-by: Andrew Fiddian-Green --- .../src/main/resources/OH-INF/thing/thing-types.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 58482e6c80f0d..091be32d41e7d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -482,30 +482,35 @@ Number:Dimensionless + Status Number:Dimensionless + Energy Number:Frequency + Energy Number:Power + Energy Number:Time + Time From fa416345ac08597c2c3960c52d0393a989feb4c3 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 20 Jun 2023 19:21:57 +0100 Subject: [PATCH 012/146] [growatt] improve documentation for grott Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 7c8692058f2fe..3936e627315e7 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -14,7 +14,8 @@ The binding supports two types of things: ## Discovery -There is no automatic discovery of the bridge or inverter things. +There is no automatic discovery of the bridge. +However if bridge exists and it receives inverter data, then a matching inverter thing is created in the Inbox. ## Grott Application @@ -29,19 +30,54 @@ _**NOTE**: make sure that the Grott application is fully operational for your in You should configure the Grott application via its `grott.ini` file. Configure Grott to match your inverter according to the [instructions](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor). -To operate with OpenHAB the recommended Grott configuration is as follows: - -- Configure Grott to run in proxy mode. -- Configure Grott to start as a service. -- Configure your inverter type in Grott. -- Install the `grottext.py` application extension in the Grott home folder. -- Configure the `grottext` extension's ip address, port, and path via `grott.ini` as follows: - -| Entry | Configuration Entry Value | -|---------|--------------------------------------------------| -| ip | IP address of your OpenHab computer. | -| port | Port of your OpenHab core server (usually 8080). | -| path | 'growatt' (fixed value). | + +### 1. Install Python + +If Python is not already installed on you computer, then istall it first. + +### 2. Install Grott + +First install the Grott application and application extension files in a Grott specific home folder. +The recommended Grott configuration for OpenHAB is as follows: + +- Create the Grott 'home' folder e.g. `/usr/bin/grott/`. +- Copy `grott.py`, `grottconf.py`, `grottdata.py`, `grottproxy.py`, `grottsniffer.py`, `grottserver.py` to the home folder. +- Copy `grottext.py` application extension to the home folder. +- Copy `grott.ini` configuration file to the home folder. +- Modify `grott.ini` to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; not run PVOutput; enable the `grottext` extension; and set the OpenHAB `/growatt` servlet url: + +```php +[Generic] +mode = proxy +compat = False +invtype = sph // or whatever + +[MQTT] +nomqtt = True + +[PVOutput] +pvoutput = False + +[extension] +extension = True +extname = grottext +extvar = {"url": "http://xxx.xxx.xxx.xxx:8080/growatt"} +``` + +### 3. Run Grott as a Service + +For best performance the Grott application should be started automatically as a service when your computer starts. + +- Copy the `grott.service` file to the `/etc/systemd/system/` folder +- Modify `grott.service` to enter your user name; the Grott settings; the path to Phyton; and the path to the Grott application: + +```php +[Service] +SyslogIdentifier=grott +User=openhabian // your user name +WorkingDirectory=/usr/bin/grott/ +ExecStart=-/usr/bin/python3 -u /usr/bin/grott/grott.py -v +``` ## Thing Configuration From 345d8bc6e31f3d81586f905b28e93887b9e0e3bc Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 21 Jun 2023 13:13:33 +0100 Subject: [PATCH 013/146] [growatt] make some channels advanced Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 198 ++++++++--------- .../internal/GrowattBindingConstants.java | 2 +- .../growatt/internal/dto/GrottValues.java | 2 +- .../resources/OH-INF/i18n/growatt.properties | 21 +- .../resources/OH-INF/thing/thing-types.xml | 199 +++++++++++------- .../binding/growatt/test/GrowattTest.java | 2 +- 6 files changed, 240 insertions(+), 184 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 3936e627315e7..8814d5bbdb48f 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -98,105 +98,105 @@ All channels are read only. Depending on the inverter model, and it configuration, not all of the channels will be present. The list of all possible channels is as follows: -| Channel | Type | Description | -|--------------------------------- |-------------------------------|-----------------------------------------------------------| -| status | Number:Dimensionless | Status code of the inverter (0=ready, 1=online, 2=fault). | -| pv-power-in | Number:Power | Total solar input power. | -| pv-power-out | Number:Power | Total solar output power. | -| pv1-potential | Number:ElectricPotential | Voltage from solar panel string #1. | -| pv2-potential | Number:ElectricPotential | Voltage from solar panel string #2. | -| pv1-current | Number:ElectricCurrent | Current from solar panel string #1. | -| pv2-current | Number:ElectricCurrent | Current from solar panel string #2. | -| pv1-power | Number:Power | Power from solar panel string #1. | -| pv2-power | Number:Power | Power from solar panel string #2. | -| grid-frequency | Number:Frequency | Frequency of the grid. | -| grid-potential | Number:ElectricPotential | Voltage of the grid (phase #R). | -| grid-potential-s | Number:ElectricPotential | Voltage of the grid phase #S. | -| grid-potential-t | Number:ElectricPotential | Voltage of the grid phase #T. | -| grid-potential-rs | Number:ElectricPotential | Voltage of the grid phases #RS. | -| grid-potential-st | Number:ElectricPotential | Voltage of the grid phases #ST. | -| grid-potential-tr | Number:ElectricPotential | Voltage of the grid phases #TR. | -| grid-current | Number:ElectricCurrent | Current delivered to the grid (phase #R). | -| grid-current-s | Number:ElectricCurrent | Current delivered to the grid phase #S. | -| grid-current-t | Number:ElectricCurrent | Current delivered to the grid phase #T. | -| grid-power | Number:Power | Power delivered to the grid (phase #R). | -| grid-power-s | Number:Power | Power delivered to the grid phase #S. | -| grid-power-t | Number:Power | Power delivered to the grid phase #T. | -| grid-va | Number:Power | VA delivered to the grid. | -| grid-charge-current | Number:ElectricCurrent | Grid current to charge battery. | -| grid-charge-power | Number:Power | Grid power to charge battery. | -| grid-charge-va | Number:Power | Grid VA to charge battery. | -| grid-discharge-power | Number:Power | Grid power from discharge of battery. | -| grid-discharge-va | Number:Power | Grid VA from discharge of battery. | -| battery-charge-power | Number:Power | Battery charge power. | -| battery-discharge-power | Number:Power | Battery discharge power. | -| battery-discharge-va | Number:Power | Battery discharge VA. | -| to-grid-power | Number:Power | Power supplied to grid. | -| to-grid-power-r | Number:Power | Power supplied to grid phase #R. | -| to-grid-power-s | Number:Power | Power supplied to grid phase #S. | -| to-grid-power-t | Number:Power | Power supplied to grid phase #T. | -| to-user-power | Number:Power | Power supplied to user. | -| to-user-power-r | Number:Power | Power supplied to user phase #R. | -| to-user-power-s | Number:Power | Power supplied to user phase #S. | -| to-user-power-t | Number:Power | Power supplied to user phase #T. | -| to-local-power | Number:Power | Power supplied to local. | -| to-local-power-r | Number:Power | Power supplied to local phase #R. | -| to-local-power-s | Number:Power | Power supplied to local phase #S. | -| to-local-power-t | Number:Power | Power supplied to local phase #T. | -| pv-energy-today | Number:Energy | Solar energy collected today. | -| pv-energy-total | Number:Energy | Total solar energy collected. | -| pv-grid-energy-today | Number:Energy | Solar energy supplied to grid today. | -| pv1-grid-energy-today | Number:Energy | Solar energy supplied by string #1 to grid today. | -| pv2-grid-energy-today | Number:Energy | Solar energy supplied by string #2 to grid today. | -| pv-grid-energy-total | Number:Energy | Total solar energy supplied to grid. | -| pv1-grid-energy-total | Number:Energy | Total solar energy supplied by string #1 to grid . | -| pv2-grid-energy-total | Number:Energy | Total solar energy supplied by string #2 to grid. | -| to-grid-energy-today | Number:Energy | Energy supplied to grid today. | -| to-grid-energy-total | Number:Energy | Total energy supplied to grid. | -| to-user-energy-today | Number:Energy | Energy supplied to user today. | -| to-user-energy-total | Number:Energy | Total energy supplied to user. | -| to-local-energy-today | Number:Energy | Energy supplied to local today. | -| to-local-energy-total | Number:Energy | Total energy supplied to local. | -| grid-charge-energy-today | Number:Energy | Energy used to charge battery today. | -| grid-charge-energy-total | Number:Energy | Total energy used to charge battery. | -| grid-discharge-energy-today | Number:Energy | Grid energy produced from battery today. | -| grid-discharge-energy-total | Number:Energy | Total grid energy produced from battery. | -| battery-discharge-energy-today | Number:Energy | Energy consumed from battery. | -| battery-discharge-energy-total | Number:Energy | Total energy consumed from battery. | -| total-work-time | Number:Time | Total work time of the system. | -| p-bus-potential | Number:ElectricPotential | P Bus voltage. | -| n-bus-potential | Number:ElectricPotential | N Bus voltage. | -| sp-bus-potential | Number:ElectricPotential | N Bus voltage. | -| pv-temperature | Number:Temperature | Temperature of the solar panels (string #1). | -| pv-ipm-temperature | Number:Temperature | Temperature of the IPM. | -| pv-boost-temperature | Number:Temperature | Boost temperature. | -| temperature-4 | Number:Temperature | Temperature #4. | -| pv2-temperature | Number:Temperature | Temperature of the solar panels (string #2). | -| battery-type | Number:Dimensionless | Type code of the battery. | -| battery-temperature | Number:Temperature | Battery temperature. | -| battery-potential | Number:ElectricPotential | Battery voltage. | -| battery-display | Number:Dimensionless | Battery display code. | -| battery-soc | Number:Dimensionless | Battery SOC code. | -| system-fault-0 | Number:Dimensionless | System fault code #0. | -| system-fault-1 | Number:Dimensionless | System fault code #1. | -| system-fault-2 | Number:Dimensionless | System fault code #2. | -| system-fault-3 | Number:Dimensionless | System fault code #3. | -| system-fault-4 | Number:Dimensionless | System fault code #4. | -| system-fault-5 | Number:Dimensionless | System fault code #5. | -| system-fault-6 | Number:Dimensionless | System fault code #6. | -| system-fault-7 | Number:Dimensionless | System fault code #7. | -| system-work-mode | Number:Dimensionless | System work mode code. | -| sp-display-status | Number:Dimensionless | Solar panel display status code. | -| constant-power-ok | Number:Dimensionless | Constant power OK code. | -| rac | Number:Dimensionless | RAC code. | -| erac-today | Number:Dimensionless | ERAC count today. | -| erac-total | Number:Dimensionless | Total ERAC count. | -| output-potential | Number:ElectricPotential | Output voltage. | -| output-frequency | Number:Frequency | Output frequency. | -| load-percent | Number:Dimensionless | Percent of full load. | -| inverter-current | Number:ElectricCurrent | Inverter current. | -| grid-input-power | Number:Power | Grid input power. | -| grid-input-va | Number:Power | Grid input VA. | +| Channel | Type | Description | Advanced | +|--------------------------------- |-------------------------------|-----------------------------------------------------------|----------| +| system-status | Number:Dimensionless | Status code of the inverter (0=ready, 1=online, 2=fault). | | +| pv-power-in | Number:Power | Total solar input power. | | +| pv-power-out | Number:Power | Total solar output power. | | +| pv1-potential | Number:ElectricPotential | Voltage from solar panel string #1. | yes | +| pv2-potential | Number:ElectricPotential | Voltage from solar panel string #2. | yes | +| pv1-current | Number:ElectricCurrent | Current from solar panel string #1. | yes | +| pv2-current | Number:ElectricCurrent | Current from solar panel string #2. | yes | +| pv1-power | Number:Power | Power from solar panel string #1. | yes | +| pv2-power | Number:Power | Power from solar panel string #2. | yes | +| grid-frequency | Number:Frequency | Frequency of the grid. | yes | +| grid-potential | Number:ElectricPotential | Voltage of the grid (phase #R). | | +| grid-potential-s | Number:ElectricPotential | Voltage of the grid phase #S. | yes | +| grid-potential-t | Number:ElectricPotential | Voltage of the grid phase #T. | yes | +| grid-potential-rs | Number:ElectricPotential | Voltage of the grid phases #RS. | yes | +| grid-potential-st | Number:ElectricPotential | Voltage of the grid phases #ST. | yes | +| grid-potential-tr | Number:ElectricPotential | Voltage of the grid phases #TR. | yes | +| grid-current | Number:ElectricCurrent | Current delivered to the grid (phase #R). | yes | +| grid-current-s | Number:ElectricCurrent | Current delivered to the grid phase #S. | yes | +| grid-current-t | Number:ElectricCurrent | Current delivered to the grid phase #T. | yes | +| grid-power | Number:Power | Power delivered to the grid (phase #R). | | +| grid-power-s | Number:Power | Power delivered to the grid phase #S. | yes | +| grid-power-t | Number:Power | Power delivered to the grid phase #T. | yes | +| grid-va | Number:Power | VA delivered to the grid. | yes | +| grid-charge-current | Number:ElectricCurrent | Grid current to charge battery. | | +| grid-charge-power | Number:Power | Grid power to charge battery. | | +| grid-charge-va | Number:Power | Grid VA to charge battery. | yes | +| grid-discharge-power | Number:Power | Grid power from discharge of battery. | | +| grid-discharge-va | Number:Power | Grid VA from discharge of battery. | yes | +| battery-charge-power | Number:Power | Battery charge power. | | +| battery-discharge-power | Number:Power | Battery discharge power. | | +| battery-discharge-va | Number:Power | Battery discharge VA. | yes | +| to-grid-power | Number:Power | Power supplied to grid. | | +| to-grid-power-r | Number:Power | Power supplied to grid phase #R. | yes | +| to-grid-power-s | Number:Power | Power supplied to grid phase #S. | yes | +| to-grid-power-t | Number:Power | Power supplied to grid phase #T. | yes | +| to-user-power | Number:Power | Power supplied to user. | | +| to-user-power-r | Number:Power | Power supplied to user phase #R. | yes | +| to-user-power-s | Number:Power | Power supplied to user phase #S. | yes | +| to-user-power-t | Number:Power | Power supplied to user phase #T. | yes | +| to-local-power | Number:Power | Power supplied to local. | | +| to-local-power-r | Number:Power | Power supplied to local phase #R. | yes | +| to-local-power-s | Number:Power | Power supplied to local phase #S. | yes | +| to-local-power-t | Number:Power | Power supplied to local phase #T. | yes | +| pv-energy-today | Number:Energy | Solar energy collected today. | | +| pv-energy-total | Number:Energy | Total solar energy collected. | | +| pv-grid-energy-today | Number:Energy | Solar energy supplied to grid today. | | +| pv1-grid-energy-today | Number:Energy | Solar energy supplied by string #1 to grid today. | yes | +| pv2-grid-energy-today | Number:Energy | Solar energy supplied by string #2 to grid today. | yes | +| pv-grid-energy-total | Number:Energy | Total solar energy supplied to grid. | | +| pv1-grid-energy-total | Number:Energy | Total solar energy supplied by string #1 to grid . | yes | +| pv2-grid-energy-total | Number:Energy | Total solar energy supplied by string #2 to grid. | yes | +| to-grid-energy-today | Number:Energy | Energy supplied to grid today. | | +| to-grid-energy-total | Number:Energy | Total energy supplied to grid. | | +| to-user-energy-today | Number:Energy | Energy supplied to user today. | | +| to-user-energy-total | Number:Energy | Total energy supplied to user. | | +| to-local-energy-today | Number:Energy | Energy supplied to local today. | | +| to-local-energy-total | Number:Energy | Total energy supplied to local. | | +| grid-charge-energy-today | Number:Energy | Energy used to charge battery today. | | +| grid-charge-energy-total | Number:Energy | Total energy used to charge battery. | | +| grid-discharge-energy-today | Number:Energy | Grid energy produced from battery today. | | +| grid-discharge-energy-total | Number:Energy | Total grid energy produced from battery. | | +| battery-discharge-energy-today | Number:Energy | Energy consumed from battery. | | +| battery-discharge-energy-total | Number:Energy | Total energy consumed from battery. | | +| total-work-time | Number:Time | Total work time of the system. | yes | +| p-bus-potential | Number:ElectricPotential | P Bus voltage. | yes | +| n-bus-potential | Number:ElectricPotential | N Bus voltage. | yes | +| sp-bus-potential | Number:ElectricPotential | N Bus voltage. | yes | +| pv-temperature | Number:Temperature | Temperature of the solar panels (string #1). | yes | +| pv-ipm-temperature | Number:Temperature | Temperature of the IPM. | yes | +| pv-boost-temperature | Number:Temperature | Boost temperature. | yes | +| temperature-4 | Number:Temperature | Temperature #4. | yes | +| pv2-temperature | Number:Temperature | Temperature of the solar panels (string #2). | yes | +| battery-type | Number:Dimensionless | Type code of the battery. | yes | +| battery-temperature | Number:Temperature | Battery temperature. | yes | +| battery-potential | Number:ElectricPotential | Battery voltage. | yes | +| battery-display | Number:Dimensionless | Battery display code. | yes | +| battery-soc | Number:Dimensionless | Battery SOC code. | yes | +| system-fault-0 | Number:Dimensionless | System fault code #0. | yes | +| system-fault-1 | Number:Dimensionless | System fault code #1. | yes | +| system-fault-2 | Number:Dimensionless | System fault code #2. | yes | +| system-fault-3 | Number:Dimensionless | System fault code #3. | yes | +| system-fault-4 | Number:Dimensionless | System fault code #4. | yes | +| system-fault-5 | Number:Dimensionless | System fault code #5. | yes | +| system-fault-6 | Number:Dimensionless | System fault code #6. | yes | +| system-fault-7 | Number:Dimensionless | System fault code #7. | yes | +| system-work-mode | Number:Dimensionless | System work mode code. | yes | +| sp-display-status | Number:Dimensionless | Solar panel display status code. | yes | +| constant-power-ok | Number:Dimensionless | Constant power OK code. | yes | +| rac | Number:Dimensionless | RAC code. | yes | +| erac-today | Number:Dimensionless | ERAC count today. | yes | +| erac-total | Number:Dimensionless | Total ERAC count. | yes | +| output-potential | Number:ElectricPotential | Output voltage. (Duplicate?) | | +| output-frequency | Number:Frequency | Output frequency. (Duplicate?) | yes | +| load-percent | Number:Dimensionless | Percent of full load. | yes | +| inverter-current | Number:ElectricCurrent | Inverter current. (Duplicate?) | | +| grid-input-power | Number:Power | Grid input power. (Duplicate?) | | +| grid-input-va | Number:Power | Grid input VA. (Duplicate?) | yes | ## Full Example diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java index eb7b2e11bdc92..46ceb9b4f851c 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -57,7 +57,7 @@ public UoM(Unit units, float divisor) { */ public static final Map CHANNEL_ID_UOM_MAP = Map.ofEntries( // inverter state - new AbstractMap.SimpleEntry("status", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-status", new UoM(Units.ONE, 1)), // solar generation new AbstractMap.SimpleEntry("pv-power-in", new UoM(Units.WATT, 10)), diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index b9c848026369d..40e31b1d05110 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -50,7 +50,7 @@ public static String getFieldName(String channelId) { // @formatter:off // inverter state - public @Nullable @SerializedName(value = "pvstatus") Integer status; + public @Nullable @SerializedName(value = "pvstatus") Integer system_status; // solar generation public @Nullable @SerializedName(value = "pvpowerin") Integer pv_power_in; diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index c503a9c2e51dd..792e9ac5114e2 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -143,8 +143,6 @@ thing-type.growatt.inverter.channel.sp-bus-potential.label = N Bus Voltage thing-type.growatt.inverter.channel.sp-bus-potential.description = N Bus voltage. thing-type.growatt.inverter.channel.sp-display-status.label = Solar Panel Display thing-type.growatt.inverter.channel.sp-display-status.description = Solar panel display status code. -thing-type.growatt.inverter.channel.status.label = Inverter Status -thing-type.growatt.inverter.channel.status.description = Status code of the inverter (0=ready, 1=online, 2=fault). thing-type.growatt.inverter.channel.system-fault-0.label = Fault Code #0 thing-type.growatt.inverter.channel.system-fault-0.description = System fault code #0. thing-type.growatt.inverter.channel.system-fault-1.label = Fault Code #1 @@ -161,6 +159,8 @@ thing-type.growatt.inverter.channel.system-fault-6.label = Fault Code #6 thing-type.growatt.inverter.channel.system-fault-6.description = System fault code #6. thing-type.growatt.inverter.channel.system-fault-7.label = Fault Code #7 thing-type.growatt.inverter.channel.system-fault-7.description = System fault code #7. +thing-type.growatt.inverter.channel.system-status.label = Inverter Status +thing-type.growatt.inverter.channel.system-status.description = Status code of the inverter (0=ready, 1=online, 2=fault). thing-type.growatt.inverter.channel.system-work-mode.label = System Work Mode thing-type.growatt.inverter.channel.system-work-mode.description = System work mode code. thing-type.growatt.inverter.channel.temperature-4.label = Temp #4 @@ -211,11 +211,18 @@ thing-type.config.growatt.inverter.deviceId.description = Id of the inverter. # channel types -channel-type.growatt.code.label = n -channel-type.growatt.frequency.label = Hz -channel-type.growatt.percent.label = Percent -channel-type.growatt.va.label = VA -channel-type.growatt.work-time.label = h +channel-type.growatt.advanced-electric-current.label = advanced-electric-current +channel-type.growatt.advanced-electric-potential.label = advanced-electric-potential +channel-type.growatt.advanced-electric-power.label = advanced-electric-power +channel-type.growatt.advanced-electrical-energy.label = advanced-electrical-energy +channel-type.growatt.advanced-fault-code.label = advanced-fault-code +channel-type.growatt.advanced-frequency.label = advanced-frequency +channel-type.growatt.advanced-outdoor-temperature.label = advanced-outdoor-temperature +channel-type.growatt.advanced-percent.label = advanced-percent +channel-type.growatt.advanced-status-code.label = advanced-status-code +channel-type.growatt.advanced-va.label = advanced-va +channel-type.growatt.advanced-work-time.label = advanced-work-time +channel-type.growatt.system-status-code.label = system-status # discovery diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 091be32d41e7d..802bc53a6fc38 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -21,7 +21,7 @@ - + Status code of the inverter (0=ready, 1=online, 2=fault). @@ -37,35 +37,35 @@ - + Voltage from solar panel string #1. - + Voltage from solar panel string #2. - + Current from solar panel string #1. - + Current from solar panel string #2. - + Power from solar panel string #1. - + Power from solar panel string #2. - + Frequency of the grid. @@ -73,23 +73,23 @@ Voltage of the grid (phase #R). - + Voltage of the grid phase #S. - + Voltage of the grid phase #T. - + Voltage of the grid phases #RS. - + Voltage of the grid phases #ST. - + Voltage of the grid phases #TR. @@ -99,11 +99,11 @@ Current delivered to the grid (phase #R). - + Current delivered to the grid phase #S. - + Current delivered to the grid phase #T. @@ -112,16 +112,16 @@ Power delivered to the grid (phase #R). - + Power delivered to the grid phase #S. - + Power delivered to the grid phase #T. - + VA delivered to the grid. @@ -135,7 +135,7 @@ Grid power to charge battery. - + Grid VA to charge battery. @@ -145,7 +145,7 @@ Grid power from discharge of battery. - + Grid VA from discharge of battery. @@ -159,7 +159,7 @@ Battery discharge power. - + Battery discharge VA. @@ -169,15 +169,15 @@ Power supplied to grid. - + Power supplied to grid phase #R. - + Power supplied to grid phase #S. - + Power supplied to grid phase #T. @@ -187,15 +187,15 @@ Power supplied to user. - + Power supplied to user phase #R. - + Power supplied to user phase #S. - + Power supplied to user phase #T. @@ -205,15 +205,15 @@ Power supplied to local. - + Power supplied to local phase #R. - + Power supplied to local phase #S. - + Power supplied to local phase #T. @@ -233,11 +233,11 @@ Solar energy supplied to grid today. - + Solar energy supplied by string #1 to grid today. - + Solar energy supplied by string #2 to grid today. @@ -246,11 +246,11 @@ Total solar energy supplied to grid. - + Total solar energy supplied by string #1 to grid . - + Total solar energy supplied by string #2 to grid. @@ -316,128 +316,128 @@ - + Total inverter working time. - + P Bus voltage. - + N Bus voltage. - + N Bus voltage. - + Temperature of the solar panels (string #1). - + Temperature of the IPM. - + Boost temperature. - + Temperature #4. - + Temperature of the solar panels (string #2). - + Type code of the battery. - + Battery temperature. - + Battery voltage. - + Battery display code. - + Battery SOC code. - + System fault code #0. - + System fault code #1. - + System fault code #2. - + System fault code #3. - + System fault code #4. - + System fault code #5. - + System fault code #6. - + System fault code #7. - + System work mode code. - + Solar panel display status code. - + Constant power OK code. - + RAC code. - + ERAC count today. - + Total ERAC count. @@ -447,11 +447,11 @@ Output voltage. - + Output frequency. - + Percent of full load. @@ -463,7 +463,7 @@ Grid input power. - + Grid input VA. @@ -479,39 +479,88 @@ - + Number:Dimensionless - + Status - + Number:Dimensionless - + + Settings + + + + + Number:Dimensionless + + Siren + + + + + Number:Dimensionless + Energy - + Number:Frequency - + Energy - + Number:Power - + Energy - + Number:Time - + Time + + Number:Power + + Energy + + + + + Number:ElectricCurrent + + Energy + + + + + Number:ElectricPotential + + Energy + + + + + Number:Energy + + Energy + + + + + Number:Temperature + + Temperature + + + diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index fad2b66cc53b1..4db72292eb3f2 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -150,7 +150,7 @@ void testGrottValuesContents() { assertTrue(state instanceof QuantityType); }); - assertEquals(QuantityType.ONE, channelStates.get("status")); + assertEquals(QuantityType.ONE, channelStates.get("system-status")); assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("grid-potential")); assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("grid-current")); assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("grid-power")); From aaddb72f80007088c08f1993781866b13fc4b678 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 21 Jun 2023 14:47:30 +0100 Subject: [PATCH 014/146] [growatt] cleanup discovery & factory; edit .xml .properties files Signed-off-by: Andrew Fiddian-Green --- .../discovery/GrowattDiscoveryService.java | 2 +- .../factory/GrowattHandlerFactory.java | 39 +++++++++++++------ .../handler/GrowattInverterHandler.java | 2 +- .../resources/OH-INF/i18n/growatt.properties | 12 ++++-- .../resources/OH-INF/thing/thing-types.xml | 26 ++++++------- 5 files changed, 50 insertions(+), 31 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java index aafd277d48cb5..52580cc217797 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java @@ -58,7 +58,7 @@ protected void startScan() { inverterIds.forEach(inverterId -> { DiscoveryResult inverter = DiscoveryResultBuilder .create(new ThingUID(GrowattBindingConstants.THING_TYPE_INVERTER, bridgeUID, inverterId)) - .withBridge(bridgeUID).withLabel("@text/discovery.growatt-inverter.label") + .withBridge(bridgeUID).withLabel("@text/discovery.growatt-inverter") .withProperty(GrowattInverterConfiguration.DEVICE_ID, inverterId) .withRepresentationProperty(GrowattInverterConfiguration.DEVICE_ID).build(); thingDiscovered(inverter); diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index 6c2aaa2b6dac8..aacfef82aaeb5 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -17,6 +17,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Hashtable; +import java.util.Objects; import java.util.Set; import javax.servlet.ServletException; @@ -61,16 +62,21 @@ public class GrowattHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(GrowattHandlerFactory.class); - private final GrottHttpServlet httpServlet = new GrottHttpServlet(); - private final GrowattDiscoveryService discoveryService; + private final HttpService httpService; + private final TranslationProvider i18nProvider; + private final LocaleProvider localeProvider; private final Set bridges = Collections.synchronizedSet(new HashSet<>()); + private final GrottHttpServlet httpServlet = new GrottHttpServlet(); + private @Nullable GrowattDiscoveryService discoveryService; private @Nullable ServiceRegistration discoveryServiceRegistration; @Activate - public GrowattHandlerFactory(@Reference TranslationProvider i18nProvider, @Reference LocaleProvider localeProvider, - @Reference HttpService httpService) { - discoveryService = new GrowattDiscoveryService(i18nProvider, localeProvider); + public GrowattHandlerFactory(@Reference HttpService httpService, @Reference TranslationProvider i18nProvider, + @Reference LocaleProvider localeProvider) { + this.httpService = httpService; + this.i18nProvider = i18nProvider; + this.localeProvider = localeProvider; try { httpService.registerServlet(GrottHttpServlet.PATH, httpServlet, null, null); } catch (ServletException | NamespaceException e) { @@ -85,7 +91,8 @@ public GrowattHandlerFactory(@Reference TranslationProvider i18nProvider, @Refer if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { discoveryRegister(); bridges.add(thing.getUID()); - return new GrowattBridgeHandler((Bridge) thing, httpServlet, discoveryService); + return new GrowattBridgeHandler((Bridge) thing, Objects.requireNonNull(httpServlet), + Objects.requireNonNull(discoveryService)); } if (THING_TYPE_INVERTER.equals(thingTypeUID)) { @@ -97,24 +104,32 @@ public GrowattHandlerFactory(@Reference TranslationProvider i18nProvider, @Refer @Override protected void deactivate(ComponentContext componentContext) { + bridges.clear(); discoveryUnregister(); + httpService.unregister(GrottHttpServlet.PATH); super.deactivate(componentContext); } private void discoveryRegister() { - ServiceRegistration temp = discoveryServiceRegistration; + GrowattDiscoveryService discoveryService = this.discoveryService; + if (discoveryService == null) { + discoveryService = new GrowattDiscoveryService(i18nProvider, localeProvider); + this.discoveryService = discoveryService; + } + ServiceRegistration temp = this.discoveryServiceRegistration; if (temp == null) { temp = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()); - discoveryServiceRegistration = temp; + this.discoveryServiceRegistration = temp; } } private void discoveryUnregister() { - ServiceRegistration temp = discoveryServiceRegistration; - if (temp != null) { - temp.unregister(); + ServiceRegistration discoveryServiceRegistration = this.discoveryServiceRegistration; + if (discoveryServiceRegistration != null) { + discoveryServiceRegistration.unregister(); } - discoveryServiceRegistration = null; + this.discoveryService = null; + this.discoveryServiceRegistration = null; } @Override diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 06940d9bf0d69..2e78c5c577c0d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -117,7 +117,7 @@ public void handleInverterValues(GrottValues inverterValues) { public void initialize() { GrowattInverterConfiguration config = getConfigAs(GrowattInverterConfiguration.class); deviceId = config.deviceId; - updateStatus(ThingStatus.UNKNOWN); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NOT_YET_READY, "@text/status.awaiting-data"); logger.debug("initialize() thing has {} channels", thing.getChannels().size()); } } diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 792e9ac5114e2..c3961eb41b486 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -212,18 +212,22 @@ thing-type.config.growatt.inverter.deviceId.description = Id of the inverter. # channel types channel-type.growatt.advanced-electric-current.label = advanced-electric-current -channel-type.growatt.advanced-electric-potential.label = advanced-electric-potential +channel-type.growatt.advanced-electric-frequency.label = advanced-electric-frequency channel-type.growatt.advanced-electric-power.label = advanced-electric-power +channel-type.growatt.advanced-electric-va.label = advanced-electric-va +channel-type.growatt.advanced-electric-voltage.label = advanced-electric-voltage channel-type.growatt.advanced-electrical-energy.label = advanced-electrical-energy channel-type.growatt.advanced-fault-code.label = advanced-fault-code -channel-type.growatt.advanced-frequency.label = advanced-frequency channel-type.growatt.advanced-outdoor-temperature.label = advanced-outdoor-temperature channel-type.growatt.advanced-percent.label = advanced-percent channel-type.growatt.advanced-status-code.label = advanced-status-code -channel-type.growatt.advanced-va.label = advanced-va channel-type.growatt.advanced-work-time.label = advanced-work-time channel-type.growatt.system-status-code.label = system-status # discovery -discovery.growatt-inverter.label = Growatt inverter +discovery.growatt-inverter = Growatt Inverter + +# thing status + +status.awaiting-data = Waiting for data from Grott application diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 802bc53a6fc38..f7cc17a147bae 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -65,7 +65,7 @@ - + Frequency of the grid. @@ -121,7 +121,7 @@ Power delivered to the grid phase #T. - + VA delivered to the grid. @@ -135,7 +135,7 @@ Grid power to charge battery. - + Grid VA to charge battery. @@ -145,7 +145,7 @@ Grid power from discharge of battery. - + Grid VA from discharge of battery. @@ -159,7 +159,7 @@ Battery discharge power. - + Battery discharge VA. @@ -447,7 +447,7 @@ Output voltage. - + Output frequency. @@ -463,7 +463,7 @@ Grid input power. - + Grid input VA. @@ -507,16 +507,16 @@ - + Number:Frequency - + Energy - + Number:Power - + Energy @@ -542,9 +542,9 @@ - + Number:ElectricPotential - + Energy From cda56c4109d09a28e65bd7265efa1372b2492d3c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 21 Jun 2023 16:49:06 +0100 Subject: [PATCH 015/146] [growatt] state of charge Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 2 +- .../binding/growatt/internal/GrowattBindingConstants.java | 4 ++-- .../src/main/resources/OH-INF/i18n/growatt.properties | 4 ++-- .../src/main/resources/OH-INF/thing/thing-types.xml | 7 +++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 8814d5bbdb48f..13a24523969a4 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -176,7 +176,7 @@ The list of all possible channels is as follows: | battery-temperature | Number:Temperature | Battery temperature. | yes | | battery-potential | Number:ElectricPotential | Battery voltage. | yes | | battery-display | Number:Dimensionless | Battery display code. | yes | -| battery-soc | Number:Dimensionless | Battery SOC code. | yes | +| battery-soc | Number:Dimensionless | Battery State of Charge percent. | yes | | system-fault-0 | Number:Dimensionless | System fault code #0. | yes | | system-fault-1 | Number:Dimensionless | System fault code #1. | yes | | system-fault-2 | Number:Dimensionless | System fault code #2. | yes | diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java index 46ceb9b4f851c..02b086fa79bcc 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -184,7 +184,7 @@ public UoM(Unit units, float divisor) { new AbstractMap.SimpleEntry("battery-potential", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("battery-temperature", new UoM(SIUnits.CELSIUS, 10)), new AbstractMap.SimpleEntry("battery-display", new UoM(Units.ONE, 10)), - new AbstractMap.SimpleEntry("battery-soc", new UoM(Units.ONE, 100)), + new AbstractMap.SimpleEntry("battery-soc", new UoM(Units.PERCENT, 1)), // fault codes new AbstractMap.SimpleEntry("system-fault-0", new UoM(Units.ONE, 1)), @@ -209,7 +209,7 @@ public UoM(Unit units, float divisor) { // duplicates ?? new AbstractMap.SimpleEntry("output-potential", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("output-frequency", new UoM(Units.HERTZ, 100)), - new AbstractMap.SimpleEntry("load-percent", new UoM(Units.ONE, 10)), + new AbstractMap.SimpleEntry("load-percent", new UoM(Units.PERCENT, 10)), new AbstractMap.SimpleEntry("inverter-current", new UoM(Units.AMPERE, 10)), new AbstractMap.SimpleEntry("grid-input-power", new UoM(Units.WATT, 10)), new AbstractMap.SimpleEntry("grid-input-va", new UoM(Units.VOLT_AMPERE, 10)) diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index c3961eb41b486..beb32904e2863 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -23,8 +23,8 @@ thing-type.growatt.inverter.channel.battery-display.label = Battery Display thing-type.growatt.inverter.channel.battery-display.description = Battery display code. thing-type.growatt.inverter.channel.battery-potential.label = Battery Voltage thing-type.growatt.inverter.channel.battery-potential.description = Battery voltage. -thing-type.growatt.inverter.channel.battery-soc.label = Battery SOC -thing-type.growatt.inverter.channel.battery-soc.description = Battery SOC code. +thing-type.growatt.inverter.channel.battery-soc.label = Battery Charge +thing-type.growatt.inverter.channel.battery-soc.description = Battery state of charge. thing-type.growatt.inverter.channel.battery-temperature.label = Battery Temperature thing-type.growatt.inverter.channel.battery-temperature.description = Battery temperature. thing-type.growatt.inverter.channel.battery-type.label = Battery Type diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index f7cc17a147bae..0fd07cb875e9e 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -374,9 +374,9 @@ Battery display code. - - - Battery SOC code. + + + Battery state of charge. @@ -503,7 +503,6 @@ Number:Dimensionless - Energy From f57a6718eecb41e4147c510fb975edcd128c587b Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 22 Jun 2023 15:38:59 +0100 Subject: [PATCH 016/146] [growatt] improve readme Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 178 +++++++++++------- 1 file changed, 106 insertions(+), 72 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 13a24523969a4..80cc9367c5183 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -3,7 +3,11 @@ ![Growatt](doc/growatt.png) This binding supports the integration of Growatt solar inverters. -It depends on the independent [Grott](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor) proxy server application to intercept the data transmissions between the inverter and the Growatt cloud server. + +It depends on the independent [Grott](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor) proxy server application. +This intercepts the logging data that the Growatt inverter data logger normally sends directly to the Growatt cloud server. +It sends the original (encoded) data onwards to the cloud server (so the cloud server will not notice anything different). +But it also sends a (decoded) copy to OpenHAB as well. ## Supported Things @@ -15,69 +19,7 @@ The binding supports two types of things: ## Discovery There is no automatic discovery of the bridge. -However if bridge exists and it receives inverter data, then a matching inverter thing is created in the Inbox. - -## Grott Application - -The Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. -It intercepts and decodes the data packets sent from the inverter to the cloud server. -And it uses the `grottext.py` application extension to send a copy of the intercepted data also to your OpenHAB system. -The data is transmitted via an HTTP POST to the 'http://openhab-ip-address:8080/growatt' end point with a JSON pay-load. - -You need to install the Grott application either on the same computer as OpenHAB or on another computer. - -_**NOTE**: make sure that the Grott application is fully operational for your inveter **BEFORE** you create any things in OpenHAB!_ - -You should configure the Grott application via its `grott.ini` file. -Configure Grott to match your inverter according to the [instructions](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor). - -### 1. Install Python - -If Python is not already installed on you computer, then istall it first. - -### 2. Install Grott - -First install the Grott application and application extension files in a Grott specific home folder. -The recommended Grott configuration for OpenHAB is as follows: - -- Create the Grott 'home' folder e.g. `/usr/bin/grott/`. -- Copy `grott.py`, `grottconf.py`, `grottdata.py`, `grottproxy.py`, `grottsniffer.py`, `grottserver.py` to the home folder. -- Copy `grottext.py` application extension to the home folder. -- Copy `grott.ini` configuration file to the home folder. -- Modify `grott.ini` to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; not run PVOutput; enable the `grottext` extension; and set the OpenHAB `/growatt` servlet url: - -```php -[Generic] -mode = proxy -compat = False -invtype = sph // or whatever - -[MQTT] -nomqtt = True - -[PVOutput] -pvoutput = False - -[extension] -extension = True -extname = grottext -extvar = {"url": "http://xxx.xxx.xxx.xxx:8080/growatt"} -``` - -### 3. Run Grott as a Service - -For best performance the Grott application should be started automatically as a service when your computer starts. - -- Copy the `grott.service` file to the `/etc/systemd/system/` folder -- Modify `grott.service` to enter your user name; the Grott settings; the path to Phyton; and the path to the Grott application: - -```php -[Service] -SyslogIdentifier=grott -User=openhabian // your user name -WorkingDirectory=/usr/bin/grott/ -ExecStart=-/usr/bin/python3 -u /usr/bin/grott/grott.py -v -``` +However if a bridge exists and it receives inverter data, then a matching inverter thing is created in the Inbox. ## Thing Configuration @@ -85,9 +27,9 @@ The `bridge` thing requires no configuration. The `inverter` thing requires configuration of its serial number resp. `deviceId`: -| Name | Type | Description | Required | -|-----------|---------|-------------------------------------------------------------------------------------------|----------| -| deviceId | text | Device serial number or id as configuted in the Growatt cloud, and the Grott application. | yes | +| Name | Type | Description | Required | +|-----------|---------|------------------------------------------------------------------------------------------|----------| +| deviceId | text | Device serial number or id as configured in the Growatt cloud and the Grott application. | yes | ## Channels @@ -95,7 +37,7 @@ The `bridge` thing has no channels. The `inverter` thing supports many possible channels relating to solar generation and consumption. All channels are read only. -Depending on the inverter model, and it configuration, not all of the channels will be present. +Depending on the inverter model, and its configuration, not all of the channels will be present. The list of all possible channels is as follows: | Channel | Type | Description | Advanced | @@ -200,14 +142,106 @@ The list of all possible channels is as follows: ## Full Example -### Thing Configuration +### Example `.things` file ```java -Example thing configuration goes here. +Bridge growatt:bridge:home "Growattt Bridge" [] { + Thing inverter sph "Growatt SPH Inverter" [deviceId="INVERTERTID"] +} ``` -### Item Configuration +### Example `.items` file ```java -Example item configuration goes here. +Number:ElectricPotential Solar_String1_Voltage "Solar String #1 PV Voltage" {channel="growatt:inverter:home:sph:pv1-potential"} +Number:ElectricCurrent Solar_String1_Current "Solar String #1 PV Current" {channel="growatt:inverter:home:sph:pv1-current"} +Number:Power Solar_String1_Power "Solar String #1 PV Power" {channel="growatt:inverter:home:sph:pv1-power"} +Number:Energy Solar_Output_Energy "Solar Output Energy Total" {channel="growatt:inverter:home:sph:pv-energy-total"} ``` + +## Grott Application Installation and Setup + +You can install the Grott application either on the same computer as OpenHAB or on another. +The following assumes you will be running it on the same computer. +Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. +It intercepts data packets between the inverter and the cloud server, and it sends a copy of the intercepted data also to OpenHAB. + +**NOTE**: make sure that the Grott application is **FULLY OPERATIONAL** for your inverter **BEFORE** you create any things in OpenHAB! +Otherwise the binding might create a wrong (or even empty) list of channels for the inverter thing. +(Yet if you do make that mistake you can rectify it by deleteing and recreating the thing). + +You should configure the Grott application via its `grott.ini` file. +Configure Grott to match your inverter according to the [instructions](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor). + +### Install Python + +If Python is not already installed on you computer, then install it first. +And install the following additional necessary python packages: + +``` +sudo pip3 install paho-mqtt +sudo pip3 install requests +``` + +### Install Grott + +First install the Grott application and the Grott application extension files in a Grott specific home folder. +Note that Grott requires the `grottext.py` application extension in addition to the standard application files. +The installation is as follows: + +- Create a 'home' sub-folder for Grott e.g. `/home//grott/`. +- Copy `grott.py`, `grottconf.py`, `grottdata.py`, `grottproxy.py`, `grottsniffer.py`, `grottserver.py` to the home folder. +- Copy `grottext.py` application extension to the home folder. +- Copy `grott.ini` configuration file to the home folder. +- Modify `grott.ini` to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; +not run PVOutput; enable the `grottext` extension; and set the OpenHAB `/growatt` servlet url. +A suggested Grott configuration for OpenHAB is as follows: + +```php +[Generic] +mode = proxy +compat = False +invtype = sph // your inverter type + +[MQTT] +nomqtt = True // disable mqtt + +[PVOutput] +pvoutput = False // disable pvoutput + +[extension] +extension = True +extname = grottext +extvar = {"url": "http://xxx.xxx.xxx.xxx:8080/growatt"} +``` + +### Start Grott as a Service + +Finally you should set your computer to starts the Grott application automatically as a service when your computer starts. +For Windows see wiki: https://github.com/johanmeijer/grott/wiki/Grott-as-a-service-(Windows) +For Linux see wiki: https://github.com/johanmeijer/grott/wiki/Grott-as-a-service-(Linux) +The service configuration for Linux is summarised below: + +- Copy the `grott.service` file to the `/etc/systemd/system/` folder +- Modify `grott.service` to enter your user name; the Grott settings; the path to Python; and the path to the Grott application: + +```php +[Service] +SyslogIdentifier=grott +User= // your username +WorkingDirectory=/home//grott/ // your home grott folder +ExecStart=-/usr/bin/python3 -u /home//grott/grott.py -v // ditto +``` + +### Route Growatt Inverter Logging via Grott Proxy + +Normally the Growatt inverter sends its logging data directly to port `5279` on the Growatt server at `server.growatt.com` (ip=47.91.67.66) on the cloud. +Grott is a proxy server that interposes itself beween the inverter and the cloud server. +i.e. it receives the inverter logging data and forwards it unchanged to the cloud server. + +**WARNING**: make sure that Grott is running on a computer with a **STATIC IP ADDRESS** (and note this safely)! +Otherwise if the computer changes its ip address dynamically, it can no longer intercept the inverter data. +This means **YOU WILL NO LONGER BE ABLE TO RESET THE INVERTER** to its original settings! + +You need to use the Growatt App to tell the inverter to send its logging data to the Grott proxy instead of to the cloud. +See wiki: https://github.com/johanmeijer/grott/wiki/Rerouting-Growatt-Wifi-TCPIP-data-via-your-Grott-Server for more information. From a7ad73b315b8955ca3f886dc0ecb751cd4e8bd99 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 22 Jun 2023 19:01:20 +0100 Subject: [PATCH 017/146] [growatt] tweaks Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 8 +++++++- .../growatt/internal/servlet/GrottHttpServlet.java | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 80cc9367c5183..c74a34ba74e2d 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -178,7 +178,7 @@ Configure Grott to match your inverter according to the [instructions](https://g If Python is not already installed on you computer, then install it first. And install the following additional necessary python packages: -``` +```bash sudo pip3 install paho-mqtt sudo pip3 install requests ``` @@ -233,6 +233,12 @@ WorkingDirectory=/home//grott/ // your home grott folder ExecStart=-/usr/bin/python3 -u /home//grott/grott.py -v // ditto ``` +And finally enable the Grott service: + +```bash +sudo systemctl enable grott +``` + ### Route Growatt Inverter Logging via Grott Proxy Normally the Growatt inverter sends its logging data directly to port `5279` on the Growatt server at `server.growatt.com` (ip=47.91.67.66) on the cloud. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java index 6231cf4783fcd..1b4c3fd8098dc 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java @@ -70,6 +70,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse response) throw protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setStatus(HttpServletResponse.SC_OK); + boolean online = handlers.size() > 0; + response.getWriter().write(online ? MESSAGE_ONLINE : MESSAGE_READY); if (request.getContentLength() > 0) { String content = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); handlers.forEach(handler -> handler.handleGrottContent(content)); From f8bfa84ab353316219502d04bb87ae83502588fc Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 23 Jun 2023 10:20:42 +0100 Subject: [PATCH 018/146] [growatt] pom Signed-off-by: Andrew Fiddian-Green --- bom/openhab-addons/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 95eba8ac828c2..5db1ce32a5276 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -626,6 +626,11 @@ org.openhab.binding.groupepsa ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.growatt + ${project.version} + org.openhab.addons.bundles org.openhab.binding.guntamatic From f88ec53095c5b86f2fb07e0f8125260885610857 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 23 Jun 2023 17:51:03 +0100 Subject: [PATCH 019/146] [growatt] spotless warnings, oh 3.4.x compatibility, grott double escaping Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 4 ++-- .../growatt/internal/handler/GrowattBridgeHandler.java | 8 ++++++-- .../growatt/internal/servlet/GrottHttpServlet.java | 8 +++----- .../org/openhab/binding/growatt/test/GrowattTest.java | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index c74a34ba74e2d..380230a5a6439 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -193,8 +193,8 @@ The installation is as follows: - Copy `grott.py`, `grottconf.py`, `grottdata.py`, `grottproxy.py`, `grottsniffer.py`, `grottserver.py` to the home folder. - Copy `grottext.py` application extension to the home folder. - Copy `grott.ini` configuration file to the home folder. -- Modify `grott.ini` to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; -not run PVOutput; enable the `grottext` extension; and set the OpenHAB `/growatt` servlet url. +- Modify `grott.ini` to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; not run PVOutput; enable the `grottext` extension; and set the OpenHAB `/growatt` servlet url. + A suggested Grott configuration for OpenHAB is as follows: ```php diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 60b6b14747ea1..2abe4350546f4 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -73,11 +73,15 @@ public void handleGrottContent(String json) { JsonElement jsonElement; try { jsonElement = JsonParser.parseString(json); + if (jsonElement.isJsonPrimitive()) { + // strip double escaping from Grott JSON + jsonElement = JsonParser.parseString(jsonElement.getAsString()); + } if (!jsonElement.isJsonObject()) { - throw new JsonSyntaxException("Unsupported element type"); + throw new JsonSyntaxException("Unsupported JSON element type"); } } catch (JsonSyntaxException e) { - logger.debug("handleGrottContent() invalid JSON string '{}'", json, e); + logger.debug("handleGrottContent() invalid JSON '{}'", json, e); return; } try { diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java index 1b4c3fd8098dc..558d50acbbfb8 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java @@ -61,17 +61,15 @@ public class GrottHttpServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { response.setStatus(HttpServletResponse.SC_OK); response.setContentType(MediaType.TEXT_HTML); - boolean online = handlers.size() > 0; - response.getWriter().write( - String.format(HTML, online ? COLOR_ONLINE : COLOR_READY, online ? MESSAGE_ONLINE : MESSAGE_READY)); + response.getWriter().write(String.format(HTML, handlers.isEmpty() ? COLOR_READY : COLOR_ONLINE, + handlers.isEmpty() ? COLOR_READY : MESSAGE_ONLINE)); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setStatus(HttpServletResponse.SC_OK); - boolean online = handlers.size() > 0; - response.getWriter().write(online ? MESSAGE_ONLINE : MESSAGE_READY); + response.getWriter().write(handlers.isEmpty() ? MESSAGE_READY : MESSAGE_ONLINE); if (request.getContentLength() > 0) { String content = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8); handlers.forEach(handler -> handler.handleGrottContent(content)); diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 4db72292eb3f2..3da7d5d173bf5 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -164,8 +164,8 @@ void testGrottValuesContents() { State state = channelStates.get("total-work-time"); assertTrue(state instanceof QuantityType); - if (state instanceof QuantityType quantity) { - QuantityType seconds = quantity.toUnit(Units.SECOND); + if (state instanceof QuantityType) { + QuantityType seconds = ((QuantityType) state).toUnit(Units.SECOND); assertNotNull(seconds); assertEquals(QuantityType.valueOf(32751939, Units.SECOND).doubleValue(), seconds.doubleValue(), 0.1); } From c6d99087b6d6de0312769157fea8967ba3d802e5 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 22 Jul 2023 16:04:48 +0100 Subject: [PATCH 020/146] [growatt] adopt core #3707 Signed-off-by: Andrew Fiddian-Green --- .../handler/GrowattInverterHandler.java | 3 +- .../resources/OH-INF/i18n/growatt.properties | 24 +++---- .../resources/OH-INF/thing/thing-types.xml | 68 +++++++++---------- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 2e78c5c577c0d..4c02ddb52c93b 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -117,7 +117,8 @@ public void handleInverterValues(GrottValues inverterValues) { public void initialize() { GrowattInverterConfiguration config = getConfigAs(GrowattInverterConfiguration.class); deviceId = config.deviceId; - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NOT_YET_READY, "@text/status.awaiting-data"); + thing.setProperty(GrowattInverterConfiguration.DEVICE_ID, deviceId); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/status.awaiting-data"); logger.debug("initialize() thing has {} channels", thing.getChannels().size()); } } diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index beb32904e2863..fac76bd535f82 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -211,18 +211,18 @@ thing-type.config.growatt.inverter.deviceId.description = Id of the inverter. # channel types -channel-type.growatt.advanced-electric-current.label = advanced-electric-current -channel-type.growatt.advanced-electric-frequency.label = advanced-electric-frequency -channel-type.growatt.advanced-electric-power.label = advanced-electric-power -channel-type.growatt.advanced-electric-va.label = advanced-electric-va -channel-type.growatt.advanced-electric-voltage.label = advanced-electric-voltage -channel-type.growatt.advanced-electrical-energy.label = advanced-electrical-energy -channel-type.growatt.advanced-fault-code.label = advanced-fault-code -channel-type.growatt.advanced-outdoor-temperature.label = advanced-outdoor-temperature -channel-type.growatt.advanced-percent.label = advanced-percent -channel-type.growatt.advanced-status-code.label = advanced-status-code -channel-type.growatt.advanced-work-time.label = advanced-work-time -channel-type.growatt.system-status-code.label = system-status +channel-type.growatt.advanced-electric-current.label = Electric Current +channel-type.growatt.advanced-electric-energy.label = Electric Energy +channel-type.growatt.advanced-electric-frequency.label = Electric Frequency +channel-type.growatt.advanced-electric-power.label = Electric Power +channel-type.growatt.advanced-electric-va.label = Electric VA +channel-type.growatt.advanced-electric-voltage.label = Electric Voltage +channel-type.growatt.advanced-fault-code.label = Fault Code +channel-type.growatt.advanced-outdoor-temperature.label = Outdoor Temperature +channel-type.growatt.advanced-percent.label = Percentage +channel-type.growatt.advanced-status-code.label = Status Code +channel-type.growatt.advanced-work-time.label = Work Time +channel-type.growatt.system-status-code.label = Status Code # discovery diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 0fd07cb875e9e..493a2dbf0a07b 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -219,98 +219,98 @@ - + Solar energy collected today. - + Total solar energy collected. - + Solar energy supplied to grid today. - + Solar energy supplied by string #1 to grid today. - + Solar energy supplied by string #2 to grid today. - + Total solar energy supplied to grid. - + Total solar energy supplied by string #1 to grid . - + Total solar energy supplied by string #2 to grid. - + Energy supplied to grid today. - + Total energy supplied to grid. - + Energy supplied to user today. - + Total energy supplied to user. - + Energy supplied to local today. - + Total energy supplied to local. - + Energy used to charge battery today. - + Total energy used to charge battery. - + Grid energy produced from battery today. - + Total grid energy produced from battery. - + Energy consumed from battery. - + Total energy consumed from battery. @@ -481,83 +481,83 @@ Number:Dimensionless - + Status Number:Dimensionless - - Settings + + Status Number:Dimensionless - + Siren Number:Dimensionless - + Number:Frequency - + Energy Number:Power - + Energy Number:Time - + Time Number:Power - + Energy Number:ElectricCurrent - + Energy Number:ElectricPotential - + Energy - + Number:Energy - + Energy Number:Temperature - + Temperature From ffbf26b8577e9e088a251f1531851686c1503a79 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 16 Aug 2023 14:12:10 +0100 Subject: [PATCH 021/146] [growatt] update pom Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.growatt/pom.xml b/bundles/org.openhab.binding.growatt/pom.xml index 6b54df216c2f6..0a328b0db37d8 100644 --- a/bundles/org.openhab.binding.growatt/pom.xml +++ b/bundles/org.openhab.binding.growatt/pom.xml @@ -7,7 +7,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT org.openhab.binding.growatt From 04b1f7a91f5c4b1bee348db58d152c31d34bc9a1 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 9 Sep 2023 15:59:42 +0100 Subject: [PATCH 022/146] [growatt] improve channel names and DTO mappings Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 191 +++++----- .../internal/GrowattBindingConstants.java | 150 ++++---- .../growatt/internal/dto/GrottValues.java | 202 +++++----- .../resources/OH-INF/i18n/growatt.properties | 244 ++++++------ .../resources/OH-INF/thing/thing-types.xml | 347 ++++++++---------- .../binding/growatt/test/GrowattTest.java | 43 ++- .../src/test/resources/sph.json | 75 ++++ 7 files changed, 623 insertions(+), 629 deletions(-) create mode 100644 bundles/org.openhab.binding.growatt/src/test/resources/sph.json diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 380230a5a6439..8fc34d809e47a 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -40,105 +40,96 @@ All channels are read only. Depending on the inverter model, and its configuration, not all of the channels will be present. The list of all possible channels is as follows: -| Channel | Type | Description | Advanced | -|--------------------------------- |-------------------------------|-----------------------------------------------------------|----------| -| system-status | Number:Dimensionless | Status code of the inverter (0=ready, 1=online, 2=fault). | | -| pv-power-in | Number:Power | Total solar input power. | | -| pv-power-out | Number:Power | Total solar output power. | | -| pv1-potential | Number:ElectricPotential | Voltage from solar panel string #1. | yes | -| pv2-potential | Number:ElectricPotential | Voltage from solar panel string #2. | yes | -| pv1-current | Number:ElectricCurrent | Current from solar panel string #1. | yes | -| pv2-current | Number:ElectricCurrent | Current from solar panel string #2. | yes | -| pv1-power | Number:Power | Power from solar panel string #1. | yes | -| pv2-power | Number:Power | Power from solar panel string #2. | yes | -| grid-frequency | Number:Frequency | Frequency of the grid. | yes | -| grid-potential | Number:ElectricPotential | Voltage of the grid (phase #R). | | -| grid-potential-s | Number:ElectricPotential | Voltage of the grid phase #S. | yes | -| grid-potential-t | Number:ElectricPotential | Voltage of the grid phase #T. | yes | -| grid-potential-rs | Number:ElectricPotential | Voltage of the grid phases #RS. | yes | -| grid-potential-st | Number:ElectricPotential | Voltage of the grid phases #ST. | yes | -| grid-potential-tr | Number:ElectricPotential | Voltage of the grid phases #TR. | yes | -| grid-current | Number:ElectricCurrent | Current delivered to the grid (phase #R). | yes | -| grid-current-s | Number:ElectricCurrent | Current delivered to the grid phase #S. | yes | -| grid-current-t | Number:ElectricCurrent | Current delivered to the grid phase #T. | yes | -| grid-power | Number:Power | Power delivered to the grid (phase #R). | | -| grid-power-s | Number:Power | Power delivered to the grid phase #S. | yes | -| grid-power-t | Number:Power | Power delivered to the grid phase #T. | yes | -| grid-va | Number:Power | VA delivered to the grid. | yes | -| grid-charge-current | Number:ElectricCurrent | Grid current to charge battery. | | -| grid-charge-power | Number:Power | Grid power to charge battery. | | -| grid-charge-va | Number:Power | Grid VA to charge battery. | yes | -| grid-discharge-power | Number:Power | Grid power from discharge of battery. | | -| grid-discharge-va | Number:Power | Grid VA from discharge of battery. | yes | -| battery-charge-power | Number:Power | Battery charge power. | | -| battery-discharge-power | Number:Power | Battery discharge power. | | -| battery-discharge-va | Number:Power | Battery discharge VA. | yes | -| to-grid-power | Number:Power | Power supplied to grid. | | -| to-grid-power-r | Number:Power | Power supplied to grid phase #R. | yes | -| to-grid-power-s | Number:Power | Power supplied to grid phase #S. | yes | -| to-grid-power-t | Number:Power | Power supplied to grid phase #T. | yes | -| to-user-power | Number:Power | Power supplied to user. | | -| to-user-power-r | Number:Power | Power supplied to user phase #R. | yes | -| to-user-power-s | Number:Power | Power supplied to user phase #S. | yes | -| to-user-power-t | Number:Power | Power supplied to user phase #T. | yes | -| to-local-power | Number:Power | Power supplied to local. | | -| to-local-power-r | Number:Power | Power supplied to local phase #R. | yes | -| to-local-power-s | Number:Power | Power supplied to local phase #S. | yes | -| to-local-power-t | Number:Power | Power supplied to local phase #T. | yes | -| pv-energy-today | Number:Energy | Solar energy collected today. | | -| pv-energy-total | Number:Energy | Total solar energy collected. | | -| pv-grid-energy-today | Number:Energy | Solar energy supplied to grid today. | | -| pv1-grid-energy-today | Number:Energy | Solar energy supplied by string #1 to grid today. | yes | -| pv2-grid-energy-today | Number:Energy | Solar energy supplied by string #2 to grid today. | yes | -| pv-grid-energy-total | Number:Energy | Total solar energy supplied to grid. | | -| pv1-grid-energy-total | Number:Energy | Total solar energy supplied by string #1 to grid . | yes | -| pv2-grid-energy-total | Number:Energy | Total solar energy supplied by string #2 to grid. | yes | -| to-grid-energy-today | Number:Energy | Energy supplied to grid today. | | -| to-grid-energy-total | Number:Energy | Total energy supplied to grid. | | -| to-user-energy-today | Number:Energy | Energy supplied to user today. | | -| to-user-energy-total | Number:Energy | Total energy supplied to user. | | -| to-local-energy-today | Number:Energy | Energy supplied to local today. | | -| to-local-energy-total | Number:Energy | Total energy supplied to local. | | -| grid-charge-energy-today | Number:Energy | Energy used to charge battery today. | | -| grid-charge-energy-total | Number:Energy | Total energy used to charge battery. | | -| grid-discharge-energy-today | Number:Energy | Grid energy produced from battery today. | | -| grid-discharge-energy-total | Number:Energy | Total grid energy produced from battery. | | -| battery-discharge-energy-today | Number:Energy | Energy consumed from battery. | | -| battery-discharge-energy-total | Number:Energy | Total energy consumed from battery. | | -| total-work-time | Number:Time | Total work time of the system. | yes | -| p-bus-potential | Number:ElectricPotential | P Bus voltage. | yes | -| n-bus-potential | Number:ElectricPotential | N Bus voltage. | yes | -| sp-bus-potential | Number:ElectricPotential | N Bus voltage. | yes | -| pv-temperature | Number:Temperature | Temperature of the solar panels (string #1). | yes | -| pv-ipm-temperature | Number:Temperature | Temperature of the IPM. | yes | -| pv-boost-temperature | Number:Temperature | Boost temperature. | yes | -| temperature-4 | Number:Temperature | Temperature #4. | yes | -| pv2-temperature | Number:Temperature | Temperature of the solar panels (string #2). | yes | -| battery-type | Number:Dimensionless | Type code of the battery. | yes | -| battery-temperature | Number:Temperature | Battery temperature. | yes | -| battery-potential | Number:ElectricPotential | Battery voltage. | yes | -| battery-display | Number:Dimensionless | Battery display code. | yes | -| battery-soc | Number:Dimensionless | Battery State of Charge percent. | yes | -| system-fault-0 | Number:Dimensionless | System fault code #0. | yes | -| system-fault-1 | Number:Dimensionless | System fault code #1. | yes | -| system-fault-2 | Number:Dimensionless | System fault code #2. | yes | -| system-fault-3 | Number:Dimensionless | System fault code #3. | yes | -| system-fault-4 | Number:Dimensionless | System fault code #4. | yes | -| system-fault-5 | Number:Dimensionless | System fault code #5. | yes | -| system-fault-6 | Number:Dimensionless | System fault code #6. | yes | -| system-fault-7 | Number:Dimensionless | System fault code #7. | yes | -| system-work-mode | Number:Dimensionless | System work mode code. | yes | -| sp-display-status | Number:Dimensionless | Solar panel display status code. | yes | -| constant-power-ok | Number:Dimensionless | Constant power OK code. | yes | -| rac | Number:Dimensionless | RAC code. | yes | -| erac-today | Number:Dimensionless | ERAC count today. | yes | -| erac-total | Number:Dimensionless | Total ERAC count. | yes | -| output-potential | Number:ElectricPotential | Output voltage. (Duplicate?) | | -| output-frequency | Number:Frequency | Output frequency. (Duplicate?) | yes | -| load-percent | Number:Dimensionless | Percent of full load. | yes | -| inverter-current | Number:ElectricCurrent | Inverter current. (Duplicate?) | | -| grid-input-power | Number:Power | Grid input power. (Duplicate?) | | -| grid-input-va | Number:Power | Grid input VA. (Duplicate?) | yes | +| Channel | Type | Description | Advanced | +|-------------------------------|---------------------------|------------------------------------------------------|----------| +| system-status | Number:Dimensionless | Inverter status code. | | +| pv-power-in | Number:Power | Total DC solar input power. | | +| pv-power-out | Number:Power | Total AC solar output power. | | +| pv1-voltage | Number:ElectricPotential | DC voltage from solar panel string #1. | yes | +| pv2-voltage | Number:ElectricPotential | DC voltage from solar panel string #2. | yes | +| pv1-current | Number:ElectricCurrent | DC current from solar panel string #1. | yes | +| pv2-current | Number:ElectricCurrent | DC current from solar panel string #2. | yes | +| pv1-power | Number:Power | DC power from solar panel string #1. | yes | +| pv2-power | Number:Power | DC power from solar panel string #2. | yes | +| grid-frequency | Number:Frequency | Frequency of the grid. | yes | +| grid-voltage | Number:ElectricPotential | Voltage of the grid (phase #R). | | +| grid-voltage-s | Number:ElectricPotential | Voltage of the grid phase #S. | yes | +| grid-voltage-t | Number:ElectricPotential | Voltage of the grid phase #T. | yes | +| grid-voltage-rs | Number:ElectricPotential | Voltage of the grid phases #RS. | yes | +| grid-voltage-st | Number:ElectricPotential | Voltage of the grid phases #ST. | yes | +| grid-voltage-tr | Number:ElectricPotential | Voltage of the grid phases #TR. | yes | +| solar-current | Number:ElectricCurrent | AC current from solar (phase #R). | yes | +| solar-current-s | Number:ElectricCurrent | AC current from solar phase #S. | yes | +| solar-current-t | Number:ElectricCurrent | AC current from solar phase #T. | yes | +| solar-power | Number:Power | AC power from solar (phase #R). | | +| solar-power-s | Number:Power | AC power from solar phase #S. | yes | +| solar-power-t | Number:Power | AC power from solar phase #T. | yes | +| solar-va | Number:Power | AC VA from solar. | yes | +| charge-power | Number:Power | Battery charge power. | | +| charge-current | Number:ElectricCurrent | Battery charge current. | yes | +| discharge-power | Number:Power | Battery discharge power. | | +| discharge-va | Number:Power | Battery discharge VA. | yes | +| export-power | Number:Power | Power exported to grid. | | +| export-power-r | Number:Power | Power exported to grid phase #R. | yes | +| export-power-s | Number:Power | Power exported to grid phase #S. | yes | +| export-power-t | Number:Power | Power exported to grid phase #T. | yes | +| import-power | Number:Power | Power imported from grid. | | +| import-power-r | Number:Power | Power imported from grid phase #R. | yes | +| import-power-s | Number:Power | Power imported from grid phase #S. | yes | +| import-power-t | Number:Power | Power imported from grid phase #T. | yes | +| load-power | Number:Power | Power supplied to load. | | +| load-power-r | Number:Power | Power supplied to load phase #R. | yes | +| load-power-s | Number:Power | Power supplied to load phase #S. | yes | +| load-power-t | Number:Power | Power supplied to load phase #T. | yes | +| solar-energy-today | Number:Energy | Solar AC energy produced today. | | +| solar-energy-total | Number:Energy | Total solar AC energy produced. | | +| pv-energy-today | Number:Energy | DC energy collected by solar panels today. | | +| pv1-energy-today | Number:Energy | DC energy collected by solar panels string #1 today. | yes | +| pv2-energy-today | Number:Energy | DC energy collected by solar panels string #2 today. | yes | +| pv-energy-total | Number:Energy | Total DC energy collected by solar panels. | | +| pv1-energy-total | Number:Energy | Total DC energy collected by solar panels string #1. | yes | +| pv2-energy-total | Number:Energy | Total DC energy collected by solar panels string #2. | yes | +| export-energy-today | Number:Energy | Energy exported today. | | +| export-energy-total | Number:Energy | Total energy exported. | | +| import-energy-today | Number:Energy | Energy imported today. | | +| import-energy-total | Number:Energy | Total energy imported. | | +| load-energy-today | Number:Energy | Energy supplied to load today. | | +| load-energy-total | Number:Energy | Total energy supplied to load. | | +| import-charge-energy-today | Number:Energy | Energy imported to charge battery today. | | +| import-charge-energy-total | Number:Energy | Total energy imported to charge battery. | | +| solar-charge-energy-today | Number:Energy | Solar energy to charge battery today. | | +| solar-charge-energy-total | Number:Energy | Total solar energy to charge battery. | | +| discharge-energy-today | Number:Energy | Energy consumed from battery. | | +| discharge-energy-total | Number:Energy | Total energy consumed from battery. | | +| total-work-time | Number:Time | Total work time of the system. | yes | +| p-bus-voltage | Number:ElectricPotential | P Bus voltage. | yes | +| n-bus-voltage | Number:ElectricPotential | N Bus voltage. | yes | +| sp-bus-voltage | Number:ElectricPotential | SP Bus voltage. | yes | +| pv-temperature | Number:Temperature | Temperature of the solar panels (string #1). | yes | +| pv-ipm-temperature | Number:Temperature | Temperature of the IPM. | yes | +| pv-boost-temperature | Number:Temperature | Boost temperature. | yes | +| temperature-4 | Number:Temperature | Temperature #4. | yes | +| pv2-temperature | Number:Temperature | Temperature of the solar panels (string #2). | yes | +| battery-type | Number:Dimensionless | Type code of the battery. | yes | +| battery-temperature | Number:Temperature | Battery temperature. | yes | +| battery-voltage | Number:ElectricPotential | Battery voltage. | yes | +| battery-display | Number:Dimensionless | Battery display code. | yes | +| battery-soc | Number:Dimensionless | Battery State of Charge percent. | yes | +| system-fault-0 | Number:Dimensionless | System fault code #0. | yes | +| system-fault-1 | Number:Dimensionless | System fault code #1. | yes | +| system-fault-2 | Number:Dimensionless | System fault code #2. | yes | +| system-fault-3 | Number:Dimensionless | System fault code #3. | yes | +| system-fault-4 | Number:Dimensionless | System fault code #4. | yes | +| system-fault-5 | Number:Dimensionless | System fault code #5. | yes | +| system-fault-6 | Number:Dimensionless | System fault code #6. | yes | +| system-fault-7 | Number:Dimensionless | System fault code #7. | yes | +| system-work-mode | Number:Dimensionless | System work mode code. | yes | +| sp-display-status | Number:Dimensionless | Solar panel display status code. | yes | +| constant-power-ok | Number:Dimensionless | Constant power OK code. | yes | +| load-percent | Number:Dimensionless | Percent of full load. | yes | +| rac | Number:Dimensionless | RAC code. | yes | +| erac-today | Number:Dimensionless | ERAC count today. | yes | +| erac-total | Number:Dimensionless | Total ERAC count. | yes | ## Full Example @@ -153,7 +144,7 @@ Bridge growatt:bridge:home "Growattt Bridge" [] { ### Example `.items` file ```java -Number:ElectricPotential Solar_String1_Voltage "Solar String #1 PV Voltage" {channel="growatt:inverter:home:sph:pv1-potential"} +Number:ElectricPotential Solar_String1_Voltage "Solar String #1 PV Voltage" {channel="growatt:inverter:home:sph:pv1-voltage"} Number:ElectricCurrent Solar_String1_Current "Solar String #1 PV Current" {channel="growatt:inverter:home:sph:pv1-current"} Number:Power Solar_String1_Power "Solar String #1 PV Power" {channel="growatt:inverter:home:sph:pv1-power"} Number:Energy Solar_Output_Energy "Solar Output Energy Total" {channel="growatt:inverter:home:sph:pv-energy-total"} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java index 02b086fa79bcc..448fe3ee61011 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -64,113 +64,104 @@ public UoM(Unit units, float divisor) { new AbstractMap.SimpleEntry("pv-power-out", new UoM(Units.WATT, 10)), // electric data for strings #1 and #2 - new AbstractMap.SimpleEntry("pv1-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv1-voltage", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("pv1-current", new UoM(Units.AMPERE, 10)), new AbstractMap.SimpleEntry("pv1-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pv2-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv2-voltage", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("pv2-current", new UoM(Units.AMPERE, 10)), new AbstractMap.SimpleEntry("pv2-power", new UoM(Units.WATT, 10)), // grid electric data (1-phase resp. 3-phase) new AbstractMap.SimpleEntry("grid-frequency", new UoM(Units.HERTZ, 100)), - new AbstractMap.SimpleEntry("grid-potential", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-potential-s", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-potential-t", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-potential-rs", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-potential-st", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-potential-tr", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-s", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-t", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-rs", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-st", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-tr", new UoM(Units.VOLT, 10)), // solar power to grid - new AbstractMap.SimpleEntry("grid-current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("grid-current-s", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("grid-current-t", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("solar-current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("solar-current-s", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("solar-current-t", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("grid-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-power-t", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("solar-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("solar-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("solar-power-t", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-va", new UoM(Units.VOLT_AMPERE, 10)), - - // grid power to battery - new AbstractMap.SimpleEntry("grid-charge-current", new UoM(Units.VOLT_AMPERE, 10)), - new AbstractMap.SimpleEntry("grid-charge-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-charge-va", new UoM(Units.VOLT_AMPERE, 10)), - - // grid power from battery - new AbstractMap.SimpleEntry("grid-discharge-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-discharge-va", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("solar-va", new UoM(Units.VOLT_AMPERE, 10)), // battery discharge / charge power - new AbstractMap.SimpleEntry("battery-charge-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("battery-discharge-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("battery-discharge-va", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("charge-current", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("charge-power", new UoM(Units.WATT, 10)), + + new AbstractMap.SimpleEntry("discharge-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("discharge-va", new UoM(Units.VOLT_AMPERE, 10)), - // power to grid - new AbstractMap.SimpleEntry("to-grid-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-grid-power-r", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-grid-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-grid-power-t", new UoM(Units.WATT, 10)), + // export power to grid + new AbstractMap.SimpleEntry("export-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("export-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("export-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("export-power-t", new UoM(Units.WATT, 10)), // power to user - new AbstractMap.SimpleEntry("to-user-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-user-power-r", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-user-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-user-power-t", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("import-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("import-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("import-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("import-power-t", new UoM(Units.WATT, 10)), // power to local - new AbstractMap.SimpleEntry("to-local-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-local-power-r", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-local-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("to-local-power-t", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("load-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("load-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("load-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("load-power-t", new UoM(Units.WATT, 10)), - // pv energy - new AbstractMap.SimpleEntry("pv-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + // solar AC energy + new AbstractMap.SimpleEntry("solar-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("solar-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - // energy taken from solar - new AbstractMap.SimpleEntry("pv-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv1-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv2-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + // solar DC energy + new AbstractMap.SimpleEntry("pv-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv1-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv2-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv1-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv2-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv1-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv2-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - // energy supplied to grid - new AbstractMap.SimpleEntry("to-grid-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("to-grid-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + // energy exported to grid + new AbstractMap.SimpleEntry("export-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("export-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - // energy supplied to user - new AbstractMap.SimpleEntry("to-user-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("to-user-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + // energy imported from grid + new AbstractMap.SimpleEntry("import-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("import-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - // energy supplied to local - new AbstractMap.SimpleEntry("to-local-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("to-local-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + // energy supplied to load + new AbstractMap.SimpleEntry("load-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("load-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - // energy taken from grid to charge - new AbstractMap.SimpleEntry("grid-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("grid-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + // energy imported to charge + new AbstractMap.SimpleEntry("import-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("import-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - // energy supplied to grid from discharge of battery - new AbstractMap.SimpleEntry("grid-discharge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("grid-discharge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + // solar energy to charge + new AbstractMap.SimpleEntry("solar-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("solar-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - // energy taken from battery - new AbstractMap.SimpleEntry("battery-discharge-energy-today", - new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("battery-discharge-energy-total", - new UoM(Units.KILOWATT_HOUR, 10)), + // energy supplied from discharge + new AbstractMap.SimpleEntry("discharge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("discharge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), // inverter up time new AbstractMap.SimpleEntry("total-work-time", new UoM(Units.HOUR, 7200)), // bus voltages - new AbstractMap.SimpleEntry("p-bus-potential", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("n-bus-potential", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("sp-bus-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("p-bus-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("n-bus-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("sp-bus-voltage", new UoM(Units.VOLT, 10)), // temperatures new AbstractMap.SimpleEntry("pv-temperature", new UoM(SIUnits.CELSIUS, 10)), @@ -181,7 +172,7 @@ public UoM(Unit units, float divisor) { // battery data new AbstractMap.SimpleEntry("battery-type", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("battery-potential", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("battery-voltage", new UoM(Units.VOLT, 10)), new AbstractMap.SimpleEntry("battery-temperature", new UoM(SIUnits.CELSIUS, 10)), new AbstractMap.SimpleEntry("battery-display", new UoM(Units.ONE, 10)), new AbstractMap.SimpleEntry("battery-soc", new UoM(Units.PERCENT, 1)), @@ -200,19 +191,12 @@ public UoM(Unit units, float divisor) { new AbstractMap.SimpleEntry("system-work-mode", new UoM(Units.ONE, 1)), new AbstractMap.SimpleEntry("sp-display-status", new UoM(Units.ONE, 10)), new AbstractMap.SimpleEntry("constant-power-ok", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("load-percent", new UoM(Units.PERCENT, 10)), // rac ?? new AbstractMap.SimpleEntry("rac", new UoM(Units.ONE, 1)), new AbstractMap.SimpleEntry("erac-today", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("erac-total", new UoM(Units.ONE, 1)), - - // duplicates ?? - new AbstractMap.SimpleEntry("output-potential", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("output-frequency", new UoM(Units.HERTZ, 100)), - new AbstractMap.SimpleEntry("load-percent", new UoM(Units.PERCENT, 10)), - new AbstractMap.SimpleEntry("inverter-current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("grid-input-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("grid-input-va", new UoM(Units.VOLT_AMPERE, 10)) + new AbstractMap.SimpleEntry("erac-total", new UoM(Units.ONE, 1)) // ); } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 40e31b1d05110..9eec3498b08bb 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -52,132 +52,123 @@ public static String getFieldName(String channelId) { // inverter state public @Nullable @SerializedName(value = "pvstatus") Integer system_status; - // solar generation - public @Nullable @SerializedName(value = "pvpowerin") Integer pv_power_in; - public @Nullable @SerializedName(value = "pvpowerout") Integer pv_power_out; + // solar AC and DC generation + public @Nullable @SerializedName(value = "pvpowerin") Integer pv_power_in; // from DC solar + public @Nullable @SerializedName(value = "pvpowerout") Integer pv_power_out; // to AC mains - // electric data for strings #1 and #2 - public @Nullable @SerializedName(value = "pv1voltage", alternate = { "vpv1" }) Integer pv1_potential; + // DC electric data for strings #1 and #2 + public @Nullable @SerializedName(value = "pv1voltage", alternate = { "vpv1" }) Integer pv1_voltage; public @Nullable @SerializedName(value = "pv1current", alternate = { "buck1curr" }) Integer pv1_current; public @Nullable @SerializedName(value = "pv1watt", alternate = { "ppv1" }) Integer pv1_power; - public @Nullable @SerializedName(value = "pv2voltage", alternate = { "vpv2" }) Integer pv2_potential; + public @Nullable @SerializedName(value = "pv2voltage", alternate = { "vpv2" }) Integer pv2_voltage; public @Nullable @SerializedName(value = "pv2current", alternate = { "buck2curr" }) Integer pv2_current; public @Nullable @SerializedName(value = "pv2watt", alternate = { "ppv2" }) Integer pv2_power; - // grid electric data (1-phase resp. 3-phase) - public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq" }) Integer grid_frequency; + // AC mains electric data (1-phase resp. 3-phase) + public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq", "outputfreq" }) Integer grid_frequency; + public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt", "outputvolt" }) Integer grid_voltage; + public @Nullable @SerializedName(value = "pvgridvoltage2") Integer grid_voltage_s; + public @Nullable @SerializedName(value = "pvgridvoltage3") Integer grid_voltage_t; + public @Nullable @SerializedName(value = "Vac_RS") Integer grid_voltage_rs; + public @Nullable @SerializedName(value = "Vac_ST") Integer grid_voltage_st; + public @Nullable @SerializedName(value = "Vac_TR") Integer grid_voltage_tr; - public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt" }) Integer grid_potential; - public @Nullable @SerializedName(value = "pvgridvoltage2") Integer grid_potential_s; - public @Nullable @SerializedName(value = "pvgridvoltage3") Integer grid_potential_t; - public @Nullable @SerializedName(value = "Vac_RS") Integer grid_potential_rs; - public @Nullable @SerializedName(value = "Vac_ST") Integer grid_potential_st; - public @Nullable @SerializedName(value = "Vac_TR") Integer grid_potential_tr; + // solar AC mains power + public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr", "Inv_Curr" }) Integer solar_current; + public @Nullable @SerializedName(value = "pvgridcurrent2") Integer solar_current_s; + public @Nullable @SerializedName(value = "pvgridcurrent3") Integer solar_current_t; - // solar power to grid - public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr" }) Integer grid_current; - public @Nullable @SerializedName(value = "pvgridcurrent2") Integer grid_current_s; - public @Nullable @SerializedName(value = "pvgridcurrent3") Integer grid_current_t; + public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt", "AC_InWatt" }) Integer solar_power; + public @Nullable @SerializedName(value = "pvgridpower2") Integer solar_power_s; + public @Nullable @SerializedName(value = "pvgridpower3") Integer solar_power_t; - public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt" }) Integer grid_power; - public @Nullable @SerializedName(value = "pvgridpower2") Integer grid_power_s; - public @Nullable @SerializedName(value = "pvgridpower3") Integer grid_power_t; - - public @Nullable @SerializedName(value = "op_va") Integer grid_va; - - // grid power to battery - public @Nullable @SerializedName(value = "ACCharCurr") Integer grid_charge_current; - public @Nullable @SerializedName(value = "acchr_watt") Integer grid_charge_power; - public @Nullable @SerializedName(value = "acchar_VA") Integer grid_charge_va; - - // grid power from battery - // TODO current ?? - public @Nullable @SerializedName(value = "ACDischarWatt") Integer grid_discharge_power; - public @Nullable @SerializedName(value = "ACDischarVA") Integer grid_discharge_va; + public @Nullable @SerializedName(value = "op_va", alternate = { "AC_InVA" }) Integer solar_va; // battery discharge / charge power - // TODO current ?? - public @Nullable @SerializedName(value = "p1charge1", alternate = { "BatWatt" }) Integer battery_charge_power; - public @Nullable @SerializedName(value = "pdischarge1", alternate = { "BatDischarWatt" }) Integer battery_discharge_power; - public @Nullable @SerializedName(value = "BatDischarVA") Integer battery_discharge_va; - - // power to grid - public @Nullable @SerializedName(value = "pacttogridtotal") Integer to_grid_power; - public @Nullable @SerializedName(value = "pacttogridr") Integer to_grid_power_r; - public @Nullable @SerializedName(value = "pacttogrids") Integer to_grid_power_s; - public @Nullable @SerializedName(value = "pacttogridt") Integer to_grid_power_t; - - // power to user - public @Nullable @SerializedName(value = "pacttousertotal") Integer to_user_power; - public @Nullable @SerializedName(value = "pacttouserr") Integer to_user_power_r; - public @Nullable @SerializedName(value = "pacttousers") Integer to_user_power_s; - public @Nullable @SerializedName(value = "pacttousert") Integer to_user_power_t; - - // power to local - public @Nullable @SerializedName(value = "plocalloadtotal") Integer to_local_power; - public @Nullable @SerializedName(value = "plocalloadr") Integer to_local_power_r; - public @Nullable @SerializedName(value = "plocalloads") Integer to_local_power_s; - public @Nullable @SerializedName(value = "plocalloadt") Integer to_local_power_t; - - // pv energy - public @Nullable @SerializedName(value = "pvenergytoday") Integer pv_energy_today; - public @Nullable @SerializedName(value = "pvenergytotal") Integer pv_energy_total; - - // energy taken from solar - public @Nullable @SerializedName(value = "epvtoday") Integer pv_grid_energy_today; - public @Nullable @SerializedName(value = "epv1today", alternate = { "epv1tod" }) Integer pv1_grid_energy_today; - public @Nullable @SerializedName(value = "epv2today", alternate = { "epv2tod" }) Integer pv2_grid_energy_today; - - public @Nullable @SerializedName(value = "epvtotal") Integer pv_grid_energy_total; - public @Nullable @SerializedName(value = "epv1total", alternate = { "epv1tot" }) Integer pv1_grid_energy_total; - public @Nullable @SerializedName(value = "epv2total", alternate = { "epv2tot" }) Integer pv2_grid_energy_total; - - // energy supplied to grid - public @Nullable @SerializedName(value = "etogrid_tod", alternate = { "eactoday" }) Integer to_grid_energy_today; - public @Nullable @SerializedName(value = "etogrid_tot", alternate = { "eactotal" }) Integer to_grid_energy_total; - - // energy supplied to user - public @Nullable @SerializedName(value = "etouser_tod") Integer to_user_energy_today; - public @Nullable @SerializedName(value = "etouser_tot") Integer to_user_energy_total; + public @Nullable @SerializedName(value = "p1charge1", alternate = { "acchr_watt", "BatWatt" }) Integer charge_power; + public @Nullable @SerializedName(value = "pdischarge1", alternate = { "ACDischarWatt", "BatDischarWatt" }) Integer discharge_power; + + // miscellaneous battery + public @Nullable @SerializedName(value = "ACCharCurr") Integer charge_current; + public @Nullable @SerializedName(value = "ACDischarVA", alternate = { "BatDischarVA", "acchar_VA" }) Integer discharge_va; + + // power exported to utility company + public @Nullable @SerializedName(value = "pactogridtot") Integer export_power; + public @Nullable @SerializedName(value = "pactogridr") Integer export_power_r; + public @Nullable @SerializedName(value = "pactogrids") Integer export_power_s; + public @Nullable @SerializedName(value = "pactogridt") Integer export_power_t; + + // power imported from utility company + public @Nullable @SerializedName(value = "pactousertot") Integer import_power; + public @Nullable @SerializedName(value = "pactouserr") Integer import_power_r; + public @Nullable @SerializedName(value = "pactousers") Integer import_power_s; + public @Nullable @SerializedName(value = "pactousert") Integer import_power_t; + + // power delivered to internal load + public @Nullable @SerializedName(value = "plocaloadtot") Integer load_power; + public @Nullable @SerializedName(value = "plocaloadr") Integer load_power_r; + public @Nullable @SerializedName(value = "plocaloads") Integer load_power_s; + public @Nullable @SerializedName(value = "plocaloadt") Integer load_power_t; + + // solar AC grid energy + public @Nullable @SerializedName(value = "eactoday", alternate = { "pvenergytoday" }) Integer solar_energy_today; + public @Nullable @SerializedName(value = "eactotal", alternate = { "pvenergytotal" }) Integer solar_energy_total; + + // solar DC pv energy + public @Nullable @SerializedName(value = "epvtoday") Integer pv_energy_today; + public @Nullable @SerializedName(value = "epv1today", alternate = { "epv1tod" }) Integer pv1_energy_today; + public @Nullable @SerializedName(value = "epv2today", alternate = { "epv2tod" }) Integer pv2_energy_today; + + public @Nullable @SerializedName(value = "epvtotal") Integer pv_energy_total; + public @Nullable @SerializedName(value = "epv1total", alternate = { "epv1tot" }) Integer pv1_energy_total; + public @Nullable @SerializedName(value = "epv2total", alternate = { "epv2tot" }) Integer pv2_energy_total; + + // energy exported to utility company + public @Nullable @SerializedName(value = "etogrid_tod") Integer export_energy_today; + public @Nullable @SerializedName(value = "etogrid_tot") Integer export_energy_total; + + // energy imported from utility company + public @Nullable @SerializedName(value = "etouser_tod") Integer import_energy_today; + public @Nullable @SerializedName(value = "etouser_tot") Integer import_energy_total; // energy supplied to local load - public @Nullable @SerializedName(value = "elocalload_tod") Integer to_local_energy_today; - public @Nullable @SerializedName(value = "elocalloadr_tot") Integer to_local_energy_total; + public @Nullable @SerializedName(value = "elocalload_tod") Integer load_energy_today; + public @Nullable @SerializedName(value = "elocalloadr_tot") Integer load_energy_total; - // energy taken from grid to charge - public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday" }) Integer grid_charge_energy_today; - public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal" }) Integer grid_charge_energy_total; + // charging energy from import + public @Nullable @SerializedName(value = "eharge_today") Integer import_charge_energy_today; + public @Nullable @SerializedName(value = "eharge_total") Integer import_charge_energy_total; - // energy supplied to grid from discharge of battery - public @Nullable @SerializedName(value = "edischarge1_tod", alternate = { "eacDischarToday" }) Integer grid_discharge_energy_today; - public @Nullable @SerializedName(value = "edischarge1_tot", alternate = { "eacDischarTotal" }) Integer grid_discharge_energy_total; + // charging energy from solar + public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday" }) Integer solar_charge_energy_today; + public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal" }) Integer solar_charge_energy_total; - // energy taken from battery - public @Nullable @SerializedName(value = "ebatDischarToday") Integer battery_discharge_energy_today; - public @Nullable @SerializedName(value = "ebatDischarTotal") Integer battery_discharge_energy_total; + // discharging energy + public @Nullable @SerializedName(value = "edischarge1_tod", alternate = { "eacDischarToday", "ebatDischarToday" }) Integer discharge_energy_today; + public @Nullable @SerializedName(value = "edischarge1_tot", alternate = { "eacDischarTotal","ebatDischarTotal" }) Integer discharge_energy_total; // inverter up time - public @Nullable @SerializedName(value = "totworktime") Integer total_work_time; + public @Nullable @SerializedName(value = "totworktime") Integer total_work_time; // bus voltages - public @Nullable @SerializedName(value = "pbusvolt", alternate = { "bus_volt" }) Integer p_bus_potential; - public @Nullable @SerializedName(value = "nbusvolt") Integer n_bus_potential; - public @Nullable @SerializedName(value = "spbusvolt") Integer sp_bus_potential; + public @Nullable @SerializedName(value = "pbusvolt", alternate = { "bus_volt" }) Integer p_bus_voltage; + public @Nullable @SerializedName(value = "nbusvolt") Integer n_bus_voltage; + public @Nullable @SerializedName(value = "spbusvolt") Integer sp_bus_voltage; // temperatures public @Nullable @SerializedName(value = "pvtemperature", alternate = { "dcdctemp", "buck1_ntc" }) Integer pv_temperature; public @Nullable @SerializedName(value = "pvipmtemperature", alternate = { "invtemp" }) Integer pv_ipm_temperature; - public @Nullable @SerializedName(value = "pvboosttemp", alternate = { "pvboottemperature" }) Integer pv_boost_temperature; + public @Nullable @SerializedName(value = "pvboosttemp", alternate = {"pvboottemperature" }) Integer pv_boost_temperature; public @Nullable @SerializedName(value = "temp4") Integer temperature_4; public @Nullable @SerializedName(value = "buck2_ntc") Integer pv2_temperature; // battery data public @Nullable @SerializedName(value = "batteryType") Integer battery_type; public @Nullable @SerializedName(value = "batttemp") Integer battery_temperature; - public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt" }) Integer battery_potential; - public @Nullable @SerializedName(value = "bat_dsp") Integer battery_display; - public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC" }) Integer battery_soc; + public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt" }) Integer battery_voltage; + public @Nullable @SerializedName(value = "bat_dsp") Integer battery_display; + public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC" }) Integer battery_soc; // fault codes public @Nullable @SerializedName(value = "systemfaultword0", alternate = { "isof", "faultBit" }) Integer system_fault_0; @@ -190,22 +181,15 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "systemfaultword7", alternate = { "faultcode" }) Integer system_fault_7; // miscellaneous - public @Nullable @SerializedName(value = "uwsysworkmode") Integer system_work_mode; - public @Nullable @SerializedName(value = "spdspstatus") Integer sp_display_status; - public @Nullable @SerializedName(value = "constantPowerOK") Integer constant_power_ok; + public @Nullable @SerializedName(value = "uwsysworkmode") Integer system_work_mode; + public @Nullable @SerializedName(value = "spdspstatus") Integer sp_display_status; + public @Nullable @SerializedName(value = "constantPowerOK") Integer constant_power_ok; + public @Nullable @SerializedName(value = "loadpercent") Integer load_percent; // rac ?? - public @Nullable @SerializedName(value = "rac") Integer rac; - public @Nullable @SerializedName(value = "eractoday") Integer erac_today; - public @Nullable @SerializedName(value = "eractotal") Integer erac_total; - - // duplicates ?? - public @Nullable @SerializedName(value = "outputvolt") Integer output_potential; - public @Nullable @SerializedName(value = "outputfreq") Integer output_frequency; - public @Nullable @SerializedName(value = "loadpercent") Integer load_percent; - public @Nullable @SerializedName(value = "Inv_Curr") Integer inverter_current; - public @Nullable @SerializedName(value = "AC_InWatt") Integer grid_input_power; - public @Nullable @SerializedName(value = "AC_InVA") Integer grid_input_va; + public @Nullable @SerializedName(value = "rac") Integer rac; + public @Nullable @SerializedName(value = "eractoday") Integer erac_today; + public @Nullable @SerializedName(value = "eractotal") Integer erac_total; // @formatter:on diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index fac76bd535f82..7332d1c7c53b5 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -9,138 +9,156 @@ thing-type.growatt.bridge.label = Growatt Bridge Thing thing-type.growatt.bridge.description = Bridge Thing for Growatt Binding thing-type.growatt.inverter.label = Growatt Inverter Thing thing-type.growatt.inverter.description = Inverter Thing for Growatt Binding -thing-type.growatt.inverter.channel.battery-charge-power.label = Battery Charge Power -thing-type.growatt.inverter.channel.battery-charge-power.description = Battery charge power. -thing-type.growatt.inverter.channel.battery-discharge-energy-today.label = Battery Discharge Energy Today -thing-type.growatt.inverter.channel.battery-discharge-energy-today.description = Energy consumed from battery. -thing-type.growatt.inverter.channel.battery-discharge-energy-total.label = Total Battery Discharge Energy -thing-type.growatt.inverter.channel.battery-discharge-energy-total.description = Total energy consumed from battery. -thing-type.growatt.inverter.channel.battery-discharge-power.label = Battery Discharge Power -thing-type.growatt.inverter.channel.battery-discharge-power.description = Battery discharge power. -thing-type.growatt.inverter.channel.battery-discharge-va.label = Battery Discharge VA -thing-type.growatt.inverter.channel.battery-discharge-va.description = Battery discharge VA. thing-type.growatt.inverter.channel.battery-display.label = Battery Display thing-type.growatt.inverter.channel.battery-display.description = Battery display code. -thing-type.growatt.inverter.channel.battery-potential.label = Battery Voltage -thing-type.growatt.inverter.channel.battery-potential.description = Battery voltage. thing-type.growatt.inverter.channel.battery-soc.label = Battery Charge thing-type.growatt.inverter.channel.battery-soc.description = Battery state of charge. thing-type.growatt.inverter.channel.battery-temperature.label = Battery Temperature thing-type.growatt.inverter.channel.battery-temperature.description = Battery temperature. thing-type.growatt.inverter.channel.battery-type.label = Battery Type thing-type.growatt.inverter.channel.battery-type.description = Type code of the battery. +thing-type.growatt.inverter.channel.battery-voltage.label = Battery Voltage +thing-type.growatt.inverter.channel.battery-voltage.description = Battery voltage. +thing-type.growatt.inverter.channel.charge-current.label = Charge Current +thing-type.growatt.inverter.channel.charge-current.description = Charge current to battery. +thing-type.growatt.inverter.channel.charge-power.label = Charge Power +thing-type.growatt.inverter.channel.charge-power.description = Charge power to battery. thing-type.growatt.inverter.channel.constant-power-ok.label = Constant Power OK thing-type.growatt.inverter.channel.constant-power-ok.description = Constant power OK code. +thing-type.growatt.inverter.channel.discharge-energy-today.label = Energy from Battery Today +thing-type.growatt.inverter.channel.discharge-energy-today.description = Energy consumed from battery today. +thing-type.growatt.inverter.channel.discharge-energy-total.label = Total Energy from Battery +thing-type.growatt.inverter.channel.discharge-energy-total.description = Total energy consumed from battery. +thing-type.growatt.inverter.channel.discharge-power.label = Discharge Power +thing-type.growatt.inverter.channel.discharge-power.description = Discharge power from battery. +thing-type.growatt.inverter.channel.discharge-va.label = Discharge VA +thing-type.growatt.inverter.channel.discharge-va.description = Discharge VA from battery. thing-type.growatt.inverter.channel.erac-today.label = ERAC Today thing-type.growatt.inverter.channel.erac-today.description = ERAC count today. thing-type.growatt.inverter.channel.erac-total.label = ERAC Total thing-type.growatt.inverter.channel.erac-total.description = Total ERAC count. -thing-type.growatt.inverter.channel.grid-charge-current.label = Charge Current -thing-type.growatt.inverter.channel.grid-charge-current.description = Grid current to charge battery. -thing-type.growatt.inverter.channel.grid-charge-energy-today.label = Energy to Charge Battery Today -thing-type.growatt.inverter.channel.grid-charge-energy-today.description = Energy used to charge battery today. -thing-type.growatt.inverter.channel.grid-charge-energy-total.label = Total Energy to Charge Battery -thing-type.growatt.inverter.channel.grid-charge-energy-total.description = Total energy used to charge battery. -thing-type.growatt.inverter.channel.grid-charge-power.label = Charge Power -thing-type.growatt.inverter.channel.grid-charge-power.description = Grid power to charge battery. -thing-type.growatt.inverter.channel.grid-charge-va.label = Charge VA -thing-type.growatt.inverter.channel.grid-charge-va.description = Grid VA to charge battery. -thing-type.growatt.inverter.channel.grid-current.label = Grid Current -thing-type.growatt.inverter.channel.grid-current.description = Current delivered to the grid (phase #R). -thing-type.growatt.inverter.channel.grid-current-s.label = Grid Current #S -thing-type.growatt.inverter.channel.grid-current-s.description = Current delivered to the grid phase #S. -thing-type.growatt.inverter.channel.grid-current-t.label = Grid Current #T -thing-type.growatt.inverter.channel.grid-current-t.description = Current delivered to the grid phase #T. -thing-type.growatt.inverter.channel.grid-discharge-energy-today.label = Grid Energy from Battery Today -thing-type.growatt.inverter.channel.grid-discharge-energy-today.description = Grid energy produced from battery today. -thing-type.growatt.inverter.channel.grid-discharge-energy-total.label = Total Grid Energy from Battery -thing-type.growatt.inverter.channel.grid-discharge-energy-total.description = Total grid energy produced from battery. -thing-type.growatt.inverter.channel.grid-discharge-power.label = Discharge Power -thing-type.growatt.inverter.channel.grid-discharge-power.description = Grid power from discharge of battery. -thing-type.growatt.inverter.channel.grid-discharge-va.label = Discharge VA -thing-type.growatt.inverter.channel.grid-discharge-va.description = Grid VA from discharge of battery. +thing-type.growatt.inverter.channel.export-energy-today.label = Export Energy Today +thing-type.growatt.inverter.channel.export-energy-today.description = Energy exported to grid today. +thing-type.growatt.inverter.channel.export-energy-total.label = Total Export Energy +thing-type.growatt.inverter.channel.export-energy-total.description = Total energy exported to grid. +thing-type.growatt.inverter.channel.export-power.label = Export Power +thing-type.growatt.inverter.channel.export-power.description = Power exported to grid. +thing-type.growatt.inverter.channel.export-power-r.label = Export Power #R +thing-type.growatt.inverter.channel.export-power-r.description = Power exported to grid phase #R. +thing-type.growatt.inverter.channel.export-power-s.label = Export Power #S +thing-type.growatt.inverter.channel.export-power-s.description = Power exported to grid phase #S. +thing-type.growatt.inverter.channel.export-power-t.label = Export Power #T +thing-type.growatt.inverter.channel.export-power-t.description = Power exported to grid phase #T. thing-type.growatt.inverter.channel.grid-frequency.label = Grid Frequency thing-type.growatt.inverter.channel.grid-frequency.description = Frequency of the grid. -thing-type.growatt.inverter.channel.grid-input-power.label = Grid Input Power -thing-type.growatt.inverter.channel.grid-input-power.description = Grid input power. -thing-type.growatt.inverter.channel.grid-input-va.label = Grid Input VA -thing-type.growatt.inverter.channel.grid-input-va.description = Grid input VA. -thing-type.growatt.inverter.channel.grid-potential.label = Grid Voltage -thing-type.growatt.inverter.channel.grid-potential.description = Voltage of the grid (phase #R). -thing-type.growatt.inverter.channel.grid-potential-rs.label = Grid Voltage #RS -thing-type.growatt.inverter.channel.grid-potential-rs.description = Voltage of the grid phases #RS. -thing-type.growatt.inverter.channel.grid-potential-s.label = Grid Voltage #S -thing-type.growatt.inverter.channel.grid-potential-s.description = Voltage of the grid phase #S. -thing-type.growatt.inverter.channel.grid-potential-st.label = Grid Voltage #ST -thing-type.growatt.inverter.channel.grid-potential-st.description = Voltage of the grid phases #ST. -thing-type.growatt.inverter.channel.grid-potential-t.label = Grid Voltage #T -thing-type.growatt.inverter.channel.grid-potential-t.description = Voltage of the grid phase #T. -thing-type.growatt.inverter.channel.grid-potential-tr.label = Grid Voltage #TR -thing-type.growatt.inverter.channel.grid-potential-tr.description = Voltage of the grid phases #TR. -thing-type.growatt.inverter.channel.grid-power.label = Grid Power -thing-type.growatt.inverter.channel.grid-power.description = Power delivered to the grid (phase #R). -thing-type.growatt.inverter.channel.grid-power-s.label = Grid Power #S -thing-type.growatt.inverter.channel.grid-power-s.description = Power delivered to the grid phase #S. -thing-type.growatt.inverter.channel.grid-power-t.label = Grid Power #T -thing-type.growatt.inverter.channel.grid-power-t.description = Power delivered to the grid phase #T. -thing-type.growatt.inverter.channel.grid-va.label = Grid VA -thing-type.growatt.inverter.channel.grid-va.description = VA delivered to the grid. -thing-type.growatt.inverter.channel.inverter-current.label = Inverter Current -thing-type.growatt.inverter.channel.inverter-current.description = Inverter current. +thing-type.growatt.inverter.channel.grid-voltage.label = Grid Voltage +thing-type.growatt.inverter.channel.grid-voltage.description = Voltage of the grid (phase #R). +thing-type.growatt.inverter.channel.grid-voltage-rs.label = Grid Voltage #RS +thing-type.growatt.inverter.channel.grid-voltage-rs.description = Voltage of the grid phases #RS. +thing-type.growatt.inverter.channel.grid-voltage-s.label = Grid Voltage #S +thing-type.growatt.inverter.channel.grid-voltage-s.description = Voltage of the grid phase #S. +thing-type.growatt.inverter.channel.grid-voltage-st.label = Grid Voltage #ST +thing-type.growatt.inverter.channel.grid-voltage-st.description = Voltage of the grid phases #ST. +thing-type.growatt.inverter.channel.grid-voltage-t.label = Grid Voltage #T +thing-type.growatt.inverter.channel.grid-voltage-t.description = Voltage of the grid phase #T. +thing-type.growatt.inverter.channel.grid-voltage-tr.label = Grid Voltage #TR +thing-type.growatt.inverter.channel.grid-voltage-tr.description = Voltage of the grid phases #TR. +thing-type.growatt.inverter.channel.import-charge-energy-today.label = Import Energy to Charge Battery Today +thing-type.growatt.inverter.channel.import-charge-energy-today.description = Energy imported from grid to charge battery today. +thing-type.growatt.inverter.channel.import-charge-energy-total.label = Total Import Energy to Charge Battery +thing-type.growatt.inverter.channel.import-charge-energy-total.description = Total energy imported from grid to charge battery. +thing-type.growatt.inverter.channel.import-energy-today.label = Import Energy Today +thing-type.growatt.inverter.channel.import-energy-today.description = Energy imported from grid today. +thing-type.growatt.inverter.channel.import-energy-total.label = Total Import Energy +thing-type.growatt.inverter.channel.import-energy-total.description = Total energy imported from grid. +thing-type.growatt.inverter.channel.import-power.label = Import Power +thing-type.growatt.inverter.channel.import-power.description = Power imported. +thing-type.growatt.inverter.channel.import-power-r.label = Import Power #R +thing-type.growatt.inverter.channel.import-power-r.description = Power imported phase #R. +thing-type.growatt.inverter.channel.import-power-s.label = Import Power #S +thing-type.growatt.inverter.channel.import-power-s.description = Power imported phase #S. +thing-type.growatt.inverter.channel.import-power-t.label = Import Power #T +thing-type.growatt.inverter.channel.import-power-t.description = Power imported phase #T. +thing-type.growatt.inverter.channel.load-energy-today.label = Energy to Load Today +thing-type.growatt.inverter.channel.load-energy-today.description = Energy supplied to load today. +thing-type.growatt.inverter.channel.load-energy-total.label = Total Energy to Load +thing-type.growatt.inverter.channel.load-energy-total.description = Total energy supplied to load. thing-type.growatt.inverter.channel.load-percent.label = Load Percent thing-type.growatt.inverter.channel.load-percent.description = Percent of full load. -thing-type.growatt.inverter.channel.n-bus-potential.label = N Bus Voltage -thing-type.growatt.inverter.channel.n-bus-potential.description = N Bus voltage. -thing-type.growatt.inverter.channel.output-frequency.label = Output Frequency -thing-type.growatt.inverter.channel.output-frequency.description = Output frequency. -thing-type.growatt.inverter.channel.output-potential.label = Output Voltage -thing-type.growatt.inverter.channel.output-potential.description = Output voltage. -thing-type.growatt.inverter.channel.p-bus-potential.label = P Bus Voltage -thing-type.growatt.inverter.channel.p-bus-potential.description = P Bus voltage. +thing-type.growatt.inverter.channel.load-power.label = Load Power +thing-type.growatt.inverter.channel.load-power.description = Power supplied to load. +thing-type.growatt.inverter.channel.load-power-r.label = Load Power #R +thing-type.growatt.inverter.channel.load-power-r.description = Power supplied to load phase #R. +thing-type.growatt.inverter.channel.load-power-s.label = Load Power #S +thing-type.growatt.inverter.channel.load-power-s.description = Power supplied to load phase #S. +thing-type.growatt.inverter.channel.load-power-t.label = Load Power #T +thing-type.growatt.inverter.channel.load-power-t.description = Power supplied to load phase #T. +thing-type.growatt.inverter.channel.n-bus-voltage.label = N Bus Voltage +thing-type.growatt.inverter.channel.n-bus-voltage.description = N Bus voltage. +thing-type.growatt.inverter.channel.p-bus-voltage.label = P Bus Voltage +thing-type.growatt.inverter.channel.p-bus-voltage.description = P Bus voltage. thing-type.growatt.inverter.channel.pv-boost-temperature.label = Boost Temperature thing-type.growatt.inverter.channel.pv-boost-temperature.description = Boost temperature. -thing-type.growatt.inverter.channel.pv-energy-today.label = Solar Energy Today -thing-type.growatt.inverter.channel.pv-energy-today.description = Solar energy collected today. -thing-type.growatt.inverter.channel.pv-energy-total.label = Solar Energy Total -thing-type.growatt.inverter.channel.pv-energy-total.description = Total solar energy collected. -thing-type.growatt.inverter.channel.pv-grid-energy-today.label = Solar Energy to Grid Today -thing-type.growatt.inverter.channel.pv-grid-energy-today.description = Solar energy supplied to grid today. -thing-type.growatt.inverter.channel.pv-grid-energy-total.label = Total Solar Energy to Grid -thing-type.growatt.inverter.channel.pv-grid-energy-total.description = Total solar energy supplied to grid. +thing-type.growatt.inverter.channel.pv-energy-today.label = Solar DC Energy Today +thing-type.growatt.inverter.channel.pv-energy-today.description = Solar DC energy collected. +thing-type.growatt.inverter.channel.pv-energy-total.label = Total Solar DC Energy +thing-type.growatt.inverter.channel.pv-energy-total.description = Total solar energy supplied to grid. thing-type.growatt.inverter.channel.pv-ipm-temperature.label = Solar IPM Temperature thing-type.growatt.inverter.channel.pv-ipm-temperature.description = Temperature of the IPM. thing-type.growatt.inverter.channel.pv-power-in.label = Solar Input Power -thing-type.growatt.inverter.channel.pv-power-in.description = Total solar input power. +thing-type.growatt.inverter.channel.pv-power-in.description = Total solar DC input power. thing-type.growatt.inverter.channel.pv-power-out.label = Solar Output Power -thing-type.growatt.inverter.channel.pv-power-out.description = Total solar output power. +thing-type.growatt.inverter.channel.pv-power-out.description = Total solar AC output power. thing-type.growatt.inverter.channel.pv-temperature.label = Solar Panel Temperature thing-type.growatt.inverter.channel.pv-temperature.description = Temperature of the solar panels (string #1). thing-type.growatt.inverter.channel.pv1-current.label = String #1 Current thing-type.growatt.inverter.channel.pv1-current.description = Current from solar panel string #1. -thing-type.growatt.inverter.channel.pv1-grid-energy-today.label = Solar Energy #1 to Grid Today -thing-type.growatt.inverter.channel.pv1-grid-energy-today.description = Solar energy supplied by string #1 to grid today. -thing-type.growatt.inverter.channel.pv1-grid-energy-total.label = Total Solar Energy #1 to Grid -thing-type.growatt.inverter.channel.pv1-grid-energy-total.description = Total solar energy supplied by string #1 to grid . -thing-type.growatt.inverter.channel.pv1-potential.label = String #1 Voltage -thing-type.growatt.inverter.channel.pv1-potential.description = Voltage from solar panel string #1. +thing-type.growatt.inverter.channel.pv1-energy-today.label = Solar DC Energy #1 Today +thing-type.growatt.inverter.channel.pv1-energy-today.description = Solar DC energy collected by string #1 to grid today. +thing-type.growatt.inverter.channel.pv1-energy-total.label = Total Solar DC Energy #1 +thing-type.growatt.inverter.channel.pv1-energy-total.description = Total solar DC collected by string #1. thing-type.growatt.inverter.channel.pv1-power.label = String #1 Power thing-type.growatt.inverter.channel.pv1-power.description = Power from solar panel string #1. +thing-type.growatt.inverter.channel.pv1-voltage.label = String #1 Voltage +thing-type.growatt.inverter.channel.pv1-voltage.description = Voltage from solar panel string #1. thing-type.growatt.inverter.channel.pv2-current.label = String #2 Current thing-type.growatt.inverter.channel.pv2-current.description = Current from solar panel string #2. -thing-type.growatt.inverter.channel.pv2-grid-energy-today.label = Solar Energy #2 to Grid Today -thing-type.growatt.inverter.channel.pv2-grid-energy-today.description = Solar energy supplied by string #2 to grid today. -thing-type.growatt.inverter.channel.pv2-grid-energy-total.label = Total Solar Energy #2 to Grid -thing-type.growatt.inverter.channel.pv2-grid-energy-total.description = Total solar energy supplied by string #2 to grid. -thing-type.growatt.inverter.channel.pv2-potential.label = String #2 Voltage -thing-type.growatt.inverter.channel.pv2-potential.description = Voltage from solar panel string #2. +thing-type.growatt.inverter.channel.pv2-energy-today.label = Solar Energy #2 to Grid Today +thing-type.growatt.inverter.channel.pv2-energy-today.description = Solar DC energy collected by string #2 to grid today. +thing-type.growatt.inverter.channel.pv2-energy-total.label = Total Solar DC Energy #2 +thing-type.growatt.inverter.channel.pv2-energy-total.description = Total solar DC collected by string #2. thing-type.growatt.inverter.channel.pv2-power.label = String #2 Power thing-type.growatt.inverter.channel.pv2-power.description = Power from solar panel string #2. thing-type.growatt.inverter.channel.pv2-temperature.label = Solar Panel Temperature #2 thing-type.growatt.inverter.channel.pv2-temperature.description = Temperature of the solar panels (string #2). +thing-type.growatt.inverter.channel.pv2-voltage.label = String #2 Voltage +thing-type.growatt.inverter.channel.pv2-voltage.description = Voltage from solar panel string #2. thing-type.growatt.inverter.channel.rac.label = RAC thing-type.growatt.inverter.channel.rac.description = RAC code. -thing-type.growatt.inverter.channel.sp-bus-potential.label = N Bus Voltage -thing-type.growatt.inverter.channel.sp-bus-potential.description = N Bus voltage. +thing-type.growatt.inverter.channel.solar-charge-energy-today.label = Solar Energy to Charge Battery Today +thing-type.growatt.inverter.channel.solar-charge-energy-today.description = Energy from solar to charge battery today. +thing-type.growatt.inverter.channel.solar-charge-energy-total.label = Total Solar Energy to Charge Battery +thing-type.growatt.inverter.channel.solar-charge-energy-total.description = Total energy from solar to charge battery. +thing-type.growatt.inverter.channel.solar-current.label = Solar Current +thing-type.growatt.inverter.channel.solar-current.description = AC current from solar (phase #R). +thing-type.growatt.inverter.channel.solar-current-s.label = Solar Current #S +thing-type.growatt.inverter.channel.solar-current-s.description = AC current from solar phase #S. +thing-type.growatt.inverter.channel.solar-current-t.label = Solar Current #T +thing-type.growatt.inverter.channel.solar-current-t.description = AC current from solar phase #T. +thing-type.growatt.inverter.channel.solar-energy-today.label = Solar Energy Today +thing-type.growatt.inverter.channel.solar-energy-today.description = Solar energy collected today. +thing-type.growatt.inverter.channel.solar-energy-total.label = Solar Energy Total +thing-type.growatt.inverter.channel.solar-energy-total.description = Total solar energy collected. +thing-type.growatt.inverter.channel.solar-power.label = Solar AC Power +thing-type.growatt.inverter.channel.solar-power.description = AC power from solar (phase #R). +thing-type.growatt.inverter.channel.solar-power-s.label = Solar AC Power #S +thing-type.growatt.inverter.channel.solar-power-s.description = AC power from solar phase #S. +thing-type.growatt.inverter.channel.solar-power-t.label = Solar AC Power #T +thing-type.growatt.inverter.channel.solar-power-t.description = AC power from solar phase #T. +thing-type.growatt.inverter.channel.solar-va.label = Solar VA +thing-type.growatt.inverter.channel.solar-va.description = AC VA from solar. +thing-type.growatt.inverter.channel.sp-bus-voltage.label = SP Bus Voltage +thing-type.growatt.inverter.channel.sp-bus-voltage.description = SP Bus voltage. thing-type.growatt.inverter.channel.sp-display-status.label = Solar Panel Display thing-type.growatt.inverter.channel.sp-display-status.description = Solar panel display status code. thing-type.growatt.inverter.channel.system-fault-0.label = Fault Code #0 @@ -160,47 +178,11 @@ thing-type.growatt.inverter.channel.system-fault-6.description = System fault co thing-type.growatt.inverter.channel.system-fault-7.label = Fault Code #7 thing-type.growatt.inverter.channel.system-fault-7.description = System fault code #7. thing-type.growatt.inverter.channel.system-status.label = Inverter Status -thing-type.growatt.inverter.channel.system-status.description = Status code of the inverter (0=ready, 1=online, 2=fault). +thing-type.growatt.inverter.channel.system-status.description = Status code of the inverter. thing-type.growatt.inverter.channel.system-work-mode.label = System Work Mode thing-type.growatt.inverter.channel.system-work-mode.description = System work mode code. thing-type.growatt.inverter.channel.temperature-4.label = Temp #4 thing-type.growatt.inverter.channel.temperature-4.description = Temperature #4. -thing-type.growatt.inverter.channel.to-grid-energy-today.label = Energy to Grid Today -thing-type.growatt.inverter.channel.to-grid-energy-today.description = Energy supplied to grid today. -thing-type.growatt.inverter.channel.to-grid-energy-total.label = Total Energy to Grid -thing-type.growatt.inverter.channel.to-grid-energy-total.description = Total energy supplied to grid. -thing-type.growatt.inverter.channel.to-grid-power.label = Power to Grid -thing-type.growatt.inverter.channel.to-grid-power.description = Power supplied to grid. -thing-type.growatt.inverter.channel.to-grid-power-r.label = Power to Grid #R -thing-type.growatt.inverter.channel.to-grid-power-r.description = Power supplied to grid phase #R. -thing-type.growatt.inverter.channel.to-grid-power-s.label = Power to Grid #S -thing-type.growatt.inverter.channel.to-grid-power-s.description = Power supplied to grid phase #S. -thing-type.growatt.inverter.channel.to-grid-power-t.label = Power to Grid #T -thing-type.growatt.inverter.channel.to-grid-power-t.description = Power supplied to grid phase #T. -thing-type.growatt.inverter.channel.to-local-energy-today.label = Energy to Local Today -thing-type.growatt.inverter.channel.to-local-energy-today.description = Energy supplied to local today. -thing-type.growatt.inverter.channel.to-local-energy-total.label = Total Energy to Local -thing-type.growatt.inverter.channel.to-local-energy-total.description = Total energy supplied to local. -thing-type.growatt.inverter.channel.to-local-power.label = Power to Local -thing-type.growatt.inverter.channel.to-local-power.description = Power supplied to local. -thing-type.growatt.inverter.channel.to-local-power-r.label = Power to Local #R -thing-type.growatt.inverter.channel.to-local-power-r.description = Power supplied to local phase #R. -thing-type.growatt.inverter.channel.to-local-power-s.label = Power to Local #S -thing-type.growatt.inverter.channel.to-local-power-s.description = Power supplied to local phase #S. -thing-type.growatt.inverter.channel.to-local-power-t.label = Power to Local #T -thing-type.growatt.inverter.channel.to-local-power-t.description = Power supplied to local phase #T. -thing-type.growatt.inverter.channel.to-user-energy-today.label = Energy to User Today -thing-type.growatt.inverter.channel.to-user-energy-today.description = Energy supplied to user today. -thing-type.growatt.inverter.channel.to-user-energy-total.label = Total Energy to User -thing-type.growatt.inverter.channel.to-user-energy-total.description = Total energy supplied to user. -thing-type.growatt.inverter.channel.to-user-power.label = Power to User -thing-type.growatt.inverter.channel.to-user-power.description = Power supplied to user. -thing-type.growatt.inverter.channel.to-user-power-r.label = Power to User #R -thing-type.growatt.inverter.channel.to-user-power-r.description = Power supplied to user phase #R. -thing-type.growatt.inverter.channel.to-user-power-s.label = Power to User #S -thing-type.growatt.inverter.channel.to-user-power-s.description = Power supplied to user phase #S. -thing-type.growatt.inverter.channel.to-user-power-t.label = Power to User #T -thing-type.growatt.inverter.channel.to-user-power-t.description = Power supplied to user phase #T. thing-type.growatt.inverter.channel.total-work-time.label = Total Working Time thing-type.growatt.inverter.channel.total-work-time.description = Total inverter working time. diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 493a2dbf0a07b..f3bedb836b518 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -23,25 +23,25 @@ - Status code of the inverter (0=ready, 1=online, 2=fault). + Status code of the inverter. - Total solar input power. + Total solar DC input power. - Total solar output power. + Total solar AC output power. - + Voltage from solar panel string #1. - + Voltage from solar panel string #2. @@ -69,249 +69,229 @@ Frequency of the grid. - + Voltage of the grid (phase #R). - + Voltage of the grid phase #S. - + Voltage of the grid phase #T. - + Voltage of the grid phases #RS. - + Voltage of the grid phases #ST. - + Voltage of the grid phases #TR. - - - Current delivered to the grid (phase #R). + + + AC current from solar (phase #R). - - - Current delivered to the grid phase #S. + + + AC current from solar phase #S. - - - Current delivered to the grid phase #T. + + + AC current from solar phase #T. - - - Power delivered to the grid (phase #R). + + + AC power from solar (phase #R). - - - Power delivered to the grid phase #S. + + + AC power from solar phase #S. - - - Power delivered to the grid phase #T. + + + AC power from solar phase #T. - - - VA delivered to the grid. + + + AC VA from solar. - - - - Grid current to charge battery. - - + + - Grid power to charge battery. + Charge power to battery. - - - Grid VA to charge battery. + + + Charge current to battery. - - - - - Grid power from discharge of battery. + + + Discharge power from battery. - + - Grid VA from discharge of battery. - - - - - - Battery charge power. - - - - Battery discharge power. - - - - Battery discharge VA. + Discharge VA from battery. - - - - Power supplied to grid. + + + + Power exported to grid. - - - Power supplied to grid phase #R. + + + Power exported to grid phase #R. - - - Power supplied to grid phase #S. + + + Power exported to grid phase #S. - - - Power supplied to grid phase #T. + + + Power exported to grid phase #T. - - - - Power supplied to user. + + + + Power imported. - - - Power supplied to user phase #R. + + + Power imported phase #R. - - - Power supplied to user phase #S. + + + Power imported phase #S. - - - Power supplied to user phase #T. + + + Power imported phase #T. - - - - Power supplied to local. + + + + Power supplied to load. - - - Power supplied to local phase #R. + + + Power supplied to load phase #R. - - - Power supplied to local phase #S. + + + Power supplied to load phase #S. - - - Power supplied to local phase #T. + + + Power supplied to load phase #T. - - + + Solar energy collected today. - + Total solar energy collected. - - - - Solar energy supplied to grid today. + + + + Solar DC energy collected. - - - Solar energy supplied by string #1 to grid today. + + + Solar DC energy collected by string #1 to grid today. - + - Solar energy supplied by string #2 to grid today. + Solar DC energy collected by string #2 to grid today. - - + + Total solar energy supplied to grid. - - - Total solar energy supplied by string #1 to grid . + + + Total solar DC collected by string #1. - - - Total solar energy supplied by string #2 to grid. + + + Total solar DC collected by string #2. - - - - Energy supplied to grid today. + + + + Energy exported to grid today. - - - Total energy supplied to grid. + + + Total energy exported to grid. - - - - Energy supplied to user today. + + + + Energy imported from grid today. - - - Total energy supplied to user. + + + Total energy imported from grid. - - - Energy supplied to local today. + + + Energy supplied to load today. - - - Total energy supplied to local. + + + Total energy supplied to load. - - - - Energy used to charge battery today. + + + + Energy imported from grid to charge battery today. - - - Total energy used to charge battery. + + + Total energy imported from grid to charge battery. - - - - Grid energy produced from battery today. + + + + Energy from solar to charge battery today. - - - Total grid energy produced from battery. + + + Total energy from solar to charge battery. - - - - Energy consumed from battery. + + + + Energy consumed from battery today. - - + + Total energy consumed from battery. @@ -322,17 +302,17 @@ - + P Bus voltage. - + N Bus voltage. - - - N Bus voltage. + + + SP Bus voltage. @@ -366,7 +346,7 @@ Battery temperature. - + Battery voltage. @@ -426,7 +406,10 @@ Constant power OK code. - + + + Percent of full load. + @@ -442,32 +425,6 @@ Total ERAC count. - - - - Output voltage. - - - - Output frequency. - - - - Percent of full load. - - - - Inverter current. - - - - Grid input power. - - - - Grid input VA. - - diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 3da7d5d173bf5..9923292958888 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -81,29 +81,35 @@ private GrottValues loadGrottValues(String fileName) { } /** - * Test that GrottValues implements the same fields as thye GrowattBindingConstants.CHANNEL_ID_UOM_MAP. - * Test that all fields can be accessed and that they are either null or an Integer instance. + * For the given JSON file, test that GrottValues implements the same fields as the + * GrowattBindingConstants.CHANNEL_ID_UOM_MAP. Test that all fields can be accessed and that they are either null or + * an Integer instance. + * + * @param filename the name of the JSON file to be tested. */ - @Test - void testGrottValuesAccessibility() { - GrottValues grottValues = loadGrottValues("simple"); + private void testGrottValuesAccessibilityforFile(String filename) { + GrottValues grottValues = loadGrottValues(filename); List fields = Arrays.asList(GrottValues.class.getFields()).stream().map(f -> f.getName()) .collect(Collectors.toList()); + // test that the GrottValues DTO has identical field names to the CHANNEL_ID_UOM_MAP channel ids for (String channel : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.keySet()) { assertTrue(fields.contains(GrottValues.getFieldName(channel))); } + // test that the CHANNEL_ID_UOM_MAP has identical channel ids to the GrottValues DTO field names for (String field : fields) { assertTrue(GrowattBindingConstants.CHANNEL_ID_UOM_MAP.containsKey(GrottValues.getChannelId(field))); } + // test that the CHANNEL_ID_UOM_MAP and the GrottValues DTO have the same number of fields resp. channel ids assertEquals(fields.size(), GrowattBindingConstants.CHANNEL_ID_UOM_MAP.size()); for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { String channelId = entry.getKey(); Field field; + // test that the field can be accessed try { field = GrottValues.class.getField(GrottValues.getFieldName(channelId)); } catch (NoSuchFieldException e) { @@ -113,6 +119,7 @@ void testGrottValuesAccessibility() { fail(e.getMessage()); continue; } + // test that the field value is either null or an Integer try { Object value = field.get(grottValues); assertTrue(value == null || (value instanceof Integer)); @@ -124,7 +131,16 @@ void testGrottValuesAccessibility() { } /** - * Test that GrottValues is loaded with the correct contents from a JSON file. + * Test the cross mapping of JSON fields and channel names for two JSON test files. + */ + @Test + void testGrottValuesAccessibility() { + testGrottValuesAccessibilityforFile("simple"); + testGrottValuesAccessibilityforFile("sph"); + } + + /** + * Spot checks to test that GrottValues is loaded with the correct contents from the "simple" JSON file. */ @Test void testGrottValuesContents() { @@ -151,14 +167,17 @@ void testGrottValuesContents() { }); assertEquals(QuantityType.ONE, channelStates.get("system-status")); - assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("grid-potential")); - assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("grid-current")); - assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("grid-power")); + assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("grid-voltage")); assertEquals(QuantityType.valueOf(49.97, Units.HERTZ), channelStates.get("grid-frequency")); + + assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("solar-current")); + assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("solar-power")); + assertEquals(QuantityType.valueOf(27.3, SIUnits.CELSIUS), channelStates.get("pv-temperature")); - assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("pv-grid-energy-total")); - assertEquals(QuantityType.valueOf(0, Units.VOLT), channelStates.get("pv2-potential")); + assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("pv1-energy-total")); + + assertEquals(QuantityType.valueOf(0, Units.VOLT), channelStates.get("pv2-voltage")); assertEquals(QuantityType.valueOf(0, Units.AMPERE), channelStates.get("pv2-current")); assertEquals(QuantityType.valueOf(0, Units.WATT), channelStates.get("pv2-power")); @@ -169,5 +188,7 @@ void testGrottValuesContents() { assertNotNull(seconds); assertEquals(QuantityType.valueOf(32751939, Units.SECOND).doubleValue(), seconds.doubleValue(), 0.1); } + + assertNull(channelStates.get("aardvark")); } } diff --git a/bundles/org.openhab.binding.growatt/src/test/resources/sph.json b/bundles/org.openhab.binding.growatt/src/test/resources/sph.json new file mode 100644 index 0000000000000..cd7e8b3fcc159 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/test/resources/sph.json @@ -0,0 +1,75 @@ +{ + "device": "KUM0CLU03Y", + "time": "2023-08-25T17:40:34", + "buffered": "no", + "values": { + "datalogserial": "GPG0DBJ05N", + "pvserial": "KUM0CLU03Y", + "pvstatus": 5, + "pvpowerin": 3100, + "pv1voltage": 3183, + "pv1current": 9, + "pv1watt": 3156, + "pv2voltage": 0, + "pv2current": 0, + "pv2watt": 0, + "pvpowerout": 5530, + "pvfrequentie": 5003, + "pvgridvoltage": 2460, + "pvgridcurrent": 23, + "pvgridpower": 5833, + "pvgridvoltage2": 0, + "pvgridcurrent2": 0, + "pvgridpower2": 0, + "pvgridvoltage3": 0, + "pvgridcurrent3": 0, + "pvgridpower3": 0, + "totworktime": 3998358, + "eactoday": 86, + "pvenergytoday": 86, + "eactotal": 1996, + "epvtotal": 2313, + "epv1today": 125, + "epv1total": 2290, + "epv2today": 0, + "epv2total": 0, + "pvtemperature": 492, + "pvipmtemperature": 459, + "pvboosttemp": 476, + "bat_dsp": 531, + "eacharge_today": 3, + "eacharge_total": 186, + "batterytype": 1, + "uwsysworkmode": 5, + "systemfaultword0": 0, + "systemfaultword1": 0, + "systemfaultword2": 0, + "systemfaultword3": 0, + "systemfaultword4": 0, + "systemfaultword5": 0, + "systemfaultword6": 0, + "systemfaultword7": 0, + "pdischarge1": 3900, + "p1charge1": 0, + "vbat": 530, + "SOC": 80, + "pactouserr": 0, + "pactousertot": 0, + "pactogridr": 0, + "pactogridtot": 0, + "plocaloadr": 7000, + "plocaloadtot": 7000, + "spdspstatus": 5, + "spbusvolt": 3177, + "etouser_tod": 37, + "etouser_tot": 1760, + "etogrid_tod": 2, + "etogrid_tot": 249, + "edischarge1_tod": 33, + "edischarge1_tot": 1036, + "eharge1_tod": 58, + "eharge1_tot": 981, + "elocalload_tod": 136, + "elocalload_tot": 3879 + } +} From a35cda23e1ba685e3ca2dc2b23402efb2961708e Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 10 Sep 2023 16:37:20 +0100 Subject: [PATCH 023/146] [growatt] fixes after live testing Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/dto/GrottValues.java | 8 +- .../handler/GrowattBridgeHandler.java | 2 +- .../handler/GrowattInverterHandler.java | 54 +++++++++---- .../resources/OH-INF/i18n/growatt.properties | 1 + .../binding/growatt/test/GrowattTest.java | 65 ++++++++++++---- .../src/test/resources/sph.json | 78 +++++++++---------- 6 files changed, 134 insertions(+), 74 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 9eec3498b08bb..b3a8f6c14347f 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -134,11 +134,11 @@ public static String getFieldName(String channelId) { // energy supplied to local load public @Nullable @SerializedName(value = "elocalload_tod") Integer load_energy_today; - public @Nullable @SerializedName(value = "elocalloadr_tot") Integer load_energy_total; + public @Nullable @SerializedName(value = "elocalload_tot") Integer load_energy_total; // charging energy from import - public @Nullable @SerializedName(value = "eharge_today") Integer import_charge_energy_today; - public @Nullable @SerializedName(value = "eharge_total") Integer import_charge_energy_total; + public @Nullable @SerializedName(value = "eharge1_tod") Integer import_charge_energy_today; + public @Nullable @SerializedName(value = "eharge1_tot") Integer import_charge_energy_total; // charging energy from solar public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday" }) Integer solar_charge_energy_today; @@ -164,7 +164,7 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "buck2_ntc") Integer pv2_temperature; // battery data - public @Nullable @SerializedName(value = "batteryType") Integer battery_type; + public @Nullable @SerializedName(value = "batterytype") Integer battery_type; public @Nullable @SerializedName(value = "batttemp") Integer battery_temperature; public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt" }) Integer battery_voltage; public @Nullable @SerializedName(value = "bat_dsp") Integer battery_display; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 2abe4350546f4..5ccc736cf3718 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -96,7 +96,7 @@ public void handleGrottContent(String json) { } getThing().getThings().stream().map(thing -> thing.getHandler()) .filter(handler -> (handler instanceof GrowattInverterHandler)) - .forEach(handler -> ((GrowattInverterHandler) handler).handleInverters(inverters.values())); + .forEach(handler -> ((GrowattInverterHandler) handler).updateInverters(inverters.values())); } @Override diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 4c02ddb52c93b..d4d98f970c17c 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -15,9 +15,12 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; @@ -44,15 +47,44 @@ public class GrowattInverterHandler extends BaseThingHandler { private String deviceId = "unknown"; + private @Nullable ScheduledFuture awaitingDataTimeoutTask; + public GrowattInverterHandler(Thing thing) { super(thing); } + @Override + public void dispose() { + ScheduledFuture task = awaitingDataTimeoutTask; + if (task != null) { + task.cancel(true); + } + } + @Override public void handleCommand(ChannelUID channelUID, Command command) { // everything is read only so do nothing } + @Override + public void initialize() { + GrowattInverterConfiguration config = getConfigAs(GrowattInverterConfiguration.class); + deviceId = config.deviceId; + thing.setProperty(GrowattInverterConfiguration.DEVICE_ID, deviceId); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/status.awaiting-data"); + scheduleAwaitingDataTimeoutTask(); + logger.debug("initialize() thing has {} channels", thing.getChannels().size()); + } + + private void scheduleAwaitingDataTimeoutTask() { + ScheduledFuture task = awaitingDataTimeoutTask; + if (task != null) { + task.cancel(true); + } + awaitingDataTimeoutTask = scheduler.schedule(() -> updateStatus(ThingStatus.OFFLINE, + ThingStatusDetail.COMMUNICATION_ERROR, "@text/status.awaiting-data-timeout"), 5, TimeUnit.MINUTES); + } + /** * Receives a collection of GrottDevice inverter objects containing potential data for this thing. If the collection * contains an entry matching the things's deviceId, and it contains GrottValues, then process it further. Otherwise @@ -60,12 +92,13 @@ public void handleCommand(ChannelUID channelUID, Command command) { * * @param inverters collection of GrottDevice objects. */ - public void handleInverters(Collection inverters) { + public void updateInverters(Collection inverters) { inverters.stream().filter(inverter -> deviceId.equals(inverter.getDeviceId())) .map(inverter -> inverter.getValues()).filter(values -> values != null).findAny() .ifPresentOrElse(values -> { updateStatus(ThingStatus.ONLINE); - handleInverterValues(values); + scheduleAwaitingDataTimeoutTask(); + updateInverterValues(values); }, () -> { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); }); @@ -77,13 +110,13 @@ public void handleInverters(Collection inverters) { * * @param inverter a GrottDevice object containing the new status values. */ - public void handleInverterValues(GrottValues inverterValues) { + public void updateInverterValues(GrottValues inverterValues) { // get channel states Map> channelStates; try { channelStates = inverterValues.getChannelStates(); } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) { - logger.warn("handleInverterValues() unexpected exception:{}, message:{}", e.getClass().getName(), + logger.warn("updateInverterValues() unexpected exception:{}, message:{}", e.getClass().getName(), e.getMessage(), e); return; } @@ -96,7 +129,7 @@ public void handleInverterValues(GrottValues inverterValues) { // remove unused channels if (!unusedChannels.isEmpty()) { updateThing(editThing().withoutChannels(unusedChannels).build()); - logger.debug("handleInverterValues() channel count {} reduced by {} to {}", actualChannels.size(), + logger.debug("updateInverterValues() channel count {} reduced by {} to {}", actualChannels.size(), unusedChannels.size(), thing.getChannels().size()); } @@ -108,17 +141,8 @@ public void handleInverterValues(GrottValues inverterValues) { if (thingChannelIds.contains(channelId)) { updateState(channelId, state); } else { - logger.debug("handleInverterValues() channel '{}' not found; try re-creating the thing", channelId); + logger.warn("updateInverterValues() channel '{}' not found; try re-creating the thing", channelId); } }); } - - @Override - public void initialize() { - GrowattInverterConfiguration config = getConfigAs(GrowattInverterConfiguration.class); - deviceId = config.deviceId; - thing.setProperty(GrowattInverterConfiguration.DEVICE_ID, deviceId); - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/status.awaiting-data"); - logger.debug("initialize() thing has {} channels", thing.getChannels().size()); - } } diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 7332d1c7c53b5..f52df09cd5baa 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -213,3 +213,4 @@ discovery.growatt-inverter = Growatt Inverter # thing status status.awaiting-data = Waiting for data from Grott application +status.awaiting-data-timeout = Timed out waiting for data from Grott application diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 9923292958888..c63ec0ad36e57 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -36,6 +36,8 @@ import org.openhab.core.types.State; import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; /** * The {@link GrowattTest} is a JUnit test suite for the Growatt binding. @@ -48,7 +50,7 @@ public class GrowattTest { private final Gson gson = new Gson(); /** - * load a string from a file + * Load a (JSON) string from a file */ private String load(String fileName) { try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName)); @@ -68,8 +70,8 @@ private String load(String fileName) { /** * Load a GrottValues class from a JSON payload. * - * @param fileName the file containing the json payload. - * @return a GrottValues dto. + * @param fileName the file containing the JSON payload. + * @return a GrottValues DTO. */ private GrottValues loadGrottValues(String fileName) { String json = load(fileName); @@ -80,15 +82,21 @@ private GrottValues loadGrottValues(String fileName) { return grottValues; } + @Test + void testGrottValuesAccessibility() { + testGrottValuesAccessibility("simple"); + testGrottValuesAccessibility("sph"); + } + /** * For the given JSON file, test that GrottValues implements the same fields as the * GrowattBindingConstants.CHANNEL_ID_UOM_MAP. Test that all fields can be accessed and that they are either null or * an Integer instance. * - * @param filename the name of the JSON file to be tested. + * @param fileName the name of the JSON file to be tested. */ - private void testGrottValuesAccessibilityforFile(String filename) { - GrottValues grottValues = loadGrottValues(filename); + private void testGrottValuesAccessibility(String fileName) { + GrottValues grottValues = loadGrottValues(fileName); List fields = Arrays.asList(GrottValues.class.getFields()).stream().map(f -> f.getName()) .collect(Collectors.toList()); @@ -130,15 +138,6 @@ private void testGrottValuesAccessibilityforFile(String filename) { } } - /** - * Test the cross mapping of JSON fields and channel names for two JSON test files. - */ - @Test - void testGrottValuesAccessibility() { - testGrottValuesAccessibilityforFile("simple"); - testGrottValuesAccessibilityforFile("sph"); - } - /** * Spot checks to test that GrottValues is loaded with the correct contents from the "simple" JSON file. */ @@ -191,4 +190,40 @@ void testGrottValuesContents() { assertNull(channelStates.get("aardvark")); } + + @Test + void testJsonFieldsMappedToDto() { + testJsonFieldsMappedToDto("simple"); + testJsonFieldsMappedToDto("sph"); + } + + /** + * For the given JSON test file name, check that each field in its JSON is mapped to precisely one field in the + * values DTO. + * + * @param fileName the name of the JSON file to be tested. + */ + private void testJsonFieldsMappedToDto(String fileName) { + Field[] fields = GrottValues.class.getFields(); + String json = load(fileName); + JsonParser.parseString(json).getAsJsonObject().get("values").getAsJsonObject().entrySet().forEach(e -> { + String key = e.getKey(); + if (!"datalogserial".equals(key) && !"pvserial".equals(key)) { + JsonObject testJsonObject = new JsonObject(); + testJsonObject.add(key, e.getValue()); + GrottValues testDto = gson.fromJson(testJsonObject, GrottValues.class); + int mappedFieldCount = 0; + for (Field field : fields) { + try { + if (field.get(testDto) != null) { + mappedFieldCount++; + } + } catch (IllegalAccessException | IllegalArgumentException ex) { + fail("Exception"); + } + } + assertEquals(1, mappedFieldCount); + } + }); + } } diff --git a/bundles/org.openhab.binding.growatt/src/test/resources/sph.json b/bundles/org.openhab.binding.growatt/src/test/resources/sph.json index cd7e8b3fcc159..a6ce013394a90 100644 --- a/bundles/org.openhab.binding.growatt/src/test/resources/sph.json +++ b/bundles/org.openhab.binding.growatt/src/test/resources/sph.json @@ -1,44 +1,44 @@ { "device": "KUM0CLU03Y", - "time": "2023-08-25T17:40:34", + "time": "2023-09-10T12:23:13", "buffered": "no", "values": { "datalogserial": "GPG0DBJ05N", "pvserial": "KUM0CLU03Y", "pvstatus": 5, - "pvpowerin": 3100, - "pv1voltage": 3183, - "pv1current": 9, - "pv1watt": 3156, + "pvpowerin": 16100, + "pv1voltage": 1805, + "pv1current": 89, + "pv1watt": 16169, "pv2voltage": 0, "pv2current": 0, "pv2watt": 0, - "pvpowerout": 5530, + "pvpowerout": 4285, "pvfrequentie": 5003, - "pvgridvoltage": 2460, - "pvgridcurrent": 23, - "pvgridpower": 5833, + "pvgridvoltage": 2443, + "pvgridcurrent": 18, + "pvgridpower": 4609, "pvgridvoltage2": 0, "pvgridcurrent2": 0, "pvgridpower2": 0, "pvgridvoltage3": 0, "pvgridcurrent3": 0, "pvgridpower3": 0, - "totworktime": 3998358, - "eactoday": 86, - "pvenergytoday": 86, - "eactotal": 1996, - "epvtotal": 2313, - "epv1today": 125, - "epv1total": 2290, + "totworktime": 6723587, + "eactoday": 27, + "pvenergytoday": 27, + "eactotal": 3571, + "epvtotal": 4105, + "epv1today": 64, + "epv1total": 4057, "epv2today": 0, "epv2total": 0, - "pvtemperature": 492, - "pvipmtemperature": 459, - "pvboosttemp": 476, - "bat_dsp": 531, - "eacharge_today": 3, - "eacharge_total": 186, + "pvtemperature": 576, + "pvipmtemperature": 527, + "pvboosttemp": 572, + "bat_dsp": 541, + "eacharge_today": 10, + "eacharge_total": 277, "batterytype": 1, "uwsysworkmode": 5, "systemfaultword0": 0, @@ -49,27 +49,27 @@ "systemfaultword5": 0, "systemfaultword6": 0, "systemfaultword7": 0, - "pdischarge1": 3900, - "p1charge1": 0, - "vbat": 530, - "SOC": 80, + "pdischarge1": 0, + "p1charge1": 10284, + "vbat": 539, + "SOC": 69, "pactouserr": 0, "pactousertot": 0, "pactogridr": 0, "pactogridtot": 0, - "plocaloadr": 7000, - "plocaloadtot": 7000, + "plocaloadr": 5800, + "plocaloadtot": 5800, "spdspstatus": 5, - "spbusvolt": 3177, - "etouser_tod": 37, - "etouser_tot": 1760, - "etogrid_tod": 2, - "etogrid_tot": 249, - "edischarge1_tod": 33, - "edischarge1_tot": 1036, - "eharge1_tod": 58, - "eharge1_tot": 981, - "elocalload_tod": 136, - "elocalload_tot": 3879 + "spbusvolt": 3290, + "etouser_tod": 54, + "etouser_tot": 2330, + "etogrid_tod": 1, + "etogrid_tot": 707, + "edischarge1_tod": 3, + "edischarge1_tot": 1652, + "eharge1_tod": 41, + "eharge1_tot": 1524, + "elocalload_tod": 80, + "elocalload_tot": 5856 } } From cf061ec506d7e38365bf6723dc42b93aca70f7bc Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 10 Sep 2023 16:46:12 +0100 Subject: [PATCH 024/146] growatt] fix typos Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 8fc34d809e47a..80f4a7b24dff5 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -154,12 +154,12 @@ Number:Energy Solar_Output_Energy "Solar Output Energy Total" {channel="growatt: You can install the Grott application either on the same computer as OpenHAB or on another. The following assumes you will be running it on the same computer. -Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. +The Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. It intercepts data packets between the inverter and the cloud server, and it sends a copy of the intercepted data also to OpenHAB. **NOTE**: make sure that the Grott application is **FULLY OPERATIONAL** for your inverter **BEFORE** you create any things in OpenHAB! Otherwise the binding might create a wrong (or even empty) list of channels for the inverter thing. -(Yet if you do make that mistake you can rectify it by deleteing and recreating the thing). +(Yet if you do make that mistake you can rectify it by deleting and recreating the thing). You should configure the Grott application via its `grott.ini` file. Configure Grott to match your inverter according to the [instructions](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor). @@ -203,7 +203,7 @@ pvoutput = False // disable pvoutput [extension] extension = True extname = grottext -extvar = {"url": "http://xxx.xxx.xxx.xxx:8080/growatt"} +extvar = {"url": "http://xxx.xxx.xxx.xxx:8080/growatt"} // ip address of openhab ``` ### Start Grott as a Service From e80a2b2c35d36d15a780f10c622aaf36d2022a23 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 11 Sep 2023 17:46:02 +0100 Subject: [PATCH 025/146] [growatt] refactoring; fix channel ids, descriptions Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 36 ++-- .../internal/GrowattBindingConstants.java | 173 +--------------- .../growatt/internal/GrowattChannels.java | 196 ++++++++++++++++++ .../growatt/internal/dto/GrottValues.java | 58 +++--- .../factory/GrowattHandlerFactory.java | 8 +- .../handler/GrowattBridgeHandler.java | 7 +- .../handler/GrowattInverterHandler.java | 9 +- ...tpServlet.java => GrowattHttpServlet.java} | 4 +- .../resources/OH-INF/i18n/growatt.properties | 56 ++--- .../resources/OH-INF/thing/thing-types.xml | 86 ++++---- .../binding/growatt/test/GrowattTest.java | 55 +++-- 11 files changed, 369 insertions(+), 319 deletions(-) create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java rename bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/{GrottHttpServlet.java => GrowattHttpServlet.java} (95%) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 80f4a7b24dff5..b355941053199 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -43,32 +43,28 @@ The list of all possible channels is as follows: | Channel | Type | Description | Advanced | |-------------------------------|---------------------------|------------------------------------------------------|----------| | system-status | Number:Dimensionless | Inverter status code. | | -| pv-power-in | Number:Power | Total DC solar input power. | | -| pv-power-out | Number:Power | Total AC solar output power. | | | pv1-voltage | Number:ElectricPotential | DC voltage from solar panel string #1. | yes | | pv2-voltage | Number:ElectricPotential | DC voltage from solar panel string #2. | yes | | pv1-current | Number:ElectricCurrent | DC current from solar panel string #1. | yes | | pv2-current | Number:ElectricCurrent | DC current from solar panel string #2. | yes | +| pv-power | Number:Power | Total DC solar input power. | | | pv1-power | Number:Power | DC power from solar panel string #1. | yes | | pv2-power | Number:Power | DC power from solar panel string #2. | yes | | grid-frequency | Number:Frequency | Frequency of the grid. | yes | -| grid-voltage | Number:ElectricPotential | Voltage of the grid (phase #R). | | +| grid-voltage-r | Number:ElectricPotential | Voltage of the grid (phase #R). | | | grid-voltage-s | Number:ElectricPotential | Voltage of the grid phase #S. | yes | | grid-voltage-t | Number:ElectricPotential | Voltage of the grid phase #T. | yes | | grid-voltage-rs | Number:ElectricPotential | Voltage of the grid phases #RS. | yes | | grid-voltage-st | Number:ElectricPotential | Voltage of the grid phases #ST. | yes | | grid-voltage-tr | Number:ElectricPotential | Voltage of the grid phases #TR. | yes | -| solar-current | Number:ElectricCurrent | AC current from solar (phase #R). | yes | -| solar-current-s | Number:ElectricCurrent | AC current from solar phase #S. | yes | -| solar-current-t | Number:ElectricCurrent | AC current from solar phase #T. | yes | -| solar-power | Number:Power | AC power from solar (phase #R). | | -| solar-power-s | Number:Power | AC power from solar phase #S. | yes | -| solar-power-t | Number:Power | AC power from solar phase #T. | yes | -| solar-va | Number:Power | AC VA from solar. | yes | -| charge-power | Number:Power | Battery charge power. | | -| charge-current | Number:ElectricCurrent | Battery charge current. | yes | -| discharge-power | Number:Power | Battery discharge power. | | -| discharge-va | Number:Power | Battery discharge VA. | yes | +| inverter-current-r | Number:ElectricCurrent | AC current from inverter (phase #R). | yes | +| inverter-current-s | Number:ElectricCurrent | AC current from inverter phase #S. | yes | +| inverter-current-t | Number:ElectricCurrent | AC current from inverter phase #T. | yes | +| inverter-power | Number:Power | Total AC output power from inverter. | | +| inverter-power-r | Number:Power | AC power from inverter (phase #R). | | +| inverter-power-s | Number:Power | AC power from inverter phase #S. | yes | +| inverter-power-t | Number:Power | AC power from inverter phase #T. | yes | +| inverter-va | Number:Power | AC VA from inverter. | yes | | export-power | Number:Power | Power exported to grid. | | | export-power-r | Number:Power | Power exported to grid phase #R. | yes | | export-power-s | Number:Power | Power exported to grid phase #S. | yes | @@ -81,14 +77,18 @@ The list of all possible channels is as follows: | load-power-r | Number:Power | Power supplied to load phase #R. | yes | | load-power-s | Number:Power | Power supplied to load phase #S. | yes | | load-power-t | Number:Power | Power supplied to load phase #T. | yes | -| solar-energy-today | Number:Energy | Solar AC energy produced today. | | -| solar-energy-total | Number:Energy | Total solar AC energy produced. | | +| charge-power | Number:Power | Battery charge power. | | +| charge-current | Number:ElectricCurrent | Battery charge current. | yes | +| discharge-power | Number:Power | Battery discharge power. | | +| discharge-va | Number:Power | Battery discharge VA. | yes | | pv-energy-today | Number:Energy | DC energy collected by solar panels today. | | | pv1-energy-today | Number:Energy | DC energy collected by solar panels string #1 today. | yes | | pv2-energy-today | Number:Energy | DC energy collected by solar panels string #2 today. | yes | | pv-energy-total | Number:Energy | Total DC energy collected by solar panels. | | | pv1-energy-total | Number:Energy | Total DC energy collected by solar panels string #1. | yes | | pv2-energy-total | Number:Energy | Total DC energy collected by solar panels string #2. | yes | +| inverter-energy-today | Number:Energy | AC energy produced by inverter today. | | +| inverter-energy-total | Number:Energy | Total AC energy produced by inverter. | | | export-energy-today | Number:Energy | Energy exported today. | | | export-energy-total | Number:Energy | Total energy exported. | | | import-energy-today | Number:Energy | Energy imported today. | | @@ -97,8 +97,8 @@ The list of all possible channels is as follows: | load-energy-total | Number:Energy | Total energy supplied to load. | | | import-charge-energy-today | Number:Energy | Energy imported to charge battery today. | | | import-charge-energy-total | Number:Energy | Total energy imported to charge battery. | | -| solar-charge-energy-today | Number:Energy | Solar energy to charge battery today. | | -| solar-charge-energy-total | Number:Energy | Total solar energy to charge battery. | | +| inverter-charge-energy-today | Number:Energy | Inverter energy to charge battery today. | | +| inverter-charge-energy-total | Number:Energy | Total inverter energy to charge battery. | | | discharge-energy-today | Number:Energy | Energy consumed from battery. | | | discharge-energy-total | Number:Energy | Total energy consumed from battery. | | | total-work-time | Number:Time | Total work time of the system. | yes | diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java index 448fe3ee61011..6fcfcbb40f4f2 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -12,14 +12,7 @@ */ package org.openhab.binding.growatt.internal; -import java.util.AbstractMap; -import java.util.Map; - -import javax.measure.Unit; - import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; import org.openhab.core.thing.ThingTypeUID; /** @@ -31,172 +24,8 @@ @NonNullByDefault public class GrowattBindingConstants { - private static final String BINDING_ID = "growatt"; + public static final String BINDING_ID = "growatt"; - /** - * List of Thing Type UIDs - */ public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); public static final ThingTypeUID THING_TYPE_INVERTER = new ThingTypeUID(BINDING_ID, "inverter"); - - /** - * Class encapsulating units of measure and scale information. - */ - public static class UoM { - public final Unit units; - public final float divisor; - - public UoM(Unit units, float divisor) { - this.units = units; - this.divisor = divisor; - } - } - - /** - * Map of supported channel ids and respective UoM - */ - public static final Map CHANNEL_ID_UOM_MAP = Map.ofEntries( - // inverter state - new AbstractMap.SimpleEntry("system-status", new UoM(Units.ONE, 1)), - - // solar generation - new AbstractMap.SimpleEntry("pv-power-in", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("pv-power-out", new UoM(Units.WATT, 10)), - - // electric data for strings #1 and #2 - new AbstractMap.SimpleEntry("pv1-voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pv1-current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("pv1-power", new UoM(Units.WATT, 10)), - - new AbstractMap.SimpleEntry("pv2-voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("pv2-current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("pv2-power", new UoM(Units.WATT, 10)), - - // grid electric data (1-phase resp. 3-phase) - new AbstractMap.SimpleEntry("grid-frequency", new UoM(Units.HERTZ, 100)), - - new AbstractMap.SimpleEntry("grid-voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-voltage-s", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-voltage-t", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-voltage-rs", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-voltage-st", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("grid-voltage-tr", new UoM(Units.VOLT, 10)), - - // solar power to grid - new AbstractMap.SimpleEntry("solar-current", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("solar-current-s", new UoM(Units.AMPERE, 10)), - new AbstractMap.SimpleEntry("solar-current-t", new UoM(Units.AMPERE, 10)), - - new AbstractMap.SimpleEntry("solar-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("solar-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("solar-power-t", new UoM(Units.WATT, 10)), - - new AbstractMap.SimpleEntry("solar-va", new UoM(Units.VOLT_AMPERE, 10)), - - // battery discharge / charge power - new AbstractMap.SimpleEntry("charge-current", new UoM(Units.VOLT_AMPERE, 10)), - new AbstractMap.SimpleEntry("charge-power", new UoM(Units.WATT, 10)), - - new AbstractMap.SimpleEntry("discharge-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("discharge-va", new UoM(Units.VOLT_AMPERE, 10)), - - // export power to grid - new AbstractMap.SimpleEntry("export-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("export-power-r", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("export-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("export-power-t", new UoM(Units.WATT, 10)), - - // power to user - new AbstractMap.SimpleEntry("import-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("import-power-r", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("import-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("import-power-t", new UoM(Units.WATT, 10)), - - // power to local - new AbstractMap.SimpleEntry("load-power", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("load-power-r", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("load-power-s", new UoM(Units.WATT, 10)), - new AbstractMap.SimpleEntry("load-power-t", new UoM(Units.WATT, 10)), - - // solar AC energy - new AbstractMap.SimpleEntry("solar-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("solar-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // solar DC energy - new AbstractMap.SimpleEntry("pv-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv1-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv2-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - - new AbstractMap.SimpleEntry("pv-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv1-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("pv2-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // energy exported to grid - new AbstractMap.SimpleEntry("export-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("export-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // energy imported from grid - new AbstractMap.SimpleEntry("import-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("import-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // energy supplied to load - new AbstractMap.SimpleEntry("load-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("load-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // energy imported to charge - new AbstractMap.SimpleEntry("import-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("import-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // solar energy to charge - new AbstractMap.SimpleEntry("solar-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("solar-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // energy supplied from discharge - new AbstractMap.SimpleEntry("discharge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("discharge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), - - // inverter up time - new AbstractMap.SimpleEntry("total-work-time", new UoM(Units.HOUR, 7200)), - - // bus voltages - new AbstractMap.SimpleEntry("p-bus-voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("n-bus-voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("sp-bus-voltage", new UoM(Units.VOLT, 10)), - - // temperatures - new AbstractMap.SimpleEntry("pv-temperature", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("pv-ipm-temperature", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("pv-boost-temperature", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("temperature-4", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("pv2-temperature", new UoM(SIUnits.CELSIUS, 10)), - - // battery data - new AbstractMap.SimpleEntry("battery-type", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("battery-voltage", new UoM(Units.VOLT, 10)), - new AbstractMap.SimpleEntry("battery-temperature", new UoM(SIUnits.CELSIUS, 10)), - new AbstractMap.SimpleEntry("battery-display", new UoM(Units.ONE, 10)), - new AbstractMap.SimpleEntry("battery-soc", new UoM(Units.PERCENT, 1)), - - // fault codes - new AbstractMap.SimpleEntry("system-fault-0", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("system-fault-1", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("system-fault-2", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("system-fault-3", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("system-fault-4", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("system-fault-5", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("system-fault-6", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("system-fault-7", new UoM(Units.ONE, 1)), - - // miscellaneous - new AbstractMap.SimpleEntry("system-work-mode", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("sp-display-status", new UoM(Units.ONE, 10)), - new AbstractMap.SimpleEntry("constant-power-ok", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("load-percent", new UoM(Units.PERCENT, 10)), - - // rac ?? - new AbstractMap.SimpleEntry("rac", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("erac-today", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("erac-total", new UoM(Units.ONE, 1)) - // - ); } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java new file mode 100644 index 0000000000000..63b3fbce06438 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal; + +import java.util.AbstractMap; +import java.util.Map; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; + +/** + * The {@link GrowattChannels} class defines the channel ids and respective UoM and scaling factors. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattChannels { + + /** + * Class encapsulating units of measure and scale information. + */ + public static class UoM { + public final Unit units; + public final float divisor; + + public UoM(Unit units, float divisor) { + this.units = units; + this.divisor = divisor; + } + } + + /** + * Map of the channel ids to their respective UoM and scaling factors + */ + private static final Map CHANNEL_ID_UOM_MAP = Map.ofEntries( + // inverter state + new AbstractMap.SimpleEntry("system-status", new UoM(Units.ONE, 1)), + + // solar generation + new AbstractMap.SimpleEntry("pv-power", new UoM(Units.WATT, 10)), + + // electric data for strings #1 and #2 + new AbstractMap.SimpleEntry("pv1-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv1-current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pv1-power", new UoM(Units.WATT, 10)), + + new AbstractMap.SimpleEntry("pv2-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("pv2-current", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("pv2-power", new UoM(Units.WATT, 10)), + + // grid electric data (1-phase resp. 3-phase) + new AbstractMap.SimpleEntry("grid-frequency", new UoM(Units.HERTZ, 100)), + + new AbstractMap.SimpleEntry("grid-voltage-r", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-s", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-t", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-rs", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-st", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("grid-voltage-tr", new UoM(Units.VOLT, 10)), + + // inverter output + new AbstractMap.SimpleEntry("inverter-current-r", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("inverter-current-s", new UoM(Units.AMPERE, 10)), + new AbstractMap.SimpleEntry("inverter-current-t", new UoM(Units.AMPERE, 10)), + + new AbstractMap.SimpleEntry("inverter-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("inverter-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("inverter-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("inverter-power-t", new UoM(Units.WATT, 10)), + + new AbstractMap.SimpleEntry("inverter-va", new UoM(Units.VOLT_AMPERE, 10)), + + // battery discharge / charge power + new AbstractMap.SimpleEntry("charge-current", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("charge-power", new UoM(Units.WATT, 10)), + + new AbstractMap.SimpleEntry("discharge-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("discharge-va", new UoM(Units.VOLT_AMPERE, 10)), + + // export power to grid + new AbstractMap.SimpleEntry("export-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("export-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("export-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("export-power-t", new UoM(Units.WATT, 10)), + + // power to user + new AbstractMap.SimpleEntry("import-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("import-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("import-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("import-power-t", new UoM(Units.WATT, 10)), + + // power to local + new AbstractMap.SimpleEntry("load-power", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("load-power-r", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("load-power-s", new UoM(Units.WATT, 10)), + new AbstractMap.SimpleEntry("load-power-t", new UoM(Units.WATT, 10)), + + // inverter output energy + new AbstractMap.SimpleEntry("inverter-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("inverter-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // solar DC input energy + new AbstractMap.SimpleEntry("pv-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv1-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv2-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + + new AbstractMap.SimpleEntry("pv-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv1-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("pv2-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy exported to grid + new AbstractMap.SimpleEntry("export-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("export-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy imported from grid + new AbstractMap.SimpleEntry("import-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("import-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy supplied to load + new AbstractMap.SimpleEntry("load-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("load-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy imported to charge + new AbstractMap.SimpleEntry("import-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("import-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // inverter energy to charge + new AbstractMap.SimpleEntry("inverter-charge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("inverter-charge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // energy supplied from discharge + new AbstractMap.SimpleEntry("discharge-energy-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("discharge-energy-total", new UoM(Units.KILOWATT_HOUR, 10)), + + // inverter up time + new AbstractMap.SimpleEntry("total-work-time", new UoM(Units.HOUR, 7200)), + + // bus voltages + new AbstractMap.SimpleEntry("p-bus-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("n-bus-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("sp-bus-voltage", new UoM(Units.VOLT, 10)), + + // temperatures + new AbstractMap.SimpleEntry("pv-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pv-ipm-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pv-boost-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("temperature-4", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("pv2-temperature", new UoM(SIUnits.CELSIUS, 10)), + + // battery data + new AbstractMap.SimpleEntry("battery-type", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("battery-voltage", new UoM(Units.VOLT, 10)), + new AbstractMap.SimpleEntry("battery-temperature", new UoM(SIUnits.CELSIUS, 10)), + new AbstractMap.SimpleEntry("battery-display", new UoM(Units.ONE, 10)), + new AbstractMap.SimpleEntry("battery-soc", new UoM(Units.PERCENT, 1)), + + // fault codes + new AbstractMap.SimpleEntry("system-fault-0", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-1", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-2", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-3", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-4", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-5", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-6", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("system-fault-7", new UoM(Units.ONE, 1)), + + // miscellaneous + new AbstractMap.SimpleEntry("system-work-mode", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("sp-display-status", new UoM(Units.ONE, 10)), + new AbstractMap.SimpleEntry("constant-power-ok", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("load-percent", new UoM(Units.PERCENT, 10)), + + // rac ?? + new AbstractMap.SimpleEntry("rac", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("erac-today", new UoM(Units.ONE, 1)), + new AbstractMap.SimpleEntry("erac-total", new UoM(Units.ONE, 1)) + // + ); + + public static Map getMap() { + return GrowattChannels.CHANNEL_ID_UOM_MAP; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index b3a8f6c14347f..41aecac895f57 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -12,15 +12,13 @@ */ package org.openhab.binding.growatt.internal.dto; -import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.growatt.internal.GrowattBindingConstants; -import org.openhab.binding.growatt.internal.GrowattBindingConstants.UoM; +import org.openhab.binding.growatt.internal.GrowattChannels; +import org.openhab.binding.growatt.internal.GrowattChannels.UoM; import org.openhab.core.library.types.QuantityType; import com.google.gson.annotations.SerializedName; @@ -53,8 +51,8 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "pvstatus") Integer system_status; // solar AC and DC generation - public @Nullable @SerializedName(value = "pvpowerin") Integer pv_power_in; // from DC solar - public @Nullable @SerializedName(value = "pvpowerout") Integer pv_power_out; // to AC mains + public @Nullable @SerializedName(value = "pvpowerin") Integer pv_power; // from DC solar + public @Nullable @SerializedName(value = "pvpowerout") Integer inverter_power; // to AC mains // DC electric data for strings #1 and #2 public @Nullable @SerializedName(value = "pv1voltage", alternate = { "vpv1" }) Integer pv1_voltage; @@ -67,7 +65,7 @@ public static String getFieldName(String channelId) { // AC mains electric data (1-phase resp. 3-phase) public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq", "outputfreq" }) Integer grid_frequency; - public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt", "outputvolt" }) Integer grid_voltage; + public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt", "outputvolt" }) Integer grid_voltage_r; public @Nullable @SerializedName(value = "pvgridvoltage2") Integer grid_voltage_s; public @Nullable @SerializedName(value = "pvgridvoltage3") Integer grid_voltage_t; public @Nullable @SerializedName(value = "Vac_RS") Integer grid_voltage_rs; @@ -75,15 +73,15 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "Vac_TR") Integer grid_voltage_tr; // solar AC mains power - public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr", "Inv_Curr" }) Integer solar_current; - public @Nullable @SerializedName(value = "pvgridcurrent2") Integer solar_current_s; - public @Nullable @SerializedName(value = "pvgridcurrent3") Integer solar_current_t; + public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr", "Inv_Curr" }) Integer inverter_current_r; + public @Nullable @SerializedName(value = "pvgridcurrent2") Integer inverter_current_s; + public @Nullable @SerializedName(value = "pvgridcurrent3") Integer inverter_current_t; - public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt", "AC_InWatt" }) Integer solar_power; - public @Nullable @SerializedName(value = "pvgridpower2") Integer solar_power_s; - public @Nullable @SerializedName(value = "pvgridpower3") Integer solar_power_t; + public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt", "AC_InWatt" }) Integer inverter_power_r; + public @Nullable @SerializedName(value = "pvgridpower2") Integer inverter_power_s; + public @Nullable @SerializedName(value = "pvgridpower3") Integer inverter_power_t; - public @Nullable @SerializedName(value = "op_va", alternate = { "AC_InVA" }) Integer solar_va; + public @Nullable @SerializedName(value = "op_va", alternate = { "AC_InVA" }) Integer inverter_va; // battery discharge / charge power public @Nullable @SerializedName(value = "p1charge1", alternate = { "acchr_watt", "BatWatt" }) Integer charge_power; @@ -112,8 +110,8 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "plocaloadt") Integer load_power_t; // solar AC grid energy - public @Nullable @SerializedName(value = "eactoday", alternate = { "pvenergytoday" }) Integer solar_energy_today; - public @Nullable @SerializedName(value = "eactotal", alternate = { "pvenergytotal" }) Integer solar_energy_total; + public @Nullable @SerializedName(value = "eactoday", alternate = { "pvenergytoday" }) Integer inverter_energy_today; + public @Nullable @SerializedName(value = "eactotal", alternate = { "pvenergytotal" }) Integer inverter_energy_total; // solar DC pv energy public @Nullable @SerializedName(value = "epvtoday") Integer pv_energy_today; @@ -141,8 +139,8 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "eharge1_tot") Integer import_charge_energy_total; // charging energy from solar - public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday" }) Integer solar_charge_energy_today; - public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal" }) Integer solar_charge_energy_total; + public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday" }) Integer inverter_charge_energy_today; + public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal" }) Integer inverter_charge_energy_total; // discharging energy public @Nullable @SerializedName(value = "edischarge1_tod", alternate = { "eacDischarToday", "ebatDischarToday" }) Integer discharge_energy_today; @@ -197,23 +195,25 @@ public static String getFieldName(String channelId) { * Return the valid values from this DTO in a map between channel id and respective QuantityType states. * * @return a map of channel ids and respective QuantityType state values. - * @throws NoSuchFieldException should not occur since we specifically tested this in JUnit tests. - * @throws SecurityException should not occur since all fields are public. - * @throws IllegalAccessException should not occur since all fields are public. - * @throws IllegalArgumentException should not occur since we are specifically working with this class. */ public Map> getChannelStates() throws NoSuchFieldException, SecurityException, IllegalAccessException, IllegalArgumentException { Map> map = new HashMap<>(); - for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { + GrowattChannels.getMap().entrySet().forEach(entry -> { String channelId = entry.getKey(); - UoM uom = entry.getValue(); - Field field = getClass().getField(getFieldName(channelId)); - Object obj = field.get(this); - if (obj instanceof Integer) { - map.put(channelId, QuantityType.valueOf(((Integer) obj).doubleValue() / uom.divisor, uom.units)); + try { + Object field = getClass().getField(getFieldName(channelId)).get(this); + if (field instanceof Integer) { + UoM uom = entry.getValue(); + map.put(channelId, QuantityType.valueOf(((Integer) field).doubleValue() / uom.divisor, uom.units)); + } + } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) { + // Ignore exceptions because they never actually occur at run time.. + // - NoSuchFieldException never occurs since we have explicitly tested this in the JUnit tests. + // - SecurityException, IllegalAccessException never occur since all fields are public. + // - IllegalArgumentException never occurs since we are explicitly working within this same class. } - } + }); return map; } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index aacfef82aaeb5..9f1c08789a807 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -27,7 +27,7 @@ import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.handler.GrowattBridgeHandler; import org.openhab.binding.growatt.internal.handler.GrowattInverterHandler; -import org.openhab.binding.growatt.internal.servlet.GrottHttpServlet; +import org.openhab.binding.growatt.internal.servlet.GrowattHttpServlet; import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; @@ -66,7 +66,7 @@ public class GrowattHandlerFactory extends BaseThingHandlerFactory { private final TranslationProvider i18nProvider; private final LocaleProvider localeProvider; private final Set bridges = Collections.synchronizedSet(new HashSet<>()); - private final GrottHttpServlet httpServlet = new GrottHttpServlet(); + private final GrowattHttpServlet httpServlet = new GrowattHttpServlet(); private @Nullable GrowattDiscoveryService discoveryService; private @Nullable ServiceRegistration discoveryServiceRegistration; @@ -78,7 +78,7 @@ public GrowattHandlerFactory(@Reference HttpService httpService, @Reference Tran this.i18nProvider = i18nProvider; this.localeProvider = localeProvider; try { - httpService.registerServlet(GrottHttpServlet.PATH, httpServlet, null, null); + httpService.registerServlet(GrowattHttpServlet.PATH, httpServlet, null, null); } catch (ServletException | NamespaceException e) { logger.warn("GrowattHandlerFactory() failed to register servlet", e); } @@ -106,7 +106,7 @@ public GrowattHandlerFactory(@Reference HttpService httpService, @Reference Tran protected void deactivate(ComponentContext componentContext) { bridges.clear(); discoveryUnregister(); - httpService.unregister(GrottHttpServlet.PATH); + httpService.unregister(GrowattHttpServlet.PATH); super.deactivate(componentContext); } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 5ccc736cf3718..861dfa1a769cf 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.dto.GrottDevice; -import org.openhab.binding.growatt.internal.servlet.GrottHttpServlet; +import org.openhab.binding.growatt.internal.servlet.GrowattHttpServlet; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingStatus; @@ -44,9 +44,10 @@ public class GrowattBridgeHandler extends BaseBridgeHandler { private final Gson gson = new Gson(); private final GrowattDiscoveryService discoveryService; private final Map inverters = new HashMap<>(); - private final GrottHttpServlet httpServlet; + private final GrowattHttpServlet httpServlet; - public GrowattBridgeHandler(Bridge bridge, GrottHttpServlet httpServlet, GrowattDiscoveryService discoveryService) { + public GrowattBridgeHandler(Bridge bridge, GrowattHttpServlet httpServlet, + GrowattDiscoveryService discoveryService) { super(bridge); this.httpServlet = httpServlet; this.discoveryService = discoveryService; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index d4d98f970c17c..0f5a824919d43 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -43,6 +43,9 @@ @NonNullByDefault public class GrowattInverterHandler extends BaseThingHandler { + // data-logger sends packets each 5 minutes; timeout means 2 packets missed + private static final int AWAITING_DATA_TIMEOUT_MINUTES = 11; + private final Logger logger = LoggerFactory.getLogger(GrowattInverterHandler.class); private String deviceId = "unknown"; @@ -81,8 +84,10 @@ private void scheduleAwaitingDataTimeoutTask() { if (task != null) { task.cancel(true); } - awaitingDataTimeoutTask = scheduler.schedule(() -> updateStatus(ThingStatus.OFFLINE, - ThingStatusDetail.COMMUNICATION_ERROR, "@text/status.awaiting-data-timeout"), 5, TimeUnit.MINUTES); + awaitingDataTimeoutTask = scheduler.schedule(() -> { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/status.awaiting-data-timeout"); + }, AWAITING_DATA_TIMEOUT_MINUTES, TimeUnit.MINUTES); } /** diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrowattHttpServlet.java similarity index 95% rename from bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java rename to bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrowattHttpServlet.java index 558d50acbbfb8..a13b7a583c7d9 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrottHttpServlet.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrowattHttpServlet.java @@ -28,12 +28,12 @@ import org.openhab.binding.growatt.internal.handler.GrowattBridgeHandler; /** - * The {@link GrottHttpServlet} is an HttpServlet to handle data posted by the Grott application. + * The {@link GrowattHttpServlet} is an HttpServlet to handle data posted by the Grott application. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public class GrottHttpServlet extends HttpServlet { +public class GrowattHttpServlet extends HttpServlet { public static final String PATH = "/growatt"; diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index f52df09cd5baa..720943398c81b 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -51,8 +51,8 @@ thing-type.growatt.inverter.channel.export-power-t.label = Export Power #T thing-type.growatt.inverter.channel.export-power-t.description = Power exported to grid phase #T. thing-type.growatt.inverter.channel.grid-frequency.label = Grid Frequency thing-type.growatt.inverter.channel.grid-frequency.description = Frequency of the grid. -thing-type.growatt.inverter.channel.grid-voltage.label = Grid Voltage -thing-type.growatt.inverter.channel.grid-voltage.description = Voltage of the grid (phase #R). +thing-type.growatt.inverter.channel.grid-voltage-r.label = Grid Voltage (#R) +thing-type.growatt.inverter.channel.grid-voltage-r.description = Voltage of the grid (phase #R). thing-type.growatt.inverter.channel.grid-voltage-rs.label = Grid Voltage #RS thing-type.growatt.inverter.channel.grid-voltage-rs.description = Voltage of the grid phases #RS. thing-type.growatt.inverter.channel.grid-voltage-s.label = Grid Voltage #S @@ -79,6 +79,30 @@ thing-type.growatt.inverter.channel.import-power-s.label = Import Power #S thing-type.growatt.inverter.channel.import-power-s.description = Power imported phase #S. thing-type.growatt.inverter.channel.import-power-t.label = Import Power #T thing-type.growatt.inverter.channel.import-power-t.description = Power imported phase #T. +thing-type.growatt.inverter.channel.inverter-charge-energy-today.label = Inverter Energy to Charge Battery Today +thing-type.growatt.inverter.channel.inverter-charge-energy-today.description = Energy from inverter to charge battery today. +thing-type.growatt.inverter.channel.inverter-charge-energy-total.label = Total Inverter Energy to Charge Battery +thing-type.growatt.inverter.channel.inverter-charge-energy-total.description = Total energy from inverter to charge battery. +thing-type.growatt.inverter.channel.inverter-current-r.label = Inverter Output Current (#R) +thing-type.growatt.inverter.channel.inverter-current-r.description = AC current from inverter (phase #R). +thing-type.growatt.inverter.channel.inverter-current-s.label = Inverter Output Current #S +thing-type.growatt.inverter.channel.inverter-current-s.description = AC current from inverter phase #S. +thing-type.growatt.inverter.channel.inverter-current-t.label = Inverter Output Current #T +thing-type.growatt.inverter.channel.inverter-current-t.description = AC current from inverter phase #T. +thing-type.growatt.inverter.channel.inverter-energy-today.label = Inverter Output Energy Today +thing-type.growatt.inverter.channel.inverter-energy-today.description = Inverter output energy produced today. +thing-type.growatt.inverter.channel.inverter-energy-total.label = Inverter Output Energy Total +thing-type.growatt.inverter.channel.inverter-energy-total.description = Total inverter output energy produced. +thing-type.growatt.inverter.channel.inverter-power.label = Inverter Output Power +thing-type.growatt.inverter.channel.inverter-power.description = Total AC output power from the inverter. +thing-type.growatt.inverter.channel.inverter-power-r.label = Inverter Output Power (#R) +thing-type.growatt.inverter.channel.inverter-power-r.description = AC power from inverter (phase #R). +thing-type.growatt.inverter.channel.inverter-power-s.label = Inverter Output Power #S +thing-type.growatt.inverter.channel.inverter-power-s.description = AC power from inverter phase #S. +thing-type.growatt.inverter.channel.inverter-power-t.label = Inverter Output Power #T +thing-type.growatt.inverter.channel.inverter-power-t.description = AC power from inverter phase #T. +thing-type.growatt.inverter.channel.inverter-va.label = Inverter VA +thing-type.growatt.inverter.channel.inverter-va.description = AC VA produced by inverter. thing-type.growatt.inverter.channel.load-energy-today.label = Energy to Load Today thing-type.growatt.inverter.channel.load-energy-today.description = Energy supplied to load today. thing-type.growatt.inverter.channel.load-energy-total.label = Total Energy to Load @@ -105,10 +129,8 @@ thing-type.growatt.inverter.channel.pv-energy-total.label = Total Solar DC Energ thing-type.growatt.inverter.channel.pv-energy-total.description = Total solar energy supplied to grid. thing-type.growatt.inverter.channel.pv-ipm-temperature.label = Solar IPM Temperature thing-type.growatt.inverter.channel.pv-ipm-temperature.description = Temperature of the IPM. -thing-type.growatt.inverter.channel.pv-power-in.label = Solar Input Power -thing-type.growatt.inverter.channel.pv-power-in.description = Total solar DC input power. -thing-type.growatt.inverter.channel.pv-power-out.label = Solar Output Power -thing-type.growatt.inverter.channel.pv-power-out.description = Total solar AC output power. +thing-type.growatt.inverter.channel.pv-power.label = Solar Input Power +thing-type.growatt.inverter.channel.pv-power.description = Total solar DC input power. thing-type.growatt.inverter.channel.pv-temperature.label = Solar Panel Temperature thing-type.growatt.inverter.channel.pv-temperature.description = Temperature of the solar panels (string #1). thing-type.growatt.inverter.channel.pv1-current.label = String #1 Current @@ -135,28 +157,6 @@ thing-type.growatt.inverter.channel.pv2-voltage.label = String #2 Voltage thing-type.growatt.inverter.channel.pv2-voltage.description = Voltage from solar panel string #2. thing-type.growatt.inverter.channel.rac.label = RAC thing-type.growatt.inverter.channel.rac.description = RAC code. -thing-type.growatt.inverter.channel.solar-charge-energy-today.label = Solar Energy to Charge Battery Today -thing-type.growatt.inverter.channel.solar-charge-energy-today.description = Energy from solar to charge battery today. -thing-type.growatt.inverter.channel.solar-charge-energy-total.label = Total Solar Energy to Charge Battery -thing-type.growatt.inverter.channel.solar-charge-energy-total.description = Total energy from solar to charge battery. -thing-type.growatt.inverter.channel.solar-current.label = Solar Current -thing-type.growatt.inverter.channel.solar-current.description = AC current from solar (phase #R). -thing-type.growatt.inverter.channel.solar-current-s.label = Solar Current #S -thing-type.growatt.inverter.channel.solar-current-s.description = AC current from solar phase #S. -thing-type.growatt.inverter.channel.solar-current-t.label = Solar Current #T -thing-type.growatt.inverter.channel.solar-current-t.description = AC current from solar phase #T. -thing-type.growatt.inverter.channel.solar-energy-today.label = Solar Energy Today -thing-type.growatt.inverter.channel.solar-energy-today.description = Solar energy collected today. -thing-type.growatt.inverter.channel.solar-energy-total.label = Solar Energy Total -thing-type.growatt.inverter.channel.solar-energy-total.description = Total solar energy collected. -thing-type.growatt.inverter.channel.solar-power.label = Solar AC Power -thing-type.growatt.inverter.channel.solar-power.description = AC power from solar (phase #R). -thing-type.growatt.inverter.channel.solar-power-s.label = Solar AC Power #S -thing-type.growatt.inverter.channel.solar-power-s.description = AC power from solar phase #S. -thing-type.growatt.inverter.channel.solar-power-t.label = Solar AC Power #T -thing-type.growatt.inverter.channel.solar-power-t.description = AC power from solar phase #T. -thing-type.growatt.inverter.channel.solar-va.label = Solar VA -thing-type.growatt.inverter.channel.solar-va.description = AC VA from solar. thing-type.growatt.inverter.channel.sp-bus-voltage.label = SP Bus Voltage thing-type.growatt.inverter.channel.sp-bus-voltage.description = SP Bus voltage. thing-type.growatt.inverter.channel.sp-display-status.label = Solar Panel Display diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index f3bedb836b518..701a5f4899454 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -27,14 +27,10 @@ - + Total solar DC input power. - - - Total solar AC output power. - @@ -69,8 +65,8 @@ Frequency of the grid. - - + + Voltage of the grid (phase #R). @@ -94,36 +90,40 @@ Voltage of the grid phases #TR. - - - - AC current from solar (phase #R). + + + + AC current from inverter (phase #R). - - - AC current from solar phase #S. + + + AC current from inverter phase #S. - - - AC current from solar phase #T. + + + AC current from inverter phase #T. - - - AC power from solar (phase #R). + + + Total AC output power from the inverter. + + + + AC power from inverter (phase #R). - - - AC power from solar phase #S. + + + AC power from inverter phase #S. - - - AC power from solar phase #T. + + + AC power from inverter phase #T. - - - AC VA from solar. + + + AC VA produced by inverter. @@ -198,14 +198,14 @@ Power supplied to load phase #T. - - - - Solar energy collected today. + + + + Inverter output energy produced today. - - - Total solar energy collected. + + + Total inverter output energy produced. @@ -275,14 +275,14 @@ Total energy imported from grid to charge battery. - - - - Energy from solar to charge battery today. + + + + Energy from inverter to charge battery today. - - - Total energy from solar to charge battery. + + + Total energy from inverter to charge battery. diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index c63ec0ad36e57..cc4060597e431 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -26,8 +26,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.growatt.internal.GrowattBindingConstants; -import org.openhab.binding.growatt.internal.GrowattBindingConstants.UoM; +import org.openhab.binding.growatt.internal.GrowattChannels; +import org.openhab.binding.growatt.internal.GrowattChannels.UoM; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; import org.openhab.core.library.types.QuantityType; @@ -89,9 +89,9 @@ void testGrottValuesAccessibility() { } /** - * For the given JSON file, test that GrottValues implements the same fields as the - * GrowattBindingConstants.CHANNEL_ID_UOM_MAP. Test that all fields can be accessed and that they are either null or - * an Integer instance. + * For the given JSON file, test that GrottValues implements the same fields as the Map returned from + * GrowattChannels.getMap(). Test that all fields can be accessed and that they are either null or an Integer + * instance. * * @param fileName the name of the JSON file to be tested. */ @@ -102,19 +102,19 @@ private void testGrottValuesAccessibility(String fileName) { .collect(Collectors.toList()); // test that the GrottValues DTO has identical field names to the CHANNEL_ID_UOM_MAP channel ids - for (String channel : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.keySet()) { + for (String channel : GrowattChannels.getMap().keySet()) { assertTrue(fields.contains(GrottValues.getFieldName(channel))); } // test that the CHANNEL_ID_UOM_MAP has identical channel ids to the GrottValues DTO field names for (String field : fields) { - assertTrue(GrowattBindingConstants.CHANNEL_ID_UOM_MAP.containsKey(GrottValues.getChannelId(field))); + assertTrue(GrowattChannels.getMap().containsKey(GrottValues.getChannelId(field))); } // test that the CHANNEL_ID_UOM_MAP and the GrottValues DTO have the same number of fields resp. channel ids - assertEquals(fields.size(), GrowattBindingConstants.CHANNEL_ID_UOM_MAP.size()); + assertEquals(fields.size(), GrowattChannels.getMap().size()); - for (Entry entry : GrowattBindingConstants.CHANNEL_ID_UOM_MAP.entrySet()) { + for (Entry entry : GrowattChannels.getMap().entrySet()) { String channelId = entry.getKey(); Field field; // test that the field can be accessed @@ -144,8 +144,26 @@ private void testGrottValuesAccessibility(String fileName) { @Test void testGrottValuesContents() { GrottValues grottValues = loadGrottValues("simple"); - Map> channelStates = null; + assertEquals(1, grottValues.system_status); + assertEquals(1622, grottValues.pv_power); + assertEquals(4997, grottValues.grid_frequency); + assertEquals(2353, grottValues.grid_voltage_r); + assertEquals(7, grottValues.inverter_current_r); + assertEquals(1460, grottValues.inverter_power); + assertEquals(1460, grottValues.inverter_power_r); + assertEquals(273, grottValues.pv_temperature); + assertEquals(87, grottValues.inverter_energy_today); + assertEquals(43265, grottValues.inverter_energy_total); + assertEquals(90, grottValues.pv1_energy_today); + assertEquals(45453, grottValues.pv1_energy_total); + assertEquals(45453, grottValues.pv_energy_total); + assertEquals(0, grottValues.pv2_voltage); + assertEquals(0, grottValues.pv2_current); + assertEquals(0, grottValues.pv2_power); + assertEquals(65503878, grottValues.total_work_time); + + Map> channelStates = null; try { channelStates = grottValues.getChannelStates(); } catch (NoSuchFieldException e) { @@ -166,20 +184,21 @@ void testGrottValuesContents() { }); assertEquals(QuantityType.ONE, channelStates.get("system-status")); - assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("grid-voltage")); + assertEquals(QuantityType.valueOf(162.2, Units.WATT), channelStates.get("pv-power")); assertEquals(QuantityType.valueOf(49.97, Units.HERTZ), channelStates.get("grid-frequency")); - - assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("solar-current")); - assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("solar-power")); - + assertEquals(QuantityType.valueOf(235.3, Units.VOLT), channelStates.get("grid-voltage-r")); + assertEquals(QuantityType.valueOf(0.7, Units.AMPERE), channelStates.get("inverter-current-r")); + assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("inverter-power")); + assertEquals(QuantityType.valueOf(146, Units.WATT), channelStates.get("inverter-power-r")); assertEquals(QuantityType.valueOf(27.3, SIUnits.CELSIUS), channelStates.get("pv-temperature")); - + assertEquals(QuantityType.valueOf(8.7, Units.KILOWATT_HOUR), channelStates.get("inverter-energy-today")); + assertEquals(QuantityType.valueOf(4326.5, Units.KILOWATT_HOUR), channelStates.get("inverter-energy-total")); + assertEquals(QuantityType.valueOf(9, Units.KILOWATT_HOUR), channelStates.get("pv1-energy-today")); assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("pv1-energy-total")); - + assertEquals(QuantityType.valueOf(4545.3, Units.KILOWATT_HOUR), channelStates.get("pv-energy-total")); assertEquals(QuantityType.valueOf(0, Units.VOLT), channelStates.get("pv2-voltage")); assertEquals(QuantityType.valueOf(0, Units.AMPERE), channelStates.get("pv2-current")); assertEquals(QuantityType.valueOf(0, Units.WATT), channelStates.get("pv2-power")); - State state = channelStates.get("total-work-time"); assertTrue(state instanceof QuantityType); if (state instanceof QuantityType) { From ed13fc08833dbddb373b692723cf10f875b95623 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 11 Sep 2023 17:56:58 +0100 Subject: [PATCH 026/146] [growatt] fix read me Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index b355941053199..46e304753679a 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -200,10 +200,10 @@ nomqtt = True // disable mqtt [PVOutput] pvoutput = False // disable pvoutput -[extension] +[extension] // enable the 'grottext' extension extension = True extname = grottext -extvar = {"url": "http://xxx.xxx.xxx.xxx:8080/growatt"} // ip address of openhab +extvar = {"url": "http://127.0.0.1:8080/growatt"} // or ip address of openhab (if remote) ``` ### Start Grott as a Service From 0a8f53d809e4217c317c10b6eb7c92c249f3f33a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 12 Sep 2023 17:18:50 +0100 Subject: [PATCH 027/146] [growatt] improve docu Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 46e304753679a..ecc33c1f4a1f4 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -150,6 +150,16 @@ Number:Power Solar_String1_Power "Solar String #1 PV Power" {channel="growatt:in Number:Energy Solar_Output_Energy "Solar Output Energy Total" {channel="growatt:inverter:home:sph:pv-energy-total"} ``` +Example using a transform profile to invert an item value: + +```java +// charge item with positive value +Number:Power Charge_Power "Charge Power [%.0f W]" {channel="growatt:inverter:home:sph:charge-power"} + +// discarge item with negative value +Number:Power Discharge_Power "Discharge Power [%.0f W]" {channel="growatt:inverter:home:sph:discharge-power" [ profile="transform:JS", toItemScript="| Quantity(input).multiply(-1).toString();" ] } +``` + ## Grott Application Installation and Setup You can install the Grott application either on the same computer as OpenHAB or on another. From f34209aaf02bbc2547a7a00bbcd7db19ea970847 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 13 Sep 2023 18:27:32 +0100 Subject: [PATCH 028/146] [growatt] tweak channel labels, descriptions Signed-off-by: Andrew Fiddian-Green --- .../resources/OH-INF/i18n/growatt.properties | 28 +++++++++---------- .../resources/OH-INF/thing/thing-types.xml | 28 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 720943398c81b..eb8cbc434cc31 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -27,7 +27,7 @@ thing-type.growatt.inverter.channel.constant-power-ok.label = Constant Power OK thing-type.growatt.inverter.channel.constant-power-ok.description = Constant power OK code. thing-type.growatt.inverter.channel.discharge-energy-today.label = Energy from Battery Today thing-type.growatt.inverter.channel.discharge-energy-today.description = Energy consumed from battery today. -thing-type.growatt.inverter.channel.discharge-energy-total.label = Total Energy from Battery +thing-type.growatt.inverter.channel.discharge-energy-total.label = Energy from Battery Total thing-type.growatt.inverter.channel.discharge-energy-total.description = Total energy consumed from battery. thing-type.growatt.inverter.channel.discharge-power.label = Discharge Power thing-type.growatt.inverter.channel.discharge-power.description = Discharge power from battery. @@ -39,7 +39,7 @@ thing-type.growatt.inverter.channel.erac-total.label = ERAC Total thing-type.growatt.inverter.channel.erac-total.description = Total ERAC count. thing-type.growatt.inverter.channel.export-energy-today.label = Export Energy Today thing-type.growatt.inverter.channel.export-energy-today.description = Energy exported to grid today. -thing-type.growatt.inverter.channel.export-energy-total.label = Total Export Energy +thing-type.growatt.inverter.channel.export-energy-total.label = Export Energy Total thing-type.growatt.inverter.channel.export-energy-total.description = Total energy exported to grid. thing-type.growatt.inverter.channel.export-power.label = Export Power thing-type.growatt.inverter.channel.export-power.description = Power exported to grid. @@ -65,11 +65,11 @@ thing-type.growatt.inverter.channel.grid-voltage-tr.label = Grid Voltage #TR thing-type.growatt.inverter.channel.grid-voltage-tr.description = Voltage of the grid phases #TR. thing-type.growatt.inverter.channel.import-charge-energy-today.label = Import Energy to Charge Battery Today thing-type.growatt.inverter.channel.import-charge-energy-today.description = Energy imported from grid to charge battery today. -thing-type.growatt.inverter.channel.import-charge-energy-total.label = Total Import Energy to Charge Battery +thing-type.growatt.inverter.channel.import-charge-energy-total.label = Import Energy to Charge Battery Total thing-type.growatt.inverter.channel.import-charge-energy-total.description = Total energy imported from grid to charge battery. thing-type.growatt.inverter.channel.import-energy-today.label = Import Energy Today thing-type.growatt.inverter.channel.import-energy-today.description = Energy imported from grid today. -thing-type.growatt.inverter.channel.import-energy-total.label = Total Import Energy +thing-type.growatt.inverter.channel.import-energy-total.label = Import Energy Total thing-type.growatt.inverter.channel.import-energy-total.description = Total energy imported from grid. thing-type.growatt.inverter.channel.import-power.label = Import Power thing-type.growatt.inverter.channel.import-power.description = Power imported. @@ -81,7 +81,7 @@ thing-type.growatt.inverter.channel.import-power-t.label = Import Power #T thing-type.growatt.inverter.channel.import-power-t.description = Power imported phase #T. thing-type.growatt.inverter.channel.inverter-charge-energy-today.label = Inverter Energy to Charge Battery Today thing-type.growatt.inverter.channel.inverter-charge-energy-today.description = Energy from inverter to charge battery today. -thing-type.growatt.inverter.channel.inverter-charge-energy-total.label = Total Inverter Energy to Charge Battery +thing-type.growatt.inverter.channel.inverter-charge-energy-total.label = Inverter Energy to Charge Battery Total thing-type.growatt.inverter.channel.inverter-charge-energy-total.description = Total energy from inverter to charge battery. thing-type.growatt.inverter.channel.inverter-current-r.label = Inverter Output Current (#R) thing-type.growatt.inverter.channel.inverter-current-r.description = AC current from inverter (phase #R). @@ -94,7 +94,7 @@ thing-type.growatt.inverter.channel.inverter-energy-today.description = Inverter thing-type.growatt.inverter.channel.inverter-energy-total.label = Inverter Output Energy Total thing-type.growatt.inverter.channel.inverter-energy-total.description = Total inverter output energy produced. thing-type.growatt.inverter.channel.inverter-power.label = Inverter Output Power -thing-type.growatt.inverter.channel.inverter-power.description = Total AC output power from the inverter. +thing-type.growatt.inverter.channel.inverter-power.description = AC power the inverter (total). thing-type.growatt.inverter.channel.inverter-power-r.label = Inverter Output Power (#R) thing-type.growatt.inverter.channel.inverter-power-r.description = AC power from inverter (phase #R). thing-type.growatt.inverter.channel.inverter-power-s.label = Inverter Output Power #S @@ -105,7 +105,7 @@ thing-type.growatt.inverter.channel.inverter-va.label = Inverter VA thing-type.growatt.inverter.channel.inverter-va.description = AC VA produced by inverter. thing-type.growatt.inverter.channel.load-energy-today.label = Energy to Load Today thing-type.growatt.inverter.channel.load-energy-today.description = Energy supplied to load today. -thing-type.growatt.inverter.channel.load-energy-total.label = Total Energy to Load +thing-type.growatt.inverter.channel.load-energy-total.label = Energy to Load Total thing-type.growatt.inverter.channel.load-energy-total.description = Total energy supplied to load. thing-type.growatt.inverter.channel.load-percent.label = Load Percent thing-type.growatt.inverter.channel.load-percent.description = Percent of full load. @@ -125,31 +125,31 @@ thing-type.growatt.inverter.channel.pv-boost-temperature.label = Boost Temperatu thing-type.growatt.inverter.channel.pv-boost-temperature.description = Boost temperature. thing-type.growatt.inverter.channel.pv-energy-today.label = Solar DC Energy Today thing-type.growatt.inverter.channel.pv-energy-today.description = Solar DC energy collected. -thing-type.growatt.inverter.channel.pv-energy-total.label = Total Solar DC Energy +thing-type.growatt.inverter.channel.pv-energy-total.label = Solar DC Energy Total thing-type.growatt.inverter.channel.pv-energy-total.description = Total solar energy supplied to grid. thing-type.growatt.inverter.channel.pv-ipm-temperature.label = Solar IPM Temperature thing-type.growatt.inverter.channel.pv-ipm-temperature.description = Temperature of the IPM. thing-type.growatt.inverter.channel.pv-power.label = Solar Input Power -thing-type.growatt.inverter.channel.pv-power.description = Total solar DC input power. +thing-type.growatt.inverter.channel.pv-power.description = Power from solar panels. thing-type.growatt.inverter.channel.pv-temperature.label = Solar Panel Temperature thing-type.growatt.inverter.channel.pv-temperature.description = Temperature of the solar panels (string #1). thing-type.growatt.inverter.channel.pv1-current.label = String #1 Current thing-type.growatt.inverter.channel.pv1-current.description = Current from solar panel string #1. thing-type.growatt.inverter.channel.pv1-energy-today.label = Solar DC Energy #1 Today thing-type.growatt.inverter.channel.pv1-energy-today.description = Solar DC energy collected by string #1 to grid today. -thing-type.growatt.inverter.channel.pv1-energy-total.label = Total Solar DC Energy #1 +thing-type.growatt.inverter.channel.pv1-energy-total.label = Solar DC Energy #1 Total thing-type.growatt.inverter.channel.pv1-energy-total.description = Total solar DC collected by string #1. -thing-type.growatt.inverter.channel.pv1-power.label = String #1 Power +thing-type.growatt.inverter.channel.pv1-power.label = Solar String #1 Power thing-type.growatt.inverter.channel.pv1-power.description = Power from solar panel string #1. thing-type.growatt.inverter.channel.pv1-voltage.label = String #1 Voltage thing-type.growatt.inverter.channel.pv1-voltage.description = Voltage from solar panel string #1. thing-type.growatt.inverter.channel.pv2-current.label = String #2 Current thing-type.growatt.inverter.channel.pv2-current.description = Current from solar panel string #2. -thing-type.growatt.inverter.channel.pv2-energy-today.label = Solar Energy #2 to Grid Today +thing-type.growatt.inverter.channel.pv2-energy-today.label = Solar DC Energy #2 Today thing-type.growatt.inverter.channel.pv2-energy-today.description = Solar DC energy collected by string #2 to grid today. -thing-type.growatt.inverter.channel.pv2-energy-total.label = Total Solar DC Energy #2 +thing-type.growatt.inverter.channel.pv2-energy-total.label = Solar DC Energy #2 Total thing-type.growatt.inverter.channel.pv2-energy-total.description = Total solar DC collected by string #2. -thing-type.growatt.inverter.channel.pv2-power.label = String #2 Power +thing-type.growatt.inverter.channel.pv2-power.label = Solar String #2 Power thing-type.growatt.inverter.channel.pv2-power.description = Power from solar panel string #2. thing-type.growatt.inverter.channel.pv2-temperature.label = Solar Panel Temperature #2 thing-type.growatt.inverter.channel.pv2-temperature.description = Temperature of the solar panels (string #2). diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 701a5f4899454..38845a46b63d2 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -29,7 +29,7 @@ - Total solar DC input power. + Power from solar panels. @@ -52,11 +52,11 @@ - + Power from solar panel string #1. - + Power from solar panel string #2. @@ -106,7 +106,7 @@ - Total AC output power from the inverter. + AC power the inverter (total). @@ -218,20 +218,20 @@ Solar DC energy collected by string #1 to grid today. - + Solar DC energy collected by string #2 to grid today. - + Total solar energy supplied to grid. - + Total solar DC collected by string #1. - + Total solar DC collected by string #2. @@ -241,7 +241,7 @@ Energy exported to grid today. - + Total energy exported to grid. @@ -251,7 +251,7 @@ Energy imported from grid today. - + Total energy imported from grid. @@ -261,7 +261,7 @@ Energy supplied to load today. - + Total energy supplied to load. @@ -271,7 +271,7 @@ Energy imported from grid to charge battery today. - + Total energy imported from grid to charge battery. @@ -281,7 +281,7 @@ Energy from inverter to charge battery today. - + Total energy from inverter to charge battery. @@ -291,7 +291,7 @@ Energy consumed from battery today. - + Total energy consumed from battery. From cddeeedaceab9995e1006239d0d27eeef981936b Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 29 Oct 2023 11:07:10 +0100 Subject: [PATCH 029/146] [miio] readme hyperlink fix (#15814) * Minor formatting * Fix hyperlink for robo rock vacuums channels Signed-off-by: Marcel Verpaalen --- .../org.openhab.binding.miio/README.base.md | 4 + bundles/org.openhab.binding.miio/README.md | 108 +++++++++--------- .../binding/miio/internal/ReadmeHelper.java | 4 +- 3 files changed, 63 insertions(+), 53 deletions(-) diff --git a/bundles/org.openhab.binding.miio/README.base.md b/bundles/org.openhab.binding.miio/README.base.md index 155d0eb4e6e31..943bf86a96634 100644 --- a/bundles/org.openhab.binding.miio/README.base.md +++ b/bundles/org.openhab.binding.miio/README.base.md @@ -269,6 +269,8 @@ To (re-)read the file either restart openHAB, restart the binding or alternative Note, cropping is disabled (hence showing like the maps in OH3.1 and earlier) for any `cropBorder` value < 0. Note, not all the values need to be in the json file, e.g. a subset of the parameters also works, the parameters not in the `mapConfig.json` will take the default values. +### Basic, gateway and lumi Things channels + !!!channelList ## Example item file Rockrobo vacuum @@ -317,6 +319,8 @@ Switch lastCompleted "Last Cleaning Completed" (gVacLast) {channel="miio:vac Image map "Cleaning Map" (gVacLast) {channel="miio:vacuum:034F0E45:cleaning#map"} ``` +### Basic, gateway and lumi Things item files examples + !!!itemFileExamples ### Country Servers diff --git a/bundles/org.openhab.binding.miio/README.md b/bundles/org.openhab.binding.miio/README.md index 13a7bc5be7d30..0af243ddc8070 100644 --- a/bundles/org.openhab.binding.miio/README.md +++ b/bundles/org.openhab.binding.miio/README.md @@ -364,59 +364,59 @@ Currently the miio binding supports more than 360 different models. | CHINGMI Smart Power Strip v1 | miio:basic | [qmi.powerstrip.v1](#qmi-powerstrip-v1) | Yes | | | Rockrobo Xiaowa Sweeper v2 | miio:unsupported | roborock.sweeper.e2v2 | No | | | Rockrobo Xiaowa Sweeper v3 | miio:unsupported | roborock.sweeper.e2v3 | No | | -| Roborock S6 Pure | miio:vacuum | [roborock.vacuum.a08](#roborock-vacuum-a08) | Yes | | -| Roborock T7 Pro | miio:vacuum | [roborock.vacuum.a09](#roborock-vacuum-a09) | Yes | | -| Roborock S6 MaxV | miio:vacuum | [roborock.vacuum.a10](#roborock-vacuum-a10) | Yes | | -| Roborock T7 | miio:vacuum | [roborock.vacuum.a11](#roborock-vacuum-a11) | Yes | | -| Roborock T7S | miio:vacuum | [roborock.vacuum.a14](#roborock-vacuum-a14) | Yes | | -| Roborock S7 | miio:vacuum | [roborock.vacuum.a15](#roborock-vacuum-a15) | Yes | | -| Roborock S4 Max | miio:vacuum | [roborock.vacuum.a19](#roborock-vacuum-a19) | Yes | | -| Roborock T7S Plus | miio:vacuum | [roborock.vacuum.a23](#roborock-vacuum-a23) | Yes | | -| Roborock G10S Pro | miio:vacuum | [roborock.vacuum.a26](#roborock-vacuum-a26) | Yes | | -| Roborock S7 MaxV | miio:vacuum | [roborock.vacuum.a27](#roborock-vacuum-a27) | Yes | | -| Roborock G10 | miio:vacuum | [roborock.vacuum.a29](#roborock-vacuum-a29) | Yes | | -| Roborock G10 | miio:vacuum | [roborock.vacuum.a30](#roborock-vacuum-a30) | Yes | | -| Roborock Q5 | miio:vacuum | [roborock.vacuum.a34](#roborock-vacuum-a34) | Yes | | -| Roborock T8 | miio:vacuum | [roborock.vacuum.a37](#roborock-vacuum-a37) | Yes | | -| Roborock Q7 Max | miio:vacuum | [roborock.vacuum.a38](#roborock-vacuum-a38) | Yes | | -| Roborock Q7 | miio:vacuum | [roborock.vacuum.a40](#roborock-vacuum-a40) | Yes | | -| Roborock G10S | miio:vacuum | [roborock.vacuum.a46](#roborock-vacuum-a46) | Yes | | -| Roborock S8 | miio:vacuum | [roborock.vacuum.a51](#roborock-vacuum-a51) | Yes | | -| Roborock T8 Plus | miio:vacuum | [roborock.vacuum.a52](#roborock-vacuum-a52) | Yes | | -| Roborock S7 Pro Ultra | miio:vacuum | [roborock.vacuum.a62](#roborock-vacuum-a62) | Yes | | -| Roborock G10S Pure | miio:vacuum | [roborock.vacuum.a64](#roborock-vacuum-a64) | Yes | | -| Roborock S7 Max Ultra | miio:vacuum | [roborock.vacuum.a65](#roborock-vacuum-a65) | Yes | | -| Roborock G10 Plus | miio:vacuum | [roborock.vacuum.a66](#roborock-vacuum-a66) | Yes | | -| Roborock G20 | miio:vacuum | [roborock.vacuum.a69](#roborock-vacuum-a69) | Yes | | -| Roborock S8 Pro Ultra | miio:vacuum | [roborock.vacuum.a70](#roborock-vacuum-a70) | Yes | | -| Roborock Q5 Pro | miio:vacuum | [roborock.vacuum.a72](#roborock-vacuum-a72) | Yes | | -| Roborock Q8 Max | miio:vacuum | [roborock.vacuum.a73](#roborock-vacuum-a73) | Yes | | -| Roborock P10 | miio:vacuum | [roborock.vacuum.a74](#roborock-vacuum-a74) | Yes | | -| Roborock Q Revo | miio:vacuum | [roborock.vacuum.a75](#roborock-vacuum-a75) | Yes | | -| Roborock G10S Auto | miio:vacuum | [roborock.vacuum.a76](#roborock-vacuum-a76) | Yes | | -| Xiaowa C1 | miio:vacuum | [roborock.vacuum.c1](#roborock-vacuum-c1) | Yes | | +| Roborock S6 Pure | miio:vacuum | [roborock.vacuum.a08](#robo-rock-vacuum-channels) | Yes | | +| Roborock T7 Pro | miio:vacuum | [roborock.vacuum.a09](#robo-rock-vacuum-channels) | Yes | | +| Roborock S6 MaxV | miio:vacuum | [roborock.vacuum.a10](#robo-rock-vacuum-channels) | Yes | | +| Roborock T7 | miio:vacuum | [roborock.vacuum.a11](#robo-rock-vacuum-channels) | Yes | | +| Roborock T7S | miio:vacuum | [roborock.vacuum.a14](#robo-rock-vacuum-channels) | Yes | | +| Roborock S7 | miio:vacuum | [roborock.vacuum.a15](#robo-rock-vacuum-channels) | Yes | | +| Roborock S4 Max | miio:vacuum | [roborock.vacuum.a19](#robo-rock-vacuum-channels) | Yes | | +| Roborock T7S Plus | miio:vacuum | [roborock.vacuum.a23](#robo-rock-vacuum-channels) | Yes | | +| Roborock G10S Pro | miio:vacuum | [roborock.vacuum.a26](#robo-rock-vacuum-channels) | Yes | | +| Roborock S7 MaxV | miio:vacuum | [roborock.vacuum.a27](#robo-rock-vacuum-channels) | Yes | | +| Roborock G10 | miio:vacuum | [roborock.vacuum.a29](#robo-rock-vacuum-channels) | Yes | | +| Roborock G10 | miio:vacuum | [roborock.vacuum.a30](#robo-rock-vacuum-channels) | Yes | | +| Roborock Q5 | miio:vacuum | [roborock.vacuum.a34](#robo-rock-vacuum-channels) | Yes | | +| Roborock T8 | miio:vacuum | [roborock.vacuum.a37](#robo-rock-vacuum-channels) | Yes | | +| Roborock Q7 Max | miio:vacuum | [roborock.vacuum.a38](#robo-rock-vacuum-channels) | Yes | | +| Roborock Q7 | miio:vacuum | [roborock.vacuum.a40](#robo-rock-vacuum-channels) | Yes | | +| Roborock G10S | miio:vacuum | [roborock.vacuum.a46](#robo-rock-vacuum-channels) | Yes | | +| Roborock S8 | miio:vacuum | [roborock.vacuum.a51](#robo-rock-vacuum-channels) | Yes | | +| Roborock T8 Plus | miio:vacuum | [roborock.vacuum.a52](#robo-rock-vacuum-channels) | Yes | | +| Roborock S7 Pro Ultra | miio:vacuum | [roborock.vacuum.a62](#robo-rock-vacuum-channels) | Yes | | +| Roborock G10S Pure | miio:vacuum | [roborock.vacuum.a64](#robo-rock-vacuum-channels) | Yes | | +| Roborock S7 Max Ultra | miio:vacuum | [roborock.vacuum.a65](#robo-rock-vacuum-channels) | Yes | | +| Roborock G10 Plus | miio:vacuum | [roborock.vacuum.a66](#robo-rock-vacuum-channels) | Yes | | +| Roborock G20 | miio:vacuum | [roborock.vacuum.a69](#robo-rock-vacuum-channels) | Yes | | +| Roborock S8 Pro Ultra | miio:vacuum | [roborock.vacuum.a70](#robo-rock-vacuum-channels) | Yes | | +| Roborock Q5 Pro | miio:vacuum | [roborock.vacuum.a72](#robo-rock-vacuum-channels) | Yes | | +| Roborock Q8 Max | miio:vacuum | [roborock.vacuum.a73](#robo-rock-vacuum-channels) | Yes | | +| Roborock P10 | miio:vacuum | [roborock.vacuum.a74](#robo-rock-vacuum-channels) | Yes | | +| Roborock Q Revo | miio:vacuum | [roborock.vacuum.a75](#robo-rock-vacuum-channels) | Yes | | +| Roborock G10S Auto | miio:vacuum | [roborock.vacuum.a76](#robo-rock-vacuum-channels) | Yes | | +| Xiaowa C1 | miio:vacuum | [roborock.vacuum.c1](#robo-rock-vacuum-channels) | Yes | | | Roborock Xiaowa E Series Vacuum v2 | miio:unsupported | roborock.vacuum.e2 | No | | -| Mi Robot Vacuum 1S | miio:vacuum | [roborock.vacuum.m1s](#roborock-vacuum-m1s) | Yes | | -| Roborock P5 | miio:vacuum | [roborock.vacuum.p5](#roborock-vacuum-p5) | Yes | | -| Roborock S4 | miio:vacuum | [roborock.vacuum.s4](#roborock-vacuum-s4) | Yes | | -| Roborock Vacuum S4v2 | miio:vacuum | [roborock.vacuum.s4v2](#roborock-vacuum-s4v2) | Yes | | -| Roborock S5 | miio:vacuum | [roborock.vacuum.s5](#roborock-vacuum-s5) | Yes | | -| Roborock S5 Max | miio:vacuum | [roborock.vacuum.s5e](#roborock-vacuum-s5e) | Yes | | -| Roborock S6 | miio:vacuum | [roborock.vacuum.s6](#roborock-vacuum-s6) | Yes | | -| Roborock T4 | miio:vacuum | [roborock.vacuum.t4](#roborock-vacuum-t4) | Yes | | -| Roborock Vacuum T4 v2 | miio:vacuum | [roborock.vacuum.t4v2](#roborock-vacuum-t4v2) | Yes | | -| Roborock Vacuum T4 v3 | miio:vacuum | [roborock.vacuum.t4v3](#roborock-vacuum-t4v3) | Yes | | -| Roborock T6 | miio:vacuum | [roborock.vacuum.t6](#roborock-vacuum-t6) | Yes | | -| Roborock Vacuum T6 v2 | miio:vacuum | [roborock.vacuum.t6v2](#roborock-vacuum-t6v2) | Yes | | -| Roborock Vacuum T6 v3 | miio:vacuum | [roborock.vacuum.t6v3](#roborock-vacuum-t6v3) | Yes | | -| Roborock Vacuum T7 | miio:vacuum | [roborock.vacuum.t7](#roborock-vacuum-t7) | Yes | | -| Roborock Vacuum T7p | miio:vacuum | [roborock.vacuum.t7p](#roborock-vacuum-t7p) | Yes | | -| Roborock Vacuum T7 v2 | miio:vacuum | [roborock.vacuum.t7pv2](#roborock-vacuum-t7pv2) | Yes | | -| Roborock Vacuum T7 v3 | miio:vacuum | [roborock.vacuum.t7pv3](#roborock-vacuum-t7pv3) | Yes | | -| Roborock Vacuum T7 v2 | miio:vacuum | [roborock.vacuum.t7v2](#roborock-vacuum-t7v2) | Yes | | -| Roborock Vacuum T7 v3 | miio:vacuum | [roborock.vacuum.t7v3](#roborock-vacuum-t7v3) | Yes | | -| Roborock Vacuum S6 | miio:vacuum | [rockrobo.vacuum.s6](#rockrobo-vacuum-s6) | Yes | | -| Mi Robot Vacuum | miio:vacuum | [rockrobo.vacuum.v1](#rockrobo-vacuum-v1) | Yes | | +| Mi Robot Vacuum 1S | miio:vacuum | [roborock.vacuum.m1s](#robo-rock-vacuum-channels) | Yes | | +| Roborock P5 | miio:vacuum | [roborock.vacuum.p5](#robo-rock-vacuum-channels) | Yes | | +| Roborock S4 | miio:vacuum | [roborock.vacuum.s4](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum S4v2 | miio:vacuum | [roborock.vacuum.s4v2](#robo-rock-vacuum-channels) | Yes | | +| Roborock S5 | miio:vacuum | [roborock.vacuum.s5](#robo-rock-vacuum-channels) | Yes | | +| Roborock S5 Max | miio:vacuum | [roborock.vacuum.s5e](#robo-rock-vacuum-channels) | Yes | | +| Roborock S6 | miio:vacuum | [roborock.vacuum.s6](#robo-rock-vacuum-channels) | Yes | | +| Roborock T4 | miio:vacuum | [roborock.vacuum.t4](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T4 v2 | miio:vacuum | [roborock.vacuum.t4v2](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T4 v3 | miio:vacuum | [roborock.vacuum.t4v3](#robo-rock-vacuum-channels) | Yes | | +| Roborock T6 | miio:vacuum | [roborock.vacuum.t6](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T6 v2 | miio:vacuum | [roborock.vacuum.t6v2](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T6 v3 | miio:vacuum | [roborock.vacuum.t6v3](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T7 | miio:vacuum | [roborock.vacuum.t7](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T7p | miio:vacuum | [roborock.vacuum.t7p](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T7 v2 | miio:vacuum | [roborock.vacuum.t7pv2](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T7 v3 | miio:vacuum | [roborock.vacuum.t7pv3](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T7 v2 | miio:vacuum | [roborock.vacuum.t7v2](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum T7 v3 | miio:vacuum | [roborock.vacuum.t7v3](#robo-rock-vacuum-channels) | Yes | | +| Roborock Vacuum S6 | miio:vacuum | [rockrobo.vacuum.s6](#robo-rock-vacuum-channels) | Yes | | +| Mi Robot Vacuum | miio:vacuum | [rockrobo.vacuum.v1](#robo-rock-vacuum-channels) | Yes | | | ROIDMI EVE vacuum | miio:basic | [roidmi.vacuum.v60](#roidmi-vacuum-v60) | Yes | | | ROIDMI EVA | miio:basic | [roidmi.vacuum.v66](#roidmi-vacuum-v66) | Experimental | Experimental support. Please report back if all channels are functional. Preferably share the debug log of property refresh and command responses | | PTX OneKey Switch (WIFI) | miio:basic | [090615.switch.xswitch01](#090615-switch-xswitch01) | Yes | | @@ -663,6 +663,8 @@ To (re-)read the file either restart openHAB, restart the binding or alternative Note, cropping is disabled (hence showing like the maps in OH3.1 and earlier) for any `cropBorder` value < 0. Note, not all the values need to be in the json file, e.g. a subset of the parameters also works, the parameters not in the `mapConfig.json` will take the default values. +### Basic, gateway and lumi Things channels + ### Mi Air Frying Pan (careli.fryer.maf01) Channels | Channel | Type | Description | Comment | @@ -6260,6 +6262,8 @@ Switch lastCompleted "Last Cleaning Completed" (gVacLast) {channel="miio:vac Image map "Cleaning Map" (gVacLast) {channel="miio:vacuum:034F0E45:cleaning#map"} ``` +### Basic, gateway and lumi Things item files examples + ### Mi Air Frying Pan (careli.fryer.maf01) item file lines note: Autogenerated example. Replace the id (fryer) in the channel with your own. Replace `basic` with `generic` in the thing UID depending on how your thing was discovered. diff --git a/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java b/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java index 7687d86ea10a5..1cce2bd251c6f 100644 --- a/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java +++ b/bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ReadmeHelper.java @@ -141,7 +141,9 @@ private StringWriter deviceList() { Arrays.asList(MiIoDevices.values()).forEach(device -> { if (!"unknown".equals(device.getModel())) { - String link = device.getModel().replace(".", "-"); + String link = device.getThingType().equals(MiIoBindingConstants.THING_TYPE_VACUUM) + ? "robo-rock-vacuum-channels" + : device.getModel().replace(".", "-"); boolean isSupported = device.getThingType().equals(MiIoBindingConstants.THING_TYPE_UNSUPPORTED); Boolean experimental = false; String remark = ""; From 0b5e39585dfae98ca0380af17b009f01d97acf2c Mon Sep 17 00:00:00 2001 From: mlobstein Date: Sun, 29 Oct 2023 05:13:55 -0500 Subject: [PATCH 030/146] Register connection in Kaleidescape System log (#15820) Signed-off-by: Michael Lobstein --- .../kaleidescape/internal/KaleidescapeBindingConstants.java | 1 + .../kaleidescape/internal/handler/KaleidescapeHandler.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java index e7060cf99a421..09fde994a1ac7 100644 --- a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java +++ b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java @@ -183,6 +183,7 @@ public class KaleidescapeBindingConstants { public static final String MUSIC_RANDOM_ON = "MUSIC_RANDOM_ON"; public static final String MUSIC_RANDOM_OFF = "MUSIC_RANDOM_OFF"; + public static final String SEND_TO_SYSLOG = "SEND_TO_SYSLOG:INFORMATION:"; public static final String SEND_EVENT_VOLUME_CAPABILITIES_15 = "SEND_EVENT:VOLUME_CAPABILITIES=15"; public static final String SEND_EVENT_VOLUME_LEVEL_EQ = "SEND_EVENT:VOLUME_LEVEL="; public static final String SEND_EVENT_MUTE = "SEND_EVENT:MUTE_"; diff --git a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/handler/KaleidescapeHandler.java b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/handler/KaleidescapeHandler.java index 98ba12d86b8ae..ffab4046bd8ab 100644 --- a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/handler/KaleidescapeHandler.java +++ b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/handler/KaleidescapeHandler.java @@ -339,6 +339,11 @@ private void scheduleReconnectJob() { if (openConnection()) { try { cache.clear(); + + // register the connection in the Kaleidescape System log + connector.sendCommand(SEND_TO_SYSLOG + "openHAB Kaleidescape Binding version " + + org.openhab.core.OpenHAB.getVersion()); + Set initialCommands = new HashSet<>(Arrays.asList(GET_DEVICE_TYPE_NAME, GET_FRIENDLY_NAME, GET_DEVICE_INFO, GET_SYSTEM_VERSION, GET_DEVICE_POWER_STATE, GET_CINEMASCAPE_MASK, GET_CINEMASCAPE_MODE, GET_SCALE_MODE, GET_SCREEN_MASK, From a7ea27f88d0f3d86b84fe08dd4b0209b73452149 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Sun, 29 Oct 2023 21:57:30 +0100 Subject: [PATCH 031/146] [sonyaudio] small cleanup (#15823) * More SAT fixes Signed-off-by: Leo Siepel --- .../SonyAudioDiscoveryParticipant.java | 12 +++--- .../internal/handler/SonyAudioHandler.java | 30 +++++++------- .../protocol/SonyAudioConnection.java | 39 ++++++++++--------- .../internal/protocol/SonyAudioMethod.java | 8 ++-- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/discovery/SonyAudioDiscoveryParticipant.java b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/discovery/SonyAudioDiscoveryParticipant.java index 849973c1888fd..fa6a15706c944 100644 --- a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/discovery/SonyAudioDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/discovery/SonyAudioDiscoveryParticipant.java @@ -128,19 +128,19 @@ private Map getDescription(String host, int port, String path) t while ((s = bufferedReader.readLine()) != null) { builder.append(s); } - Pattern ScalarWebAPImatch = Pattern.compile("(.*)"); + Pattern scalarWebAPImatch = Pattern.compile("(.*)"); Pattern baseURLmatch = Pattern.compile("http://(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d+)([^<]*)"); - Matcher tagmatch = ScalarWebAPImatch.matcher(builder.toString()); + Matcher tagmatch = scalarWebAPImatch.matcher(builder.toString()); if (tagmatch.find()) { Matcher matcher = baseURLmatch.matcher(tagmatch.group()); matcher.find(); // String scalar_host = matcher.group(0); - int scalar_port = Integer.parseInt(matcher.group(2)); - String scalar_path = matcher.group(3); + int scalarPort = Integer.parseInt(matcher.group(2)); + String scalarPath = matcher.group(3); - properties.put(SonyAudioBindingConstants.SCALAR_PORT_PARAMETER, scalar_port); - properties.put(SonyAudioBindingConstants.SCALAR_PATH_PARAMETER, scalar_path); + properties.put(SonyAudioBindingConstants.SCALAR_PORT_PARAMETER, scalarPort); + properties.put(SonyAudioBindingConstants.SCALAR_PATH_PARAMETER, scalarPath); } return properties; } diff --git a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/handler/SonyAudioHandler.java b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/handler/SonyAudioHandler.java index a10448559e6ec..be6a5ca099d70 100644 --- a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/handler/SonyAudioHandler.java +++ b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/handler/SonyAudioHandler.java @@ -64,7 +64,7 @@ abstract class SonyAudioHandler extends BaseThingHandler implements SonyAudioEve private ScheduledFuture refreshJob; private int currentRadioStation = 0; - private final Map input_zone = new HashMap<>(); + private final Map inputZone = new HashMap<>(); private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toMillis(5); @@ -303,13 +303,13 @@ public void handleInputCommand(Command command, ChannelUID channelUID, int zone) SonyAudioConnection.SonyAudioInput result = inputCache[zone].getValue(); if (result != null) { if (zone > 0) { - input_zone.put(zone, result.input); + inputZone.put(zone, result.input); } updateState(channelUID, inputSource(result.input)); - if (result.radio_freq.isPresent()) { + if (result.radioFrequency.isPresent()) { updateState(SonyAudioBindingConstants.CHANNEL_RADIO_FREQ, - new DecimalType(result.radio_freq.get() / 1000000.0)); + new DecimalType(result.radioFrequency.get() / 1000000.0)); } } } catch (CompletionException ex) { @@ -387,7 +387,7 @@ public void handleRadioStationCommand(Command command, ChannelUID channelUID) th String radioCommand = "radio:fm?contentId=" + currentRadioStation; for (int i = 1; i <= 4; i++) { - String input = input_zone.get(i); + String input = inputZone.get(i); if (input != null && input.startsWith("radio:fm")) { connection.setInput(radioCommand, i); } @@ -418,20 +418,20 @@ public void initialize() { Configuration config = getThing().getConfiguration(); String ipAddress = (String) config.get(SonyAudioBindingConstants.HOST_PARAMETER); String path = (String) config.get(SonyAudioBindingConstants.SCALAR_PATH_PARAMETER); - Object port_o = config.get(SonyAudioBindingConstants.SCALAR_PORT_PARAMETER); + Object portO = config.get(SonyAudioBindingConstants.SCALAR_PORT_PARAMETER); int port = 10000; - if (port_o instanceof BigDecimal decimalValue) { + if (portO instanceof BigDecimal decimalValue) { port = decimalValue.intValue(); - } else if (port_o instanceof Integer) { - port = (int) port_o; + } else if (portO instanceof Integer) { + port = (int) portO; } - Object refresh_o = config.get(SonyAudioBindingConstants.REFRESHINTERVAL); + Object refreshO = config.get(SonyAudioBindingConstants.REFRESHINTERVAL); int refresh = 0; - if (refresh_o instanceof BigDecimal decimalValue) { + if (refreshO instanceof BigDecimal decimalValue) { refresh = decimalValue.intValue(); - } else if (refresh_o instanceof Integer) { - refresh = (int) refresh_o; + } else if (refreshO instanceof Integer) { + refresh = (int) refreshO; } try { @@ -509,9 +509,9 @@ public void updateInput(int zone, SonyAudioConnection.SonyAudioInput input) { break; } - if (input.radio_freq.isPresent()) { + if (input.radioFrequency.isPresent()) { updateState(SonyAudioBindingConstants.CHANNEL_RADIO_FREQ, - new DecimalType(input.radio_freq.get() / 1000000.0)); + new DecimalType(input.radioFrequency.get() / 1000000.0)); } } diff --git a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioConnection.java b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioConnection.java index 52a3552105bd0..791281bf4786f 100644 --- a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioConnection.java +++ b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioConnection.java @@ -49,7 +49,7 @@ public class SonyAudioConnection implements SonyAudioClientSocketEventListener { private final String host; private final int port; private final String path; - private final URI base_uri; + private final URI baseUri; private final WebSocketClient webSocketClient; @@ -59,8 +59,8 @@ public class SonyAudioConnection implements SonyAudioClientSocketEventListener { private final SonyAudioEventListener listener; - private int min_volume = 0; - private int max_volume = 50; + private int minVolume = 0; + private int maxVolume = 50; private final Gson gson; @@ -73,14 +73,14 @@ public SonyAudioConnection(String host, int port, String path, SonyAudioEventLis this.gson = new Gson(); this.webSocketClient = webSocketClient; - base_uri = new URI(String.format("ws://%s:%d/%s", host, port, path)).normalize(); + baseUri = new URI(String.format("ws://%s:%d/%s", host, port, path)).normalize(); - URI wsAvContentUri = base_uri.resolve(base_uri.getPath() + "/avContent").normalize(); + URI wsAvContentUri = baseUri.resolve(baseUri.getPath() + "/avContent").normalize(); avContentSocket = new SonyAudioClientSocket(this, wsAvContentUri, scheduler); - URI wsAudioUri = base_uri.resolve(base_uri.getPath() + "/audio").normalize(); + URI wsAudioUri = baseUri.resolve(baseUri.getPath() + "/audio").normalize(); audioSocket = new SonyAudioClientSocket(this, wsAudioUri, scheduler); - URI wsSystemUri = base_uri.resolve(base_uri.getPath() + "/system").normalize(); + URI wsSystemUri = baseUri.resolve(baseUri.getPath() + "/system").normalize(); systemSocket = new SonyAudioClientSocket(this, wsSystemUri, scheduler); } @@ -123,7 +123,7 @@ public void handleEvent(JsonObject json) { input.input = param.get("uri").getAsString(); if (param.has("broadcastFreq")) { int freq = param.get("broadcastFreq").getAsInt(); - input.radio_freq = Optional.of(freq); + input.radioFrequency = Optional.of(freq); checkRadioPreset(input.input); } listener.updateInput(zone, input); @@ -134,7 +134,7 @@ public void handleEvent(JsonObject json) { SonyAudioVolume volume = new SonyAudioVolume(); int rawVolume = param.get("volume").getAsInt(); - volume.volume = Math.round(100 * (rawVolume - min_volume) / (max_volume - min_volume)); + volume.volume = Math.round(100 * (rawVolume - minVolume) / (maxVolume - minVolume)); volume.mute = "on".equalsIgnoreCase(param.get("mute").getAsString()); listener.updateVolume(zone, volume); @@ -274,8 +274,8 @@ public boolean checkConnection() { } public String getConnectionName() { - if (base_uri != null) { - return base_uri.toString(); + if (baseUri != null) { + return baseUri.toString(); } return String.format("ws://%s:%d/%s", host, port, path); } @@ -292,8 +292,9 @@ public Boolean getPower(int zone) throws IOException { Iterator terminals = element.getAsJsonArray().get(0).getAsJsonArray().iterator(); while (terminals.hasNext()) { JsonObject terminal = terminals.next().getAsJsonObject(); + String zoneUri = "extOutput:zone?zone=" + Integer.toString(zone); String uri = terminal.get("uri").getAsString(); - if (uri.equalsIgnoreCase("extOutput:zone?zone=" + Integer.toString(zone))) { + if (uri.equalsIgnoreCase(zoneUri)) { return "active".equalsIgnoreCase(terminal.get("active").getAsString()) ? true : false; } } @@ -338,7 +339,7 @@ public void setPower(boolean power, int zone) throws IOException { public class SonyAudioInput { public String input = ""; - public Optional radio_freq = Optional.empty(); + public Optional radioFrequency = Optional.empty(); } public SonyAudioInput getInput() throws IOException { @@ -367,7 +368,7 @@ private SonyAudioInput getInput(GetPlayingContentInfo getPlayingContentInfo) thr if (result.has("broadcastFreq")) { int freq = result.get("broadcastFreq").getAsInt(); - ret.radio_freq = Optional.of(freq); + ret.radioFrequency = Optional.of(freq); } return ret; } @@ -425,9 +426,9 @@ public SonyAudioVolume getVolume(int zone) throws IOException { SonyAudioVolume ret = new SonyAudioVolume(); int volume = result.get("volume").getAsInt(); - min_volume = result.get("minVolume").getAsInt(); - max_volume = result.get("maxVolume").getAsInt(); - int vol = Math.round(100 * (volume - min_volume) / (max_volume - min_volume)); + minVolume = result.get("minVolume").getAsInt(); + maxVolume = result.get("maxVolume").getAsInt(); + int vol = Math.round(100 * (volume - minVolume) / (maxVolume - minVolume)); if (vol < 0) { vol = 0; } @@ -445,7 +446,7 @@ public void setVolume(int volume) throws IOException { if (audioSocket == null) { throw new IOException("Audio Socket not connected"); } - SetAudioVolume setAudioVolume = new SetAudioVolume(volume, min_volume, max_volume); + SetAudioVolume setAudioVolume = new SetAudioVolume(volume, minVolume, maxVolume); audioSocket.callMethod(setAudioVolume); } @@ -461,7 +462,7 @@ public void setVolume(int volume, int zone) throws IOException { if (audioSocket == null) { throw new IOException("Audio Socket not connected"); } - SetAudioVolume setAudioVolume = new SetAudioVolume(zone, volume, min_volume, max_volume); + SetAudioVolume setAudioVolume = new SetAudioVolume(zone, volume, minVolume, maxVolume); audioSocket.callMethod(setAudioVolume); } diff --git a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioMethod.java b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioMethod.java index 49b8d87533879..789ec5dca8b4b 100644 --- a/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioMethod.java +++ b/bundles/org.openhab.binding.sonyaudio/src/main/java/org/openhab/binding/sonyaudio/internal/protocol/SonyAudioMethod.java @@ -243,14 +243,14 @@ class Param { SetAudioVolume(int volume, int min, int max) { super("setAudioVolume", "1.1"); - long scaled_volume = scaleVolume(volume, min, max); - params = new Param[] { new Param(scaled_volume) }; + long scaledVolume = scaleVolume(volume, min, max); + params = new Param[] { new Param(scaledVolume) }; } SetAudioVolume(int zone, int volume, int min, int max) { super("setAudioVolume", "1.1"); - long scaled_volume = scaleVolume(volume, min, max); - params = new Param[] { new Param(scaled_volume, zone) }; + long scaledVolume = scaleVolume(volume, min, max); + params = new Param[] { new Param(scaledVolume, zone) }; } SetAudioVolume(String volume_change) { From 91392c0bc0bfa543d4d2c5201bb2942d360a368c Mon Sep 17 00:00:00 2001 From: Jeremy Rumpf <82414029+jeremyrumpf@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:20:42 -0400 Subject: [PATCH 032/146] [GPIO] Update GPIO binding to fix issues and provide new functionality (#13643) * [GPIO] Update the GPIO binding to fix issues and provide new functionality. * Add the ability to recover from network disconnects to pigpiod. * Provide actions to what the binding does on pigpiod connect/disconnect/reconnect. * Provide the ability to pulse outputs in a one-shot/momentary fashion. * Provide edge level configuration for gpio outputs. * Fix automated style checks. * Attempt to squash jenkins build warnings/errors. * Correct Null annotations and adjust log levels in certain areas. * Fix bracing per checkstyle review. * Normalize gpiod/pigpiod to pigpiod. openhab/openHAB normalized to OpenHAB. * Fix a copy/paste error. Output channels should not be referred as input. Attempt to clarify plurals. * Convert strings to defined constants. * Convert pulse command strings to binding constants. * Java17 instanceof pattern matching. * Nit, fix missed pulse command string to binding constant. * Add missing quotes in demo.things file definition. Workaround #11039 Fixes #11038 Fixes #13376 Signed-off-by: Jeremy Rumpf --- bundles/org.openhab.binding.gpio/README.md | 217 +++++++++-- ...ava => ChannelConfigurationException.java} | 10 +- .../gpio/internal/GPIOBindingConstants.java | 16 + .../gpio/internal/NoGpioIdException.java | 25 -- .../configuration/GPIOConfiguration.java | 3 +- .../configuration/GPIOInputConfiguration.java | 11 +- .../GPIOOutputConfiguration.java | 5 +- .../configuration/PigpioConfiguration.java | 37 ++ .../gpio/internal/handler/ChannelHandler.java | 21 +- .../handler/PigpioDigitalInputHandler.java | 177 +++++++-- .../handler/PigpioDigitalOutputHandler.java | 205 ++++++++-- .../internal/handler/PigpioRemoteHandler.java | 363 ++++++++++++++++-- .../resources/OH-INF/i18n/gpio.properties | 44 ++- .../resources/OH-INF/thing/pigpio-remote.xml | 185 ++++++++- 14 files changed, 1156 insertions(+), 163 deletions(-) rename bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/{InvalidPullUpDownException.java => ChannelConfigurationException.java} (69%) delete mode 100644 bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java diff --git a/bundles/org.openhab.binding.gpio/README.md b/bundles/org.openhab.binding.gpio/README.md index 863d226d20ae6..51e88383af2e0 100644 --- a/bundles/org.openhab.binding.gpio/README.md +++ b/bundles/org.openhab.binding.gpio/README.md @@ -1,19 +1,19 @@ # GPIO Binding -This binding adds GPIO support via the pigpio daemon to openhab. -It requires the pigpio () to be running on the pi that should be controlled. +This binding adds GPIO support via the pigpiod daemon to openHAB. +It requires the pigpiod daemon () to be installed on the pi that should be controlled. ## Supported Things ### pigpio-remote -This thing represents a remote pigpio instance running as daemon on a raspberry pi. +This thing represents a remote pigpiod instance running as daemon on a raspberry pi. ## Thing Configuration ### Pigpio Remote (`pigpio-remote`) -On a raspberry (or a compatible device) you have to install pigpio: +On a raspberry (or a compatible device) you have to install pigpiod. ```shell sudo apt-get install pigpiod @@ -39,71 +39,204 @@ ExecStart=/usr/bin/pigpiod sudo systemctl daemon-reload ``` -Now that Remote GPIO is enabled, get the daemon going (even if installed with apt-get): +Now that Remote GPIO is enabled, get the pigpiod daemon going (even if installed with apt-get): ```shell sudo systemctl enable pigpiod sudo systemctl start pigpiod ``` -In openHAB, set `host` to the address of the pi and the `port` to the port of pigpio (default: 8888). +## General Configuration -Note: If you are running Pigpio on same host as openHAB, then set host to **::1**. +Binding general configuration options. If you do not see all options, ensure `Show Advanced` is selected. + +### Host + +Set `Host` to the address of the Pi that pigpiod is running on. Default is 127.0.0.1 (IPV4). +Note: If you are running pigpiod on same host as openHAB, set the host to 127.0.0.1 (IPV4) or ::1 (IPV6). + +### Port + +Set `Port` to the network port that pigpiod is listening on. Default is 8888. + +### Heart Beat Interval + +The binding will poll pigpiod running on the Pi to determine if a network disconnect has occurred. +This is the interval in milliseconds that the heart beat poll occurs. Defaults to 30000 (30 seconds). + +## Input Channel Connect Action + +Input Channel Connect Action determines what happens when the binding initially connects to pigpiod. +This action only occurs once after binding startup. + +- **Do Nothing:** The default, do nothing. Input channels will retain their default value (UNDEF). +- **Refresh Channel:** Issues a refresh command on the input channels. This will refresh the channels from pigpiod causing the gpio pin state to reflect on the channel state. + +Input Channel Disconnect Connect Action: + +### Input Channel Disconnect Connect Action + +Input Channel Disconnect Connect Action determines what happens when the binding disconnects from pigpiod. + +- **Do Nothing:** The default, do nothing. Input channels will retain their current value. +- **Set Undef:** Sets the input channel states to UNDEF to indicate that pigpiod has disconnected. + +### Input Channel Reconnect Connect Action + +Input Channel Reconnect Action determines what happens when the binding reconnects to pigpiod +after a disconnect. This action does not occur on the initial binding connect to pigpiod. +startup. + +- **Do Nothing:** The default, do nothing. Input channels will retain their current value. +- **Refresh Channel:** Issues a refresh command on the input channels. This will refresh the channels from + pigpiod causing the gpio pin state to reflect on the channel state. + +### Output Channel Connect Action + +Output Channel Connect Action determines what happens when the binding initially connects to pigpiod. +This action only occurs once after binding startup. + +- **Do Nothing:** The default, do nothing. Output channels will retain their default value (UNDEF). +- **All On:** Issues a ON command to all configured output channels. +- **All Off:** Issues a OFF command to all configured output channels. +- **Refresh Channel:** Issues a refresh command on the output channels. This will refresh the channels from + pigpiod causing the gpio pin state to reflect on the channel state. NOTE: This does + not update the gpio pin state on the Pi itself. It only updates the channel state + within openHAB. + +### Output Channel Disconnect Connect Action + +Output Channel Disconnect Connect Action determines what happens when the binding disconnects from pigpiod. + +- **Do Nothing:** he default, do nothing. Input channels will retain their current value. +- **Set Undef:** Sets the output channel states to UNDEF to indicate that pigpiod has disconnected. + +### Output Channel Reconnect Connect Action + +Output Channel Reconnect Action determines what happens when the binding reconnects to pigpiod +after a disconnect. This action does not occur on the initial binding connect to pigpiod. + +- **Do Nothing:** The default, do nothing. Output channels will retain their current value. +- **Refresh Channel:** Issues a refresh command on the output channels. This will refresh the channels from + pigpiod causing the gpio pin state to reflect on the channel state. NOTE: This does + not update the gpio pin state on the Pi itself. It only updates the channel state + within openHAB. ## Channels -### Pigpio Remote +The binding has two channel types. +One for gpio input pins, and another for gpio output pins. | channel | type | description | |-----------------------|--------|---------------------------------| | pigpio-digital-input | Switch | Read-only value of the gpio pin | | pigpio-digital-output | Switch | Controls the gpio pin | -### GPIO digital input channel +### GPIO pigpio-digital-input channel configuration + +Input channels provide a read-only value of the gpio pin state using the `OnOffType` datatype. -Set the number of the pin in `gpioId`. -If you want to invert the value, set `invert` to true. -To prevent incorrect change events, you can adjust the `debouncingTime`. -Using `pullupdown` you can enable pull up or pull down resistor (OFF = Off, DOWN = Pull Down, UP = Pull Up). +GPIO Pin: -### GPIO digital output channel +The gpio pin number on the Pi that the channel will monitor. -Set the number of the pin in `gpioId`. -If you want to invert the value, set `invert` to true. +Invert: -## Full Example +Inverts the value of the gpio pin before reflecting the value on the channel. +Useful for active low gpio pins where you want the channel state to reflect an ON value when the pin is low (OFF). + +Delay Time: + +Sets a delay value in milliseconds that the gpio pin must remain at prior to updating the channel state. +This is the same as switch debouncing or hysteresis. +Default value is 10 milliseconds. +Increase this value for noisy inputs. + +Pull Up/Down Resistor: + +Sets the mode of operation for the internal pull up/ down resistor on the gpio pin. +Set this to OFF if you use external pull up/down resistors. + +Edge Detection Mode: + +Sets the mode of operation for the pin edge detection mode. +If you are not sure what the use case is for this, leave at the default value of `Either Edge`. +This is the most common mode that gpio inputs are used. + +### GPIO pigpio-digital-output channel configuration + +Output channels provide a means of controlling the output value of the gpio pin using the `OnOffType` datatype. + +GPIO Pin: + +The gpio pin number on the Pi that the channel will control. + +Invert: + +Inverts the value of the channel state before commanding the gpio pin. +Useful to simulate active low gpio pins. + +Pulse: + +Time in milliseconds that must elapse before the Pulse Command is sent to the channel. +Default value is 0, which disables the Pulse feature. + +Pulse Command: + +Together with the Pulse configuration, can be used to create a one shot or momentary output. +This is useful to simulate momentary button presses or to drive motors for a predefined amount +of time. + +- **Off:** When the ON command is issued to the channel. The Pulse feature will send an OFF command + after the Pulse duration. +- **On:** When the OFF command is issued to the channel. The Pulse feature will send an + ON command after the Pulse duration. +- **Blink:** Cycles the channel ON, OFF, ON indefinitely with a 50% duty cycle. The Blink + operation continues regardless of the commanded channel state. This was originaly + developed as a way to flash a status LED to visually confirm that a remote pigpiod + instance has connectivity to openHAB. + +## Config file example + +Example for users who still prefer configuration files. demo.things: ```java -Thing gpio:pigpio-remote:sample-pi-1 "Sample-Pi 1" [host="192.168.2.36", port=8888] { - Channels: - Type pigpio-digital-input : sample-input-1 [ gpioId=10] - Type pigpio-digital-input : sample-input-2 [ gpioId=14, invert=true] - Type pigpio-digital-output : sample-output-1 [ gpioId=3] -} - -Thing gpio:pigpio-remote:sample-pi-2 "Sample-Pi 2" [host="192.168.2.37", port=8888] { - Channels: - Type pigpio-digital-input : sample-input-3 [ gpioId=16, debouncingTime=20] - Type pigpio-digital-input : sample-input-4 [ gpioId=17, invert=true, debouncingTime=5, pullupdown="UP"] - Type pigpio-digital-output : sample-output-2 [ gpioId=4, invert=true] -} +Thing gpio:pigpio-remote:mypi "MyPi GPIO" [ host="192.168.1.5", port=8888, + heartBeatInterval=10000, + inputConnectAction="REFRESH", # REFRESH,NOTHING + inputDisconnectAction="NOTHING", # SETUNDEF,NOTHING + inputReconnectAction="REFRESH", # REFRESH,NOTHING + outputConnectAction="REFRESH", # ALLOFF,ALLON,REFRESH,NOTHING + outputDisconnectAction="SETUNDEF", # SETUNDEF,NOTHING + outputReconnectAction="REFRESH" ] # REFRESH,NOTHING + { + Channels: + Type pigpio-digital-output : BCM18 [ gpioId=18,invert=false,pulse=3000,pulseCommand="BLINK" ] # OFF,ON,BLINK + + Type pigpio-digital-output : GPO4 [ gpioId=4, invert=true,pulse=5000,pulseCommand="OFF" ] + Type pigpio-digital-output : GPO17 [ gpioId=17,invert=false,pulse=500,pulseCommand="ON" ] + Type pigpio-digital-output : GPO27 [ gpioId=27,invert=false ] + Type pigpio-digital-output : GPO22 [ gpioId=22,invert=true ] + + Type pigpio-digital-input : GPI23 [ gpioId=23,debouncingTime=50,pullupdown="UP",invert=true ] # OFF,DOWN,UP + Type pigpio-digital-input : GPI24 [ gpioId=24,debouncingTime=50,pullupdown="UP",invert=true ] + Type pigpio-digital-input : GPI25 [ gpioId=25,debouncingTime=50,pullupdown="UP",invert=true ] + Type pigpio-digital-input : GPI12 [ gpioId=12,debouncingTime=50,pullupdown="UP",invert=true ] + Type pigpio-digital-input : GPI16 [ gpioId=16,debouncingTime=50,pullupdown="UP",invert=true ] + Type pigpio-digital-input : GPI20 [ gpioId=20,debouncingTime=50,pullupdown="UP",invert=true,edgeMode="EDGE_EITHER" ] # EITHER,RISING,FALLING + Type pigpio-digital-input : GPI21 [ gpioId=21,debouncingTime=50,pullupdown="UP",invert=true,edgeMode="EDGE_RISING" ] + Type pigpio-digital-input : GPI5 [ gpioId=5, debouncingTime=50,pullupdown="UP",invert=true,edgeMode="EDGE_FALLING" ] + Type pigpio-digital-input : GPI6 [ gpioId=6, debouncingTime=50,pullupdown="UP",invert=true ] + Type pigpio-digital-input : GPI13 [ gpioId=13,debouncingTime=50,pullupdown="DOWN",invert=false ] + Type pigpio-digital-input : GPI26 [ gpioId=26,debouncingTime=50,pullupdown="OFF",invert=false ] + } ``` demo.items: ```java -Switch SampleInput1 {channel="gpio:pigpio-remote:sample-pi-1:sample-input-1"} -Switch SampleOutput1 {channel="gpio:pigpio-remote:sample-pi-1:sample-output-1"} -``` - -demo.sitemap: - -```perl -sitemap demo label="Main Menu" -{ - Switch item=SampleInput1 - Switch item=SampleOutput1 -} +Switch SampleInput1 {channel="gpio:pigpio-remote:mypi:GPI23"} +Switch SampleOutput1 {channel="gpio:pigpio-remote:mypi:GPO4"} ``` diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/InvalidPullUpDownException.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java similarity index 69% rename from bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/InvalidPullUpDownException.java rename to bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java index 76f10c332d296..1569dd3d1f686 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/InvalidPullUpDownException.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java @@ -15,11 +15,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * Is thrown when invalid GPIO pin Pull Up/Down resistor configuration is set + * Is thrown when a channel configuration is invalid * - * @author Martin Dagarin - Initial contribution + * @author Jeremy Rumpf - Initial contribution */ @NonNullByDefault -public class InvalidPullUpDownException extends Exception { +public class ChannelConfigurationException extends Exception { private static final long serialVersionUID = -1281107134439928767L; + + public ChannelConfigurationException(String message) { + super(message); + } } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java index f23e90dd00193..305ed05166bde 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java @@ -22,6 +22,7 @@ * * @author Nils Bauer - Initial contribution * @author Martin Dagarin - Pull Up/Down GPIO pin + * @author Jeremy Rumpf - Added Action/Edge constants */ @NonNullByDefault public class GPIOBindingConstants { @@ -43,12 +44,27 @@ public class GPIOBindingConstants { public static final String DEBOUNCING_TIME = "debouncing_time"; public static final String STRICT_DEBOUNCING = "debouncing_strict"; public static final String PULLUPDOWN_RESISTOR = "pullupdown"; + public static final String ACTION_SET_UNDEF = "SETUNDEF"; + public static final String ACTION_NOTHING = "NOTHING"; + public static final String ACTION_REFRESH = "REFRESH"; + public static final String ACTION_ALL_ON = "ALLON"; + public static final String ACTION_ALL_OFF = "ALLOFF"; // Pull Up/Down modes public static final String PUD_OFF = "OFF"; public static final String PUD_DOWN = "DOWN"; public static final String PUD_UP = "UP"; + // Pulse + public static final String PULSE_OFF = "OFF"; + public static final String PULSE_ON = "ON"; + public static final String PULSE_BLINK = "BLINK"; + + // Edge modes + public static final String EDGE_EITHER = "EDGE_EITHER"; + public static final String EDGE_RISING = "EDGE_RISING"; + public static final String EDGE_FALLING = "EDGE_FALLING"; + // GPIO config properties public static final String GPIO_ID = "gpioId"; } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java deleted file mode 100644 index 26005f95e1133..0000000000000 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.gpio.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * Is thrown when no gpio id is provided - * - * @author Nils Bauer - Initial contribution - */ -@NonNullByDefault -public class NoGpioIdException extends Exception { - private static final long serialVersionUID = -1281107134439928767L; -} diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java index 681097d4d82f2..120d84c9c353f 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java @@ -13,7 +13,6 @@ package org.openhab.binding.gpio.internal.configuration; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; /** * The {@link GPIOConfiguration} class contains fields mapping thing configuration parameters. @@ -26,7 +25,7 @@ public class GPIOConfiguration { /** * The id of the gpio pin. */ - public @Nullable Integer gpioId; + public Integer gpioId = 0; /** * Should the input/output be inverted? diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java index 3121a348cfb89..abf6f6131bda1 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.gpio.internal.configuration; +import static org.openhab.binding.gpio.internal.GPIOBindingConstants.*; + import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -31,5 +33,12 @@ public class GPIOInputConfiguration extends GPIOConfiguration { * Setup a pullup resistor on the GPIO pin * OFF = PI_PUD_OFF, DOWN = PI_PUD_DOWN, UP = PI_PUD_UP */ - public String pullupdown = "OFF"; + public String pullupdown = PUD_OFF; + + /** + * Sets the input detection type. + * EDGE_EITHER = PI_EITHER_EDGE, EDGE_FALLING = PI_FALLING_EDGE, + * EDGE_RISING = PI_RISING_EDGE + */ + public String edgeMode = EDGE_EITHER; } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java index 750fe6e49efc0..d507364081496 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.gpio.internal.configuration; +import java.math.BigDecimal; + import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -21,5 +23,6 @@ */ @NonNullByDefault public class GPIOOutputConfiguration extends GPIOConfiguration { - + public BigDecimal pulse = new BigDecimal(0); + public String pulseCommand = "OFF"; } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java index 4e2ead631b76d..9acb3d22bcb0d 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java @@ -32,4 +32,41 @@ public class PigpioConfiguration { * Port of pigpio on the remote raspberry pi */ public int port = 8888; + + /** + * Interval to send heartbeat checks + */ + public int heartBeatInterval = 60000; + + /** + * Input channel action on connect + * (First connect after INITIALIATION) + */ + public @Nullable String inputConnectAction; + + /** + * Input channel action on reconnect + */ + public @Nullable String inputReconnectAction; + + /** + * Input channel action on disconnect + */ + public @Nullable String inputDisconnectAction; + + /** + * Output channel action on connect + * (First connect after INITIALIATION) + */ + public @Nullable String outputConnectAction; + + /** + * Output channel action on reconnect + */ + public @Nullable String outputReconnectAction; + + /** + * Output channel action on disconnect + */ + public @Nullable String outputDisconnectAction; } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java index 3958c68857331..a87373b02486c 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java @@ -13,8 +13,12 @@ package org.openhab.binding.gpio.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.types.Command; +import eu.xeli.jpigpio.JPigpio; +import eu.xeli.jpigpio.PigpioException; + /** * The {@link ChannelHandler} provides an interface for different pin * configuration handlers @@ -24,5 +28,20 @@ @NonNullByDefault public interface ChannelHandler { - void handleCommand(Command command); + /** + * Handles a Command being sent from the + * Openhab framework. + */ + void handleCommand(Command command) throws PigpioException; + + /** + * (Re)Establishes the JPigpio listeners. + */ + void listen(@Nullable JPigpio jPigpio) throws PigpioException; + + /** + * Terminates sending Channels status updates and + * shuts down any JPigpio listeners. + */ + void dispose(); } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java index 65086458a0071..5ecb5daa73f1f 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java @@ -18,9 +18,9 @@ import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.gpio.internal.ChannelConfigurationException; import org.openhab.binding.gpio.internal.GPIOBindingConstants; -import org.openhab.binding.gpio.internal.InvalidPullUpDownException; -import org.openhab.binding.gpio.internal.NoGpioIdException; import org.openhab.binding.gpio.internal.configuration.GPIOInputConfiguration; import org.openhab.core.library.types.OnOffType; import org.openhab.core.types.Command; @@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory; import eu.xeli.jpigpio.GPIO; +import eu.xeli.jpigpio.GPIOListener; import eu.xeli.jpigpio.JPigpio; import eu.xeli.jpigpio.PigpioException; @@ -39,64 +40,174 @@ * @author Nils Bauer - Initial contribution * @author Jan N. Klug - Channel redesign * @author Martin Dagarin - Pull Up/Down GPIO pin + * @author Jeremy Rumpf - Refactored for network disruptions */ @NonNullByDefault public class PigpioDigitalInputHandler implements ChannelHandler { - private final Logger logger = LoggerFactory.getLogger(PigpioDigitalInputHandler.class); private Date lastChanged = new Date(); private final GPIOInputConfiguration configuration; - private final GPIO gpio; - private final Consumer updateStatus; + private final ScheduledExecutorService scheduler; + private final Integer gpioId; + private @Nullable GPIO gpio; + private @Nullable Consumer updateStatus; + private Integer pullupdown = JPigpio.PI_PUD_OFF; + private final GPIOListener listener; + private int edgeMode = JPigpio.PI_EITHER_EDGE; - public PigpioDigitalInputHandler(GPIOInputConfiguration configuration, JPigpio jPigpio, - ScheduledExecutorService scheduler, Consumer updateStatus) - throws PigpioException, InvalidPullUpDownException, NoGpioIdException { + /** + * Constructor for PigpioDigitalOutputHandler + * + * @param configuration The channel configuration + * @param jPigpio The jPigpio instance + * @param updateStatus Is called when the state should be changed + * + * @throws PigpioException Can be thrown by Pigpio + * @throws ChannelConfigurationException Thrown on configuration error + */ + public PigpioDigitalInputHandler(GPIOInputConfiguration configuration, ScheduledExecutorService scheduler, + Consumer updateStatus) throws PigpioException, ChannelConfigurationException { this.configuration = configuration; + this.scheduler = scheduler; this.updateStatus = updateStatus; - Integer gpioId = configuration.gpioId; - if (gpioId == null) { - throw new NoGpioIdException(); + this.gpioId = configuration.gpioId; + + if (this.gpioId <= 0) { + throw new ChannelConfigurationException("Invalid gpioId value: " + this.gpioId); } - Integer pullupdown = JPigpio.PI_PUD_OFF; + String pullupdownStr = configuration.pullupdown.toUpperCase(); if (pullupdownStr.equals(GPIOBindingConstants.PUD_DOWN)) { - pullupdown = JPigpio.PI_PUD_DOWN; + this.pullupdown = JPigpio.PI_PUD_DOWN; } else if (pullupdownStr.equals(GPIOBindingConstants.PUD_UP)) { - pullupdown = JPigpio.PI_PUD_UP; + this.pullupdown = JPigpio.PI_PUD_UP; + } else if (pullupdownStr.equals(GPIOBindingConstants.PUD_OFF)) { + this.pullupdown = JPigpio.PI_PUD_OFF; } else { - if (!pullupdownStr.equals(GPIOBindingConstants.PUD_OFF)) { - throw new InvalidPullUpDownException(); - } + throw new ChannelConfigurationException("Invalid pull up/down value."); + } + + String edgeModeStr = configuration.edgeMode; + if (edgeModeStr.equals(GPIOBindingConstants.EDGE_RISING)) { + this.edgeMode = JPigpio.PI_RISING_EDGE; + } else if (edgeModeStr.equals(GPIOBindingConstants.EDGE_FALLING)) { + this.edgeMode = JPigpio.PI_FALLING_EDGE; + } else if (edgeModeStr.equals(GPIOBindingConstants.EDGE_EITHER)) { + this.edgeMode = JPigpio.PI_EITHER_EDGE; + } else { + throw new ChannelConfigurationException("Invalid edgeMode value."); } - gpio = new GPIO(jPigpio, gpioId, JPigpio.PI_INPUT); - jPigpio.gpioSetAlertFunc(gpio.getPin(), (gpio, level, tick) -> { - lastChanged = new Date(); - Date thisChange = new Date(); + + this.listener = new GPIOListener(this.gpioId, this.edgeMode) { + @Override + public void alert(int gpio, int level, long tick) { + alertFunc(gpio, level, tick); + } + }; + } + + public void alertFunc(int gpio, int level, long tick) { + this.lastChanged = new Date(); + Date thisChange = new Date(); + if (configuration.debouncingTime > 0) { scheduler.schedule(() -> afterDebounce(thisChange), configuration.debouncingTime, TimeUnit.MILLISECONDS); - }); - jPigpio.gpioSetPullUpDown(gpio.getPin(), pullupdown); + } else { + afterDebounce(thisChange); + } } + /** + * Syncronize debouncing callbacks to + * ensure they are not out of order. + */ + private Object debounceLock = new Object(); + private void afterDebounce(Date thisChange) { - try { - // Check if value changed over time - if (!thisChange.before(lastChanged)) { - updateStatus.accept(OnOffType.from(configuration.invert != gpio.getValue())); + synchronized (debounceLock) { + GPIO lgpio = this.gpio; + Consumer lupdateStatus = this.updateStatus; + + if (lgpio == null || lupdateStatus == null) { + // We raced and went offline in the meantime. + return; + } + + try { + // Check if value changed over time + if (!thisChange.before(lastChanged)) { + lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue())); + } + } catch (PigpioException e) { + // -99999999 is communication related, we will let the Thing connect poll refresh it. + if (e.getErrorCode() != -99999999) { + logger.debug("Debounce exception :", e); + } } + } + } + + /** + * Establishes or re-establishes a listener on the JPigpio + * instance for the configured gpio pin. + */ + public void listen(@Nullable JPigpio jPigpio) throws PigpioException { + if (jPigpio == null) { + this.gpio = null; + return; + } + + GPIO lgpio = new GPIO(jPigpio, this.gpioId, JPigpio.PI_INPUT); + this.gpio = lgpio; + + try { + lgpio.setDirection(JPigpio.PI_INPUT); + jPigpio.gpioSetPullUpDown(lgpio.getPin(), this.pullupdown); + jPigpio.removeCallback(this.listener); } catch (PigpioException e) { - logger.warn("Unknown pigpio exception", e); + // If there is a communication error, the set alert below will throw. + if (e.getErrorCode() != -99999999) { + logger.debug("Listen exception :", e); + } } + + jPigpio.gpioSetAlertFunc(lgpio.getPin(), this.listener); } @Override - public void handleCommand(Command command) { + public void handleCommand(Command command) throws PigpioException { + GPIO lgpio = this.gpio; + Consumer lupdateStatus = this.updateStatus; + + if (lgpio == null || lupdateStatus == null) { + logger.warn("An attempt to submit a command was made when pigpiod was offline: {}", command.toString()); + return; + } + if (command instanceof RefreshType) { - try { - updateStatus.accept(OnOffType.from(configuration.invert != gpio.getValue())); - } catch (PigpioException e) { - logger.warn("Unknown pigpio exception while handling Refresh", e); + lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue())); + } + } + + @Override + public void dispose() { + synchronized (debounceLock) { + GPIO lgpio = this.gpio; + + updateStatus = null; + if (lgpio != null) { + JPigpio ljPigpio = lgpio.getPigpio(); + if (ljPigpio != null) { + try { + ljPigpio.removeCallback(listener); + } catch (PigpioException e) { + // Best effort to remove listener, + // the command socket could already be dead. + if (e.getErrorCode() != -99999999) { + logger.debug("Dispose exception :", e); + } + } + } } } } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java index a48874c18ff95..cb1dd34bab796 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java @@ -12,10 +12,17 @@ */ package org.openhab.binding.gpio.internal.handler; +import static org.openhab.binding.gpio.internal.GPIOBindingConstants.*; + +import java.math.BigDecimal; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.gpio.internal.NoGpioIdException; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.gpio.internal.ChannelConfigurationException; import org.openhab.binding.gpio.internal.configuration.GPIOOutputConfiguration; import org.openhab.core.library.types.OnOffType; import org.openhab.core.types.Command; @@ -33,16 +40,19 @@ * * @author Nils Bauer - Initial contribution * @author Jan N. Klug - Channel redesign + * @author Jeremy Rumpf - Refactored for network disruptions */ @NonNullByDefault public class PigpioDigitalOutputHandler implements ChannelHandler { - - /** The logger. */ private final Logger logger = LoggerFactory.getLogger(PigpioDigitalOutputHandler.class); private final GPIOOutputConfiguration configuration; - private final GPIO gpio; - private final Consumer updateStatus; + private final ScheduledExecutorService scheduler; + private final Integer gpioId; + private Integer pulseTimeout = -1; + private @Nullable String pulseCommand = ""; + private @Nullable GPIO gpio; + private @Nullable Consumer updateStatus; /** * Constructor for PigpioDigitalOutputHandler @@ -52,34 +62,183 @@ public class PigpioDigitalOutputHandler implements ChannelHandler { * @param updateStatus Is called when the state should be changed * * @throws PigpioException Can be thrown by Pigpio - * @throws NoGpioIdException Is thrown when no gpioId is defined + * @throws ChannelConfigurationException Thrown on configuration error */ - public PigpioDigitalOutputHandler(GPIOOutputConfiguration configuration, JPigpio jPigpio, - Consumer updateStatus) throws PigpioException, NoGpioIdException { + public PigpioDigitalOutputHandler(GPIOOutputConfiguration configuration, ScheduledExecutorService scheduler, + Consumer updateStatus) throws PigpioException, ChannelConfigurationException { this.configuration = configuration; + this.gpioId = configuration.gpioId; + this.scheduler = scheduler; this.updateStatus = updateStatus; - Integer gpioId = configuration.gpioId; - if (gpioId == null) { - throw new NoGpioIdException(); + + if (this.gpioId <= 0) { + throw new ChannelConfigurationException("Invalid gpioId value."); + } + + if (configuration.pulse.compareTo(BigDecimal.ZERO) > 0) { + try { + this.pulseTimeout = configuration.pulse.intValue(); + } catch (Exception e) { + throw new ChannelConfigurationException("Invalid expire value."); + } + } + + if (configuration.pulseCommand.length() > 0) { + this.pulseCommand = configuration.pulseCommand.toUpperCase(); + if (!PULSE_ON.equals(pulseCommand) && !PULSE_OFF.equals(pulseCommand) + && !PULSE_BLINK.equals(pulseCommand)) { + throw new ChannelConfigurationException("Invalid pulseCommand value."); + } } - this.gpio = new GPIO(jPigpio, gpioId, JPigpio.PI_OUTPUT); } + /** + * Future to track pulse commands. + */ + private @Nullable Future pulseJob = null; + private @Nullable OnOffType lastPulseCommand; + + /** + * Used to only keep a single gpio command handle in flight + * at a time. + */ + private Object handleLock = new Object(); + @Override - public void handleCommand(Command command) { - if (command instanceof RefreshType) { - try { - updateStatus.accept(OnOffType.from(configuration.invert != gpio.getValue())); - } catch (PigpioException e) { - logger.warn("Unknown pigpio exception while handling Refresh", e); + public void handleCommand(Command command) throws PigpioException { + synchronized (handleLock) { + GPIO lgpio = this.gpio; + Consumer lupdateStatus = this.updateStatus; + Future job = this.pulseJob; + + if (lgpio == null || lupdateStatus == null) { + logger.warn("An attempt to submit a command was made when the pigpiod was offline: {}", + command.toString()); + return; + } + + if (command instanceof RefreshType) { + lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue())); + } else if (command instanceof OnOffType) { + lgpio.setValue(configuration.invert != (OnOffType.ON.equals(command))); + lupdateStatus.accept((State) command); + + if (this.pulseTimeout > 0 && this.pulseCommand != null) { + if (job != null) { + job.cancel(false); + } + + this.pulseJob = scheduler.schedule(() -> handlePulseCommand(command), this.pulseTimeout, + TimeUnit.MILLISECONDS); + } } } - if (command instanceof OnOffType) { - try { - gpio.setValue(configuration.invert != (OnOffType.ON.equals(command))); - } catch (PigpioException e) { - logger.warn("An error occured while changing the gpio value: {}", e.getMessage()); + } + + public void handlePulseCommand(@Nullable Command command) { + OnOffType eCommand = OnOffType.OFF; + + try { + synchronized (handleLock) { + GPIO lgpio = this.gpio; + Consumer lupdateStatus = this.updateStatus; + Future job = this.pulseJob; + + if (lgpio == null) { + return; + } + + if (command instanceof OnOffType) { + if (this.pulseCommand != null) { + if (PULSE_ON.equals(this.pulseCommand)) { + eCommand = OnOffType.ON; + } else if (PULSE_OFF.equals(this.pulseCommand)) { + eCommand = OnOffType.OFF; + } else if (PULSE_BLINK.equals(this.pulseCommand)) { + if (OnOffType.ON.equals(command)) { + eCommand = OnOffType.OFF; + } else if (OnOffType.OFF.equals(command)) { + eCommand = OnOffType.ON; + } + } + } else { + if (OnOffType.ON.equals(command)) { + eCommand = OnOffType.OFF; + } else if (OnOffType.OFF.equals(command)) { + eCommand = OnOffType.ON; + } + } + + logger.debug("gpio pulse command : {} {}", this.gpioId, eCommand.toString()); + + lgpio.setValue(configuration.invert != (OnOffType.ON.equals(eCommand))); + if (lupdateStatus != null) { + lupdateStatus.accept((State) eCommand); + } + + lastPulseCommand = eCommand; + + if (PULSE_BLINK.equals(this.pulseCommand) && this.pulseTimeout > 0) { + final OnOffType feCommand = eCommand; + if (job != null) { + job.cancel(false); + } + this.pulseJob = scheduler.schedule(() -> handlePulseCommand(feCommand), this.pulseTimeout, + TimeUnit.MILLISECONDS); + } + } + } + } catch (Exception e) { + logger.warn( + "Pulse command exception, {} command may not have been received by pigpiod resulting in an unknown state:", + eCommand.toString(), e); + } + } + + /** + * Configures the GPIO pin for OUTPUT. + */ + public void listen(@Nullable JPigpio jPigpio) throws PigpioException { + if (jPigpio == null) { + this.gpio = null; + return; + } + + GPIO lgpio = new GPIO(jPigpio, gpioId, JPigpio.PI_OUTPUT); + this.gpio = lgpio; + lgpio.setDirection(JPigpio.PI_OUTPUT); + scheduleBlink(); + } + + private void scheduleBlink() { + synchronized (handleLock) { + Future job = this.pulseJob; + + if (this.pulseTimeout > 0 && PULSE_BLINK.equals(configuration.pulseCommand)) { + if (job != null) { + job.cancel(false); + } + if (this.lastPulseCommand != null) { + scheduler.schedule(() -> handlePulseCommand(this.lastPulseCommand), this.pulseTimeout, + TimeUnit.MILLISECONDS); + } else { + this.pulseJob = scheduler.schedule(() -> handlePulseCommand(OnOffType.OFF), this.pulseTimeout, + TimeUnit.MILLISECONDS); + } + } + } + } + + @Override + public void dispose() { + synchronized (handleLock) { + Future job = this.pulseJob; + + if (job != null) { + job.cancel(true); } + this.updateStatus = null; + this.gpio = null; } } } diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java index 3f561cd7449ac..f2ccd8fc846bb 100644 --- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java +++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java @@ -16,13 +16,16 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.gpio.internal.InvalidPullUpDownException; -import org.openhab.binding.gpio.internal.NoGpioIdException; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.gpio.internal.ChannelConfigurationException; import org.openhab.binding.gpio.internal.configuration.GPIOInputConfiguration; import org.openhab.binding.gpio.internal.configuration.GPIOOutputConfiguration; import org.openhab.binding.gpio.internal.configuration.PigpioConfiguration; +import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -30,6 +33,8 @@ import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +49,7 @@ * * @author Nils Bauer - Initial contribution * @author Jan N. Klug - Channel redesign + * @author Jeremy Rumpf - Improve JPigpio connection handling */ @NonNullByDefault public class PigpioRemoteHandler extends BaseThingHandler { @@ -61,56 +67,355 @@ public PigpioRemoteHandler(Thing thing) { @Override public void handleCommand(ChannelUID channelUID, Command command) { - ChannelHandler channelHandler = channelHandlers.get(channelUID); - if (channelHandler != null) { - channelHandler.handleCommand(command); + try { + synchronized (this.connectionLock) { + ChannelHandler channelHandler = channelHandlers.get(channelUID); + + if (channelHandler == null || !(ThingStatus.ONLINE.equals(thing.getStatus()))) { + // We raced with connectPollWorker and lost + return; + } + + if (channelHandler instanceof PigpioDigitalInputHandler inputHandler) { + try { + inputHandler.handleCommand(command); + } catch (PigpioException pe) { + logger.warn("Input command exception on channel {} {}", channelUID, pe.toString()); + if (pe.getErrorCode() == -99999999) { + runDisconnectActions(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + pe.getLocalizedMessage()); + } + } + } else if (channelHandler instanceof PigpioDigitalOutputHandler outputHandler) { + try { + outputHandler.handleCommand(command); + } catch (PigpioException pe) { + logger.warn("Output command exception on channel {} {}", channelUID, pe.toString()); + if (pe.getErrorCode() == -99999999) { + runDisconnectActions(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + pe.getLocalizedMessage()); + } + } + } else { + logger.warn("Command received for an unknown channel: {}", channelUID); + } + } + } catch (Exception e) { + logger.warn("Command exception on channel {} {}", channelUID, e.toString()); } } + protected PigpioConfiguration config = new PigpioConfiguration(); + protected @Nullable JPigpio jPigpio = null; + @Override public void initialize() { - PigpioConfiguration config = getConfigAs(PigpioConfiguration.class); - String host = config.host; - int port = config.port; - JPigpio jPigpio; - if (host == null) { + PigpioConfiguration lconfig = getConfigAs(PigpioConfiguration.class); + this.config = lconfig; + + if (lconfig.host == null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Cannot connect to PiGPIO Service on remote raspberry. IP address not set."); return; } - try { - jPigpio = new PigpioSocket(host, port); - updateStatus(ThingStatus.ONLINE); - } catch (PigpioException e) { - if (e.getErrorCode() == PigpioException.PI_BAD_SOCKET_PORT) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port out of range"); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, - e.getLocalizedMessage()); - } + if (lconfig.port < 1 && lconfig.port > 65535) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Cannot connect to PiGPIO Service on remote raspberry. Invalid Port."); return; } - thing.getChannels().forEach(channel -> { + + createChannelHandlers(); + + logger.debug("gpio binding initialized"); + + connectionJob = scheduler.submit(() -> { + connectionPollWorker(); + }); + } + + protected void clearChannelHandlers() { + for (ChannelHandler handler : channelHandlers.values()) { + handler.dispose(); + } + channelHandlers.clear(); + } + + protected void createChannelHandlers() { + clearChannelHandlers(); + this.getThing().getChannels().forEach(channel -> { ChannelUID channelUID = channel.getUID(); ChannelTypeUID type = channel.getChannelTypeUID(); + try { if (CHANNEL_TYPE_DIGITAL_INPUT.equals(type)) { GPIOInputConfiguration configuration = channel.getConfiguration().as(GPIOInputConfiguration.class); - channelHandlers.put(channelUID, new PigpioDigitalInputHandler(configuration, jPigpio, scheduler, + this.channelHandlers.put(channelUID, new PigpioDigitalInputHandler(configuration, scheduler, state -> updateState(channelUID.getId(), state))); } else if (CHANNEL_TYPE_DIGITAL_OUTPUT.equals(type)) { GPIOOutputConfiguration configuration = channel.getConfiguration() .as(GPIOOutputConfiguration.class); - channelHandlers.put(channelUID, new PigpioDigitalOutputHandler(configuration, jPigpio, - state -> updateState(channelUID.getId(), state))); + PigpioDigitalOutputHandler handler = new PigpioDigitalOutputHandler(configuration, scheduler, + state -> updateState(channelUID.getId(), state)); + this.channelHandlers.put(channelUID, handler); } } catch (PigpioException e) { - logger.warn("Failed to initialize {}: {}", channelUID, e.getMessage()); - } catch (InvalidPullUpDownException e) { - logger.warn("Failed to initialize {}: Invalid Pull Up/Down resistor configuration", channelUID); - } catch (NoGpioIdException e) { - logger.warn("Failed to initialize {}: GpioId is not set", channelUID); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + String.format("Failed to initialize channel {} {}", channelUID, e.getLocalizedMessage())); + } catch (ChannelConfigurationException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + String.format("Invalid configuration for channel {} {}", channelUID, e.getLocalizedMessage())); } }); + + logger.debug("gpio channels initialized"); + } + + protected void setChannelJPigpio(@Nullable JPigpio jPigpio) throws PigpioException { + if (this.channelHandlers.isEmpty()) { + createChannelHandlers(); + } + + for (ChannelHandler handler : this.channelHandlers.values()) { + handler.listen(jPigpio); + } + + logger.debug("gpio jPigpio listening"); + } + + private @Nullable Future connectionJob = null; + /** + * Syncronizes all socket related code + * to avoid racing. + */ + private Object connectionLock = new Object(); + + protected void killConnectionPoll() { + if (this.connectionJob != null) { + synchronized (this.connectionLock) { + if (this.connectionJob != null) { + Future job = this.connectionJob; + this.connectionJob = null; + if (job != null) { + logger.debug("gpio connection poll : killing"); + job.cancel(true); + } + } + } + } + } + + protected void connectionPollWorker() { + Thing thing = this.getThing(); + + synchronized (connectionLock) { + ThingStatus currentStatus = thing.getStatus(); + JPigpio ljPigpio = this.jPigpio; + + if (ThingStatus.ONLINE.equals(currentStatus) && ljPigpio != null) { + // We are ONLINE and jPigpio is instantiated, this is the normal path + try { + logger.debug("gpio connection poll : CMD_TICK"); + ljPigpio.getCurrentTick(); + } catch (PigpioException e) { + logger.debug("gpio connection poll : disconnect"); + runDisconnectActions(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + e.getLocalizedMessage()); + + // We disconnected, reschedule ourselves to try a reconnect. + // First, try a quick reconnect if the user specified a long(ish) interval + int interval = this.config.heartBeatInterval; + if (interval > 1000) { + interval = 1000; + } + + this.connectionJob = scheduler.schedule(() -> { + connectionPollWorker(); + }, interval, TimeUnit.MILLISECONDS); + + logger.warn("Pigpiod disconnected : {}", this.config.host); + + return; + } + } else { + // We are OFFLINE and jPigpio may or may not be instantiated + try { + if (ljPigpio == null) { + // First initialization or re-initialization after dispose() + // jPigpio is not up and running yet. + logger.debug("gpio connection poll : connecting"); + ljPigpio = new PigpioSocket(this.config.host, this.config.port); + this.jPigpio = ljPigpio; + setChannelJPigpio(ljPigpio); + updateStatus(ThingStatus.ONLINE); + runConnectActions(); + } else { + // jPigpio is instantiated, but not connected. + // Use it's internal reconnect logic. + logger.debug("gpio connection poll : reconnecting"); + ljPigpio.reconnect(); + // jPigpio listeners are not re-established after reconnect. + // We need to reinject them into the channel handlers. + setChannelJPigpio(ljPigpio); + updateStatus(ThingStatus.ONLINE); + runReconnectActions(); + } + + logger.debug("Pigpiod connected : {}", this.config.host); + } catch (PigpioException e) { + logger.debug("gpio connection poll : failed, {}", e.getErrorCode()); + if (currentStatus.equals(ThingStatus.ONLINE) || currentStatus.equals(ThingStatus.INITIALIZING)) { + runDisconnectActions(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + e.getLocalizedMessage()); + } + } + } + + if (this.config.heartBeatInterval > 0) { + this.connectionJob = scheduler.schedule(() -> { + connectionPollWorker(); + }, this.config.heartBeatInterval, TimeUnit.MILLISECONDS); + } else { + // User disabled periodic connections, one shot? + logger.debug("gpio connection poll : disabled"); + this.connectionJob = null; + } + } + } + + protected void runConnectActions() throws PigpioException { + if (this.config.inputConnectAction != null) { + if (ACTION_REFRESH.equals(this.config.inputConnectAction)) { + refreshInputChannels(); + } + } + + if (this.config.outputConnectAction != null) { + if (ACTION_ALL_ON.equals(this.config.outputConnectAction)) { + setOutputChannels(OnOffType.ON); + } else if (ACTION_ALL_OFF.equals(this.config.outputConnectAction)) { + setOutputChannels(OnOffType.OFF); + } else if (ACTION_REFRESH.equals(this.config.outputConnectAction)) { + refreshOutputChannels(); + } + } + } + + protected void runReconnectActions() throws PigpioException { + if (this.config.inputConnectAction != null) { + if (ACTION_REFRESH.equals(this.config.inputConnectAction)) { + refreshInputChannels(); + } + } + + if (this.config.outputConnectAction != null) { + if (ACTION_REFRESH.equals(this.config.outputConnectAction)) { + refreshOutputChannels(); + } + } + } + + protected void runDisconnectActions() { + if (this.config.inputDisconnectAction != null) { + if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) { + undefInputChannels(); + } + } + + if (this.config.outputDisconnectAction != null) { + if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) { + undefOutputChannels(); + } + } + } + + protected void refreshInputChannels() throws PigpioException { + logger.debug("gpio refresh input channels"); + for (ChannelUID channelUID : channelHandlers.keySet()) { + ChannelHandler handler = channelHandlers.get(channelUID); + if (handler instanceof PigpioDigitalInputHandler) { + handler.handleCommand(RefreshType.REFRESH); + postCommand(channelUID, RefreshType.REFRESH); + } + } + } + + protected void refreshOutputChannels() throws PigpioException { + logger.debug("gpio refresh output channels"); + for (ChannelUID channelUID : this.channelHandlers.keySet()) { + ChannelHandler handler = this.channelHandlers.get(channelUID); + if (handler instanceof PigpioDigitalOutputHandler) { + handler.handleCommand(RefreshType.REFRESH); + postCommand(channelUID, RefreshType.REFRESH); + } + } + } + + protected void undefInputChannels() { + logger.debug("gpio undef input channels"); + for (ChannelUID channelUID : this.channelHandlers.keySet()) { + ChannelHandler handler = this.channelHandlers.get(channelUID); + if (handler instanceof PigpioDigitalInputHandler) { + updateState(channelUID, UnDefType.UNDEF); + } + } + } + + protected void undefOutputChannels() { + logger.debug("gpio undef output channels"); + for (ChannelUID channelUID : channelHandlers.keySet()) { + ChannelHandler handler = channelHandlers.get(channelUID); + if (handler instanceof PigpioDigitalOutputHandler) { + updateState(channelUID, UnDefType.UNDEF); + } + } + } + + protected void setOutputChannels(OnOffType command) throws PigpioException { + logger.debug("gpio setting output channels: {}", command.toString()); + for (ChannelUID channelUID : this.channelHandlers.keySet()) { + ChannelHandler handler = this.channelHandlers.get(channelUID); + if (handler instanceof PigpioDigitalOutputHandler) { + handler.handleCommand(command); + postCommand(channelUID, command); + } + } + } + + @Override + public void dispose() { + try { + synchronized (this.connectionLock) { + JPigpio ljPigpio = this.jPigpio; + + killConnectionPoll(); + + if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) { + undefInputChannels(); + } + if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) { + undefOutputChannels(); + } + + clearChannelHandlers(); + + if (ljPigpio != null) { + try { + ljPigpio.gpioTerminate(); + this.jPigpio = null; + } catch (PigpioException e) { + // Best effort at a socket shutdown + } + } + } + logger.debug("gpio disposed"); + } catch (Exception e) { + logger.debug("Dispose exception :", e); + } + + super.dispose(); } } diff --git a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties index 564f6dc4fac88..207beccff84f1 100644 --- a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties +++ b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties @@ -10,8 +10,36 @@ thing-type.gpio.pigpio-remote.description = The remote pigpio thing represents a # thing types config +thing-type.config.gpio.pigpio-remote.heartBeatInterval.label = Heart Beat Interval +thing-type.config.gpio.pigpio-remote.heartBeatInterval.description = Time in ms to send CMD_TICK calls on the communication socket. Used to detect and recover from pigpiod disconnects. thing-type.config.gpio.pigpio-remote.host.label = Network Address thing-type.config.gpio.pigpio-remote.host.description = Network address of the Raspberry Pi. +thing-type.config.gpio.pigpio-remote.inputConnectAction.label = Input Channel Connect Action +thing-type.config.gpio.pigpio-remote.inputConnectAction.description = When a pigpiod connection is first established after binding INITIALIZATION. The desired action to perform on input channels. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state. +thing-type.config.gpio.pigpio-remote.inputConnectAction.option.REFRESH = Refresh Channel +thing-type.config.gpio.pigpio-remote.inputConnectAction.option.NOTHING = Do Nothing +thing-type.config.gpio.pigpio-remote.inputDisconnectAction.label = Input Channel Disconnect Action +thing-type.config.gpio.pigpio-remote.inputDisconnectAction.description = When a pigpiod disconnect is encountered. The desired action to perform on input channel. SETUNDEF: Set all configured channels to UNDEF. NOTHING: Leave all channels at their current state. +thing-type.config.gpio.pigpio-remote.inputDisconnectAction.option.SETUNDEF = Set Undef +thing-type.config.gpio.pigpio-remote.inputDisconnectAction.option.NOTHING = Do Nothing +thing-type.config.gpio.pigpio-remote.inputReconnectAction.label = Input Channel Reconnect Action +thing-type.config.gpio.pigpio-remote.inputReconnectAction.description = When a pigpiod connection is re-established after being disconnected. The desired action to perform on input channels. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state. +thing-type.config.gpio.pigpio-remote.inputReconnectAction.option.REFRESH = Refresh Channel +thing-type.config.gpio.pigpio-remote.inputReconnectAction.option.NOTHING = Do Nothing +thing-type.config.gpio.pigpio-remote.outputConnectAction.label = Output Channel Connect Action +thing-type.config.gpio.pigpio-remote.outputConnectAction.description = When a pigpiod connection is first established after binding INITIALIZATION. The desired action to perform on outputs. ALLOFF: Update the GPIO pin to OFF. ALLON: Update the GPIO pin to ON. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state. +thing-type.config.gpio.pigpio-remote.outputConnectAction.option.ALLOFF = All OFF +thing-type.config.gpio.pigpio-remote.outputConnectAction.option.ALLON = All ON +thing-type.config.gpio.pigpio-remote.outputConnectAction.option.REFRESH = Refresh Channel +thing-type.config.gpio.pigpio-remote.outputConnectAction.option.NOTHING = Do Nothing +thing-type.config.gpio.pigpio-remote.outputDisconnectAction.label = Output Channel Disconnect Action +thing-type.config.gpio.pigpio-remote.outputDisconnectAction.description = When a pigpiod disconnect is encountered. The desired action to perform on outputs. SETUNDEF: Set all configured channels to UNDEF. NOTHING: Leave all channels at their current state. +thing-type.config.gpio.pigpio-remote.outputDisconnectAction.option.SETUNDEF = Set Undef +thing-type.config.gpio.pigpio-remote.outputDisconnectAction.option.NOTHING = Do Nothing +thing-type.config.gpio.pigpio-remote.outputReconnectAction.label = Output Channel Reconnect Action +thing-type.config.gpio.pigpio-remote.outputReconnectAction.description = When a pigpiod connection is re-established after being disconnected. The desired action to perform on outputs. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state. +thing-type.config.gpio.pigpio-remote.outputReconnectAction.option.REFRESH = Refresh Channel +thing-type.config.gpio.pigpio-remote.outputReconnectAction.option.NOTHING = Do Nothing thing-type.config.gpio.pigpio-remote.port.label = Port thing-type.config.gpio.pigpio-remote.port.description = Port of pigpio on the remote Raspberry Pi. @@ -25,10 +53,16 @@ channel-type.gpio.pigpio-digital-output.description = Set digital state of a GPI # channel types config channel-type.config.gpio.pigpio-digital-input.debouncingTime.label = Delay Time -channel-type.config.gpio.pigpio-digital-input.debouncingTime.description = Time in ms to double check if value hasn't changed +channel-type.config.gpio.pigpio-digital-input.debouncingTime.description = Time in ms to double check if value hasn't changed. Be sure that the maximum latency of your network is not greater than two times this value. +channel-type.config.gpio.pigpio-digital-input.edgeMode.label = Edge Detection Mode +channel-type.config.gpio.pigpio-digital-input.edgeMode.description = Edge detection mode of the GPIO pin +channel-type.config.gpio.pigpio-digital-input.edgeMode.option.EDGE_EITHER = Either Edge +channel-type.config.gpio.pigpio-digital-input.edgeMode.option.EDGE_FALLING = Falling Edge +channel-type.config.gpio.pigpio-digital-input.edgeMode.option.EDGE_RISING = Rising Edge channel-type.config.gpio.pigpio-digital-input.gpioId.label = GPIO Pin channel-type.config.gpio.pigpio-digital-input.gpioId.description = GPIO pin to use as input channel-type.config.gpio.pigpio-digital-input.invert.label = Invert +channel-type.config.gpio.pigpio-digital-input.invert.description = Inverts the GPIO pin state from the channel state. Setting this to true can simulate an active low GPIO pin. channel-type.config.gpio.pigpio-digital-input.pullupdown.label = Pull Up/Down Resistor channel-type.config.gpio.pigpio-digital-input.pullupdown.description = Configure Pull Up/Down Resistor of GPIO pin channel-type.config.gpio.pigpio-digital-input.pullupdown.option.OFF = Off @@ -37,3 +71,11 @@ channel-type.config.gpio.pigpio-digital-input.pullupdown.option.UP = Pull Up channel-type.config.gpio.pigpio-digital-output.gpioId.label = GPIO Pin channel-type.config.gpio.pigpio-digital-output.gpioId.description = GPIO pin to use as output channel-type.config.gpio.pigpio-digital-output.invert.label = Invert +channel-type.config.gpio.pigpio-digital-output.invert.description = Inverts the GPIO pin state from the channel state. Setting this to true can simulate an active low GPIO pin. +channel-type.config.gpio.pigpio-digital-output.pulse.label = Pulse +channel-type.config.gpio.pigpio-digital-output.pulse.description = Issues the pulse command after the given number of milliseconds. Used to pulse outputs. +channel-type.config.gpio.pigpio-digital-output.pulseCommand.label = Pulse Command +channel-type.config.gpio.pigpio-digital-output.pulseCommand.description = The command to issue after the pulse duration to complete the pulse. Blink will alternate ON/OFF, useful for beacons or flashing leds. +channel-type.config.gpio.pigpio-digital-output.pulseCommand.option.OFF = Off +channel-type.config.gpio.pigpio-digital-output.pulseCommand.option.ON = On +channel-type.config.gpio.pigpio-digital-output.pulseCommand.option.BLINK = Blink diff --git a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml index f71d13ca66653..8e17e0e559add 100644 --- a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml +++ b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml @@ -18,6 +18,7 @@ network_address Network address of the Raspberry Pi. + 127.0.0.1 port @@ -25,6 +26,141 @@ Port of pigpio on the remote Raspberry Pi. 8888 + + time + + + Time in ms to send CMD_TICK calls on the communication socket. + Used to detect and recover from pigpiod + disconnects. + + 30000 + true + + + + + When a pigpiod connection is first established after + binding INITIALIZATION. + The desired action to + perform on input channels. + REFRESH: Send a REFRESH command + to the channel. + NOTHING: Leave + all channels + at their + current + state. + + + + + + true + NOTHING + true + + + + + When a pigpiod disconnect is encountered. + The desired action to perform on input channel. + SETUNDEF: Set + all configured channels to UNDEF. + NOTHING: Leave all channels at their current state. + + + + + + true + NOTHING + true + + + + + When a pigpiod connection is re-established after being disconnected. + The desired action to perform on + input channels. + REFRESH: Send a REFRESH command + to the channel. + NOTHING: Leave all + channels at their + current + state. + + + + + + true + NOTHING + true + + + + + When a pigpiod connection is first established after + binding INITIALIZATION. + The desired action to + perform on outputs. + ALLOFF: Update the GPIO pin to OFF. + ALLON: Update the GPIO pin to ON. + REFRESH: Send a REFRESH + command + to the channel. + NOTHING: Leave all + channels at their current + state. + + + + + + + + true + NOTHING + true + + + + + When a pigpiod disconnect is encountered. + The desired action to perform on outputs. + SETUNDEF: Set all + configured channels to UNDEF. + NOTHING: Leave all channels at their current state. + + + + + + true + NOTHING + true + + + + + When a pigpiod connection is re-established after being disconnected. + The desired action to perform on + outputs. + REFRESH: Send a REFRESH command + to the channel. + NOTHING: Leave all + channels at their current + state. + + + + + + true + NOTHING + true + @@ -35,18 +171,28 @@ - + GPIO pin to use as input false + + Inverts the GPIO pin state from the channel state. + Setting this to true can simulate an active low GPIO + pin. + time - Time in ms to double check if value hasn't changed + + Time in ms to double check if value hasn't changed. + Be sure that the maximum latency of your + network is + not greater than two times this value. + 10 true @@ -61,6 +207,17 @@ true OFF + + + Edge detection mode of the GPIO pin + + + + + + true + EDGE_EITHER + @@ -76,6 +233,30 @@ false + + Inverts the GPIO pin state from the channel state. + Setting this to true can simulate an active low GPIO + pin. + + + + + Issues the pulse command after the given number of milliseconds. Used to pulse outputs. + 0 + + + + The command to issue after the pulse duration to complete the pulse. Blink will alternate ON/OFF, + useful for beacons + or + flashing leds. + + + + + + true + OFF From ba53ced63b79bad03aa852239a2a2888f03e216a Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Tue, 31 Oct 2023 19:58:11 +0100 Subject: [PATCH 033/146] Fix typo in README.md (#15826) Signed-off-by: mueller-ma --- bundles/org.openhab.binding.ecovacs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.ecovacs/README.md b/bundles/org.openhab.binding.ecovacs/README.md index 3360cf0ee965c..80266490c117f 100644 --- a/bundles/org.openhab.binding.ecovacs/README.md +++ b/bundles/org.openhab.binding.ecovacs/README.md @@ -114,7 +114,7 @@ The following actions are supported by the `command` channel: |--------------|-------------------------------------------|------------------------------------------------------| | `clean` | Start cleaning in automatic mode. | | | `spotArea` | Start cleaning specific rooms. |
  • Only if supported by device, which can be recognized by `spotArea` being present in the list of possible states of the `current-cleaning-mode` channel.
  • Format: `spotArea:`, where `room IDs` is a semicolon separated list of room letters as shown in Ecovacs' app, so a valid command could e.g. be `spotArea:A;D;E`.
  • If you want to run 2 clean passes, amend `:x2` to the command, e.g. `spotArea:A;C;B:x2`.
| -| `customArea` | Start cleaning specific areas. |
  • Only if supported by device, which can be recognized by `customArea` being present in the list of possible states of the `current-cleaning-mode` channel.
  • Format: `customArea:;;;, where the parameters are coordinates (in mm) relative to the map.
  • The coordinates can be obtained from the `current-cleaning-spot-definition` channel when starting a custom area run from the app.
  • If you want to run 2 clean passes, amend `:x2` to the command, e.g. `customArea:100;100;1000;1000:x2`.
| +| `customArea` | Start cleaning specific areas. |
  • Only if supported by device, which can be recognized by `customArea` being present in the list of possible states of the `current-cleaning-mode` channel.
  • Format: `customArea:;;;`, where the parameters are coordinates (in mm) relative to the map.
  • The coordinates can be obtained from the `current-cleaning-spot-definition` channel when starting a custom area run from the app.
  • If you want to run 2 clean passes, amend `:x2` to the command, e.g. `customArea:100;100;1000;1000:x2`.
| | `pause` | Pause cleaning if it's currently active. | If the device is idle, the command is ignored. | | `resume` | Resume cleaning if it's currently paused. | If the device is not paused, the command is ignored. | | `stop` | Stop cleaning immediately. | | From eb1f1623a2f911e1d61e8f4a192aeb67e6693133 Mon Sep 17 00:00:00 2001 From: Hakan Tandogan Date: Wed, 1 Nov 2023 21:24:36 +0100 Subject: [PATCH 034/146] [tesla] Add channels for software update (#15816) * [tesla] Add value holders for Software Update state Signed-off-by: Hakan Tandogan --- bundles/org.openhab.binding.tesla/README.md | 5 +- .../tesla/internal/TeslaBindingConstants.java | 2 + .../internal/TeslaChannelSelectorProxy.java | 2 + .../internal/handler/TeslaVehicleHandler.java | 12 +++++ .../internal/protocol/SoftwareUpdate.java | 31 ++++++++++++ .../tesla/internal/protocol/VehicleState.java | 2 + .../resources/OH-INF/i18n/tesla.properties | 6 +++ .../main/resources/OH-INF/thing/channels.xml | 18 +++++++ .../main/resources/OH-INF/thing/model3.xml | 5 +- .../main/resources/OH-INF/thing/models.xml | 5 +- .../main/resources/OH-INF/thing/modelx.xml | 5 +- .../main/resources/OH-INF/thing/modely.xml | 5 +- .../resources/OH-INF/update/instructions.xml | 48 +++++++++++++++++++ 13 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/SoftwareUpdate.java diff --git a/bundles/org.openhab.binding.tesla/README.md b/bundles/org.openhab.binding.tesla/README.md index 86bf7825256f6..4387fdc0b51aa 100644 --- a/bundles/org.openhab.binding.tesla/README.md +++ b/bundles/org.openhab.binding.tesla/README.md @@ -182,9 +182,12 @@ Additionally, these advanced channels are available (not all are available on al | shiftstate | String | Shift State | Indicates the state of the transmission, “P”, “D”, “R”, or “N” | | sidemirrorheaters | Switch | Side Mirror Heaters | Indicates if the side mirror heaters are switched on | | smartpreconditioning | Switch | Smart Preconditioning | Indicates if smart preconditioning is switched on | +| softwareupdateavailable | Switch | Update Available | Car software update available, automatically generated on non-empty "update version" | +| softwareupdatestatus | String | Update Status | Car software update status, e.g. "downloading_wifi_wait", "installing" | +| softwareupdateversion | String | Update Version | Car software version to update to, e.g. "2023.32.9" or empty | | soc | Number | State of Charge | State of Charge, in % | | state | String | State | “online”, “asleep”, “waking” | -| steeringwheelheater | Switch | Steering Wheel Heater | Turns On/Off the steering wheel heater | +| steeringwheelheater | Switch | Steering Wheel Heater | Turns On/Off the steering wheel heater | | sunroofstate | String | Sunroof State | Valid states are “unknown”, “open”, “closed”, “vent”, “comfort”. Accepts commands "close" and "vent". | | sunroof | Dimmer | Sunroof | Indicates the opening state of the sunroof (0% closed, 100% fully open) | | temperature | Number:Temperature | Temperature | Set the temperature of the autoconditioning system. The temperature for the driver and passenger will be synced. | 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 09c1cf3d3d351..c752c8ea0846d 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 @@ -101,6 +101,8 @@ public enum EventKeys { public static final String CHANNEL_COMBINED_TEMP = "combinedtemp"; public static final String CHANNEL_EVENTSTAMP = "eventstamp"; + public static final String CHANNEL_SOFTWARE_UPDATE_AVAILABLE = "softwareupdateavailable"; + // thing configurations public static final String CONFIG_ALLOWWAKEUP = "allowWakeup"; public static final String CONFIG_ALLOWWAKEUPFORCOMMANDS = "allowWakeupForCommands"; diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java index 69ed9afc50e3d..8b0bec3bad449 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java @@ -933,6 +933,8 @@ public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { 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 440b5e6da2a50..88ce230ee6e1b 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 @@ -48,6 +48,7 @@ import org.openhab.binding.tesla.internal.protocol.DriveState; import org.openhab.binding.tesla.internal.protocol.Event; import org.openhab.binding.tesla.internal.protocol.GUIState; +import org.openhab.binding.tesla.internal.protocol.SoftwareUpdate; import org.openhab.binding.tesla.internal.protocol.Vehicle; import org.openhab.binding.tesla.internal.protocol.VehicleData; import org.openhab.binding.tesla.internal.protocol.VehicleState; @@ -111,6 +112,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { protected VehicleState vehicleState; protected ChargeState chargeState; protected ClimateState climateState; + protected SoftwareUpdate softwareUpdate; protected boolean allowWakeUp; protected boolean allowWakeUpForCommands; @@ -922,6 +924,8 @@ public void parseAndUpdate(String request, String payLoad, String result) { (climateState.driver_temp_setting + climateState.passenger_temp_setting) / 2.0f)); updateState(CHANNEL_COMBINED_TEMP, new QuantityType<>(avgtemp, SIUnits.CELSIUS)); + softwareUpdate = vehicleState.software_update; + try { lock.lock(); @@ -932,6 +936,8 @@ public void parseAndUpdate(String request, String payLoad, String result) { entrySet.addAll(gson.toJsonTree(vehicleState, VehicleState.class).getAsJsonObject().entrySet()); entrySet.addAll(gson.toJsonTree(chargeState, ChargeState.class).getAsJsonObject().entrySet()); entrySet.addAll(gson.toJsonTree(climateState, ClimateState.class).getAsJsonObject().entrySet()); + entrySet.addAll( + gson.toJsonTree(softwareUpdate, SoftwareUpdate.class).getAsJsonObject().entrySet()); for (Map.Entry entry : entrySet) { try { @@ -966,6 +972,12 @@ public void parseAndUpdate(String request, String payLoad, String result) { e.getMessage(), e); } } + + if (softwareUpdate.version == null || softwareUpdate.version.isBlank()) { + updateState(CHANNEL_SOFTWARE_UPDATE_AVAILABLE, OnOffType.OFF); + } else { + updateState(CHANNEL_SOFTWARE_UPDATE_AVAILABLE, OnOffType.ON); + } } finally { lock.unlock(); } diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/SoftwareUpdate.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/SoftwareUpdate.java new file mode 100644 index 0000000000000..d6139c5a5a492 --- /dev/null +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/SoftwareUpdate.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.tesla.internal.protocol; + +/** + * The {@link SoftwareUpdate} is a datastructure to capture + * variables sent by the Tesla Vehicle + * + * @author Hakan Tandogan - Initial contribution + */ +public class SoftwareUpdate { + + public int download_perc; + public int expected_duration_sec; + public int install_perc; + public String status; + public String version; + + SoftwareUpdate() { + } +} diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleState.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleState.java index b4fe8b8c1042c..98f66a7367ca8 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleState.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleState.java @@ -57,6 +57,8 @@ public class VehicleState { public String vehicle_name; public String wheel_type; + public SoftwareUpdate software_update; + VehicleState() { } } 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 ce45237ac0e3c..cdf0ab7d8c8a3 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 @@ -322,6 +322,12 @@ channel-type.tesla.smartpreconditioning.label = Smart Preconditioning channel-type.tesla.smartpreconditioning.description = Indicates if smart preconditioning is switched on channel-type.tesla.soc.label = State of Charge channel-type.tesla.soc.description = State of Charge, in % +channel-type.tesla.softwareupdateavailable.label = Update Available +channel-type.tesla.softwareupdateavailable.description = Car software update available +channel-type.tesla.softwareupdatestatus.label = Update Status +channel-type.tesla.softwareupdatestatus.description = Car software update status +channel-type.tesla.softwareupdateversion.label = Update Version +channel-type.tesla.softwareupdateversion.description = Car software version to update to channel-type.tesla.speed.label = Speed channel-type.tesla.speed.description = Vehicle speed channel-type.tesla.state.label = State diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml index a24c283e18980..5facdaffb60fe 100644 --- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml @@ -4,6 +4,24 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> + + Switch + + Car software update available + + + + String + + Car software update status + + + + String + + Car software version to update to + + String 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 2a892589c9344..ff1c073d4d981 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 @@ -112,6 +112,9 @@ + + + @@ -126,7 +129,7 @@
- 1 + 2 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 2251bdc60477c..b6d6394fdf86f 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 @@ -115,6 +115,9 @@ + + + @@ -132,7 +135,7 @@ - 1 + 2 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 6d3074a987260..4440b6e4fa9ad 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 @@ -115,6 +115,9 @@ + + + @@ -132,7 +135,7 @@ - 1 + 2 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 647d592b928e5..781bc6a6fcc72 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 @@ -115,6 +115,9 @@ + + + @@ -128,7 +131,7 @@ - 1 + 2 diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/update/instructions.xml index e21eda2f56dd6..ef59ce2a2a5fb 100644 --- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/update/instructions.xml +++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/update/instructions.xml @@ -21,6 +21,18 @@ tesla:trafficminutesdelay + + + + tesla:softwareupdateavailable + + + tesla:softwareupdatestatus + + + tesla:softwareupdateversion + + @@ -41,6 +53,18 @@ tesla:trafficminutesdelay + + + + tesla:softwareupdateavailable + + + tesla:softwareupdatestatus + + + tesla:softwareupdateversion + + @@ -61,6 +85,18 @@ tesla:trafficminutesdelay + + + + tesla:softwareupdateavailable + + + tesla:softwareupdatestatus + + + tesla:softwareupdateversion + + @@ -81,5 +117,17 @@ tesla:trafficminutesdelay + + + + tesla:softwareupdateavailable + + + tesla:softwareupdatestatus + + + tesla:softwareupdateversion + + From f3ded850e6aa91f4a551acc87ab38d43c0af0506 Mon Sep 17 00:00:00 2001 From: morph166955 <53797132+morph166955@users.noreply.github.com> Date: Fri, 3 Nov 2023 02:47:55 -0500 Subject: [PATCH 035/146] [sonos] Add TrueHD5.1 and normalize Atmos (#15831) * Add TrueHD5.1 and normalize Atmos --------- Signed-off-by: Ben Rosenblum --- .../binding/sonos/internal/handler/ZonePlayerHandler.java | 5 ++++- .../src/main/resources/OH-INF/i18n/sonos.properties | 4 +++- .../src/main/resources/OH-INF/thing/channels.xml | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.sonos/src/main/java/org/openhab/binding/sonos/internal/handler/ZonePlayerHandler.java b/bundles/org.openhab.binding.sonos/src/main/java/org/openhab/binding/sonos/internal/handler/ZonePlayerHandler.java index 242fc486cc135..8f5a0f5e71182 100644 --- a/bundles/org.openhab.binding.sonos/src/main/java/org/openhab/binding/sonos/internal/handler/ZonePlayerHandler.java +++ b/bundles/org.openhab.binding.sonos/src/main/java/org/openhab/binding/sonos/internal/handler/ZonePlayerHandler.java @@ -1468,7 +1468,7 @@ public boolean isOutputLevelFixed() { case "59": case "61": case "63": - codec = "dolbyAtmos"; + codec = "Atmos"; break; case "33554434": case "33554488": @@ -1486,6 +1486,9 @@ public boolean isOutputLevelFixed() { case "84934714": codec = "DDPlus51"; break; + case "84934716": + codec = "TrueHD51"; + break; case "84934718": codec = "PCM51"; break; diff --git a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos.properties b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos.properties index 0e9c06e8e7334..4335c96d2b5db 100644 --- a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos.properties +++ b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos.properties @@ -91,11 +91,13 @@ channel-type.sonos.codec.description = Name of codec currently being decoded channel-type.sonos.codec.state.option.noSignal = No Signal channel-type.sonos.codec.state.option.silence = Silence channel-type.sonos.codec.state.option.DTS = DTS -channel-type.sonos.codec.state.option.dolbyAtmos = Dolby Atmos +channel-type.sonos.codec.state.option.Atmos = Dolby Atmos channel-type.sonos.codec.state.option.DD20 = Dolby Digital 2.0 channel-type.sonos.codec.state.option.PCM20 = Dolby Multichannel PCM 2.0 channel-type.sonos.codec.state.option.DD51 = Dolby Digital 5.1 +channel-type.sonos.codec.state.option.DDPlus20 = Dolby Digital Plus 2.0 channel-type.sonos.codec.state.option.DDPlus51 = Dolby Digital Plus 5.1 +channel-type.sonos.codec.state.option.TrueHD51 = Dolby TrueHD 5.1 channel-type.sonos.codec.state.option.PCM51 = Dolby Multichannel PCM 5.1 channel-type.sonos.codec.state.option.DTS51 = DTS Surround 5.1 channel-type.sonos.coordinator.label = Coordinator diff --git a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/thing/channels.xml index 8eb64d848262d..d38350d33595a 100644 --- a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/thing/channels.xml @@ -54,12 +54,13 @@ - + + From 7fbb63c2c6becf03440fe92611cd478676c3372b Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Fri, 3 Nov 2023 14:02:58 +0100 Subject: [PATCH 036/146] Reduce log level for logging of unknown resource types (#15815) Resolves #15468 Signed-off-by: Jacob Laursen --- .../openhab/binding/hue/internal/connection/Clip2Bridge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java index ad76b049d25fa..9a8abb1cce9b7 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java @@ -805,7 +805,7 @@ private Resources getResourcesImpl(ResourceReference reference) // work around for issue #15468 (and similar) ResourceType resourceType = reference.getType(); if (resourceType == ResourceType.ERROR) { - LOGGER.warn("Resource '{}' type '{}' unknown => GET aborted", reference.getId(), resourceType); + LOGGER.debug("Resource '{}' type '{}' unknown => GET aborted", reference.getId(), resourceType); return new Resources(); } Stream stream = null; From a569d3c2f62cf24780c58dd0c91cbe50bd1426fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Fri, 3 Nov 2023 18:17:36 +0100 Subject: [PATCH 037/146] Avoids NPE if no station provided by API (#15832) Signed-off-by: clinique --- .../internal/handler/VigiCruesHandler.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java index a3426f6dbbe65..7cd2a167b6d63 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java @@ -34,6 +34,7 @@ import org.openhab.binding.vigicrues.internal.api.ApiHandler; import org.openhab.binding.vigicrues.internal.api.VigiCruesException; import org.openhab.binding.vigicrues.internal.dto.hubeau.HubEauResponse; +import org.openhab.binding.vigicrues.internal.dto.hubeau.HubEauResponse.StationData; import org.openhab.binding.vigicrues.internal.dto.opendatasoft.OpenDatasoftResponse; import org.openhab.binding.vigicrues.internal.dto.vigicrues.CdStationHydro; import org.openhab.binding.vigicrues.internal.dto.vigicrues.InfoVigiCru; @@ -129,17 +130,22 @@ private Map discoverAttributes(StationConfiguration config) { try { HubEauResponse stationDetails = apiHandler.discoverStations(config.id); - stationDetails.stations.stream().findFirst().ifPresent(station -> { - PointType stationLocation = new PointType( - String.format(Locale.US, "%f,%f", station.latitudeStation, station.longitudeStation)); - properties.put(LOCATION, stationLocation.toString()); - PointType serverLocation = locationProvider.getLocation(); - if (serverLocation != null) { - DecimalType distance = serverLocation.distanceFrom(stationLocation); - properties.put(DISTANCE, new QuantityType<>(distance, SIUnits.METRE).toString()); - } - properties.put(RIVER, station.libelleCoursEau); - }); + List stations = stationDetails.stations; + if (stations != null && stations.size() > 0) { + stationDetails.stations.stream().findFirst().ifPresent(station -> { + PointType stationLocation = new PointType( + String.format(Locale.US, "%f,%f", station.latitudeStation, station.longitudeStation)); + properties.put(LOCATION, stationLocation.toString()); + PointType serverLocation = locationProvider.getLocation(); + if (serverLocation != null) { + DecimalType distance = serverLocation.distanceFrom(stationLocation); + properties.put(DISTANCE, new QuantityType<>(distance, SIUnits.METRE).toString()); + } + properties.put(RIVER, station.libelleCoursEau); + }); + } else { + throw new VigiCruesException("No stations provided"); + } } catch (VigiCruesException e) { logger.info("Unable to retrieve station location details {} : {}", config.id, e.getMessage()); } From 6201b17bec739b2bc01be349e303a85e0fffffa1 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Fri, 3 Nov 2023 18:47:40 +0100 Subject: [PATCH 038/146] [networkupstools] Add battery.temperature channel (#15812) * Add channel battery-temperature --------- Signed-off-by: Leo Siepel --- .../README.md | 1 + .../networkupstools/internal/NutName.java | 3 ++- .../OH-INF/i18n/networkupstools.properties | 2 ++ .../src/main/resources/OH-INF/thing/channels.xml | 7 ++++++- .../main/resources/OH-INF/thing/thing-types.xml | 5 +++++ .../resources/OH-INF/update/instructions.xml | 16 ++++++++++++++++ .../internal/NutNameChannelsTest.java | 2 +- 7 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/update/instructions.xml diff --git a/bundles/org.openhab.binding.networkupstools/README.md b/bundles/org.openhab.binding.networkupstools/README.md index 861abcf07013c..6f7d30aae6217 100644 --- a/bundles/org.openhab.binding.networkupstools/README.md +++ b/bundles/org.openhab.binding.networkupstools/README.md @@ -76,6 +76,7 @@ The following channels are available: | batteryCharge | Number:Dimensionless | % | Battery charge (percent) | no | | batteryRuntime | Number:Time | s | Battery runtime (seconds) | no | | batteryVoltage | Number:ElectricPotential | V | Battery voltage (V) | yes | +| batteryTemperature | Number:Temperature | °C | Battery temperature (degrees C) | yes | ### Dynamic Channels diff --git a/bundles/org.openhab.binding.networkupstools/src/main/java/org/openhab/binding/networkupstools/internal/NutName.java b/bundles/org.openhab.binding.networkupstools/src/main/java/org/openhab/binding/networkupstools/internal/NutName.java index f0453f8f9c494..dc7039c875c33 100644 --- a/bundles/org.openhab.binding.networkupstools/src/main/java/org/openhab/binding/networkupstools/internal/NutName.java +++ b/bundles/org.openhab.binding.networkupstools/src/main/java/org/openhab/binding/networkupstools/internal/NutName.java @@ -64,7 +64,8 @@ enum NutName { // Battery BATTERY_CHARGE("batteryCharge", "battery.charge", Units.PERCENT), BATTERY_RUNTIME("batteryRuntime", "battery.runtime", Units.SECOND), - BATTERY_VOLTAGE("batteryVoltage", "battery.voltage", Units.VOLT); + BATTERY_VOLTAGE("batteryVoltage", "battery.voltage", Units.VOLT), + BATTERY_TEMPERATURE("batteryTemperature", "battery.temperature", SIUnits.CELSIUS); static final Map NUT_NAME_MAP = Stream.of(NutName.values()) .collect(Collectors.toMap(NutName::getChannelId, Function.identity())); diff --git a/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/i18n/networkupstools.properties b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/i18n/networkupstools.properties index 4c6f6330f8d39..ee23de1d94c42 100644 --- a/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/i18n/networkupstools.properties +++ b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/i18n/networkupstools.properties @@ -29,6 +29,8 @@ channel-type.networkupstools.battery-charge.label = Battery Charge channel-type.networkupstools.battery-charge.description = Battery charge (percent) channel-type.networkupstools.battery-runtime.label = Battery Runtime channel-type.networkupstools.battery-runtime.description = Battery runtime (seconds) +channel-type.networkupstools.battery-temperature.label = Battery Temperature +channel-type.networkupstools.battery-temperature.description = Battery temperature (degrees C) channel-type.networkupstools.battery-voltage.label = Battery Voltage channel-type.networkupstools.battery-voltage.description = Battery voltage (V) channel-type.networkupstools.input-current-status.label = Input Current Status diff --git a/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/channels.xml index 703fa0b6c1031..f88dd7e0725ee 100644 --- a/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/channels.xml @@ -143,5 +143,10 @@ Battery voltage (V) - + + Number:Temperature + + Battery temperature (degrees C) + + diff --git a/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/thing-types.xml index 3720317e0ace7..af18867b88453 100644 --- a/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/thing/thing-types.xml @@ -13,6 +13,7 @@ + @@ -32,6 +33,10 @@ + + 1 + + diff --git a/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/update/instructions.xml new file mode 100644 index 0000000000000..8f0234d475fcb --- /dev/null +++ b/bundles/org.openhab.binding.networkupstools/src/main/resources/OH-INF/update/instructions.xml @@ -0,0 +1,16 @@ + + + + + + + + networkupstools:battery-temperature + + + + + + diff --git a/bundles/org.openhab.binding.networkupstools/src/test/java/org/openhab/binding/networkupstools/internal/NutNameChannelsTest.java b/bundles/org.openhab.binding.networkupstools/src/test/java/org/openhab/binding/networkupstools/internal/NutNameChannelsTest.java index f591b2940883e..f7a888240487f 100644 --- a/bundles/org.openhab.binding.networkupstools/src/test/java/org/openhab/binding/networkupstools/internal/NutNameChannelsTest.java +++ b/bundles/org.openhab.binding.networkupstools/src/test/java/org/openhab/binding/networkupstools/internal/NutNameChannelsTest.java @@ -43,7 +43,7 @@ public class NutNameChannelsTest { private static final String THING_TYPES_XML = "thing-types.xml"; private static final String CHANNELS_XML = "channels.xml"; - private static final int EXPECTED_NUMBER_OF_CHANNELS = 20; + private static final int EXPECTED_NUMBER_OF_CHANNELS = 21; private static final int EXPECTED_NUMMBER_OF_CHANNEL_XML_LINES = EXPECTED_NUMBER_OF_CHANNELS * 6; // README table is: | Channel Name | Item Type | Unit | Description | Advanced From 46f7838d5515e59b0fe91b259d9d062c4fbb1e27 Mon Sep 17 00:00:00 2001 From: Stefan Roellin Date: Fri, 3 Nov 2023 19:08:16 +0100 Subject: [PATCH 039/146] [pilight] Fix background discovery in case of multiple network interfaces (#15791) If there were multiple network interfaces and the DatagramSocket.receive timed out an exception was thrown and the remaining network interfaces were not considered anymore. Signed-off-by: Stefan Roellin --- .../PilightBridgeDiscoveryService.java | 89 ++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java index 423c04cabb3d5..9add4f88c2061 100644 --- a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java @@ -20,6 +20,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; +import java.net.SocketException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Enumeration; @@ -55,7 +56,7 @@ public class PilightBridgeDiscoveryService extends AbstractDiscoveryService { private static final int AUTODISCOVERY_SEARCH_TIME_SEC = 5; - private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 60 * 10; + private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 10 * 60; // 10 minutes private static final String SSDP_DISCOVERY_REQUEST_MESSAGE = """ M-SEARCH * HTTP/1.1 @@ -65,6 +66,7 @@ public class PilightBridgeDiscoveryService extends AbstractDiscoveryService { MX:3 """; + public static final String SSDP_MULTICAST_ADDRESS = "239.255.255.250"; public static final int SSDP_PORT = 1900; public static final int SSDP_WAIT_TIMEOUT = 2000; // in milliseconds @@ -91,51 +93,54 @@ protected void startScan() { Enumeration inetAddresses = nic.getInetAddresses(); for (InetAddress inetAddress : Collections.list(inetAddresses)) { if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { - DatagramSocket ssdp = new DatagramSocket( - new InetSocketAddress(inetAddress.getHostAddress(), 0)); - byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(StandardCharsets.UTF_8); - DatagramPacket sendPack = new DatagramPacket(buff, buff.length); - sendPack.setAddress(InetAddress.getByName(SSDP_MULTICAST_ADDRESS)); - sendPack.setPort(SSDP_PORT); - ssdp.send(sendPack); - ssdp.setSoTimeout(SSDP_WAIT_TIMEOUT); - - boolean loop = true; - while (loop) { - DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024); - ssdp.receive(recvPack); - byte[] recvData = recvPack.getData(); - - final Scanner scanner = new Scanner(new ByteArrayInputStream(recvData), - StandardCharsets.UTF_8); - loop = scanner.findAll("Location:([0-9.]+):(.*)").peek(matchResult -> { - final String server = matchResult.group(1); - final Integer port = Integer.parseInt(matchResult.group(2)); - final String bridgeName = server.replace(".", "") + "" + port; - - logger.debug("Found pilight daemon at {}:{}", server, port); - - Map properties = new HashMap<>(); - properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server); - properties.put(PilightBindingConstants.PROPERTY_PORT, port); - properties.put(PilightBindingConstants.PROPERTY_NAME, bridgeName); - - ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE, bridgeName); - - DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties) - .withRepresentationProperty(PilightBindingConstants.PROPERTY_NAME) - .withLabel("Pilight Bridge (" + server + ")").build(); - - thingDiscovered(result); - }).count() == 0; + try { + DatagramSocket ssdp = new DatagramSocket( + new InetSocketAddress(inetAddress.getHostAddress(), 0)); + byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(StandardCharsets.UTF_8); + DatagramPacket sendPack = new DatagramPacket(buff, buff.length); + sendPack.setAddress(InetAddress.getByName(SSDP_MULTICAST_ADDRESS)); + sendPack.setPort(SSDP_PORT); + ssdp.send(sendPack); + ssdp.setSoTimeout(SSDP_WAIT_TIMEOUT); + + boolean loop = true; + while (loop) { + DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024); + ssdp.receive(recvPack); + byte[] recvData = recvPack.getData(); + + final Scanner scanner = new Scanner(new ByteArrayInputStream(recvData), + StandardCharsets.UTF_8); + loop = scanner.findAll("Location:([0-9.]+):(.*)").peek(matchResult -> { + final String server = matchResult.group(1); + final Integer port = Integer.parseInt(matchResult.group(2)); + final String bridgeName = server.replace(".", "") + "" + port; + + logger.debug("Found pilight daemon at {}:{}", server, port); + + Map properties = new HashMap<>(); + properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server); + properties.put(PilightBindingConstants.PROPERTY_PORT, port); + properties.put(PilightBindingConstants.PROPERTY_NAME, bridgeName); + + ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE, bridgeName); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid) + .withProperties(properties) + .withRepresentationProperty(PilightBindingConstants.PROPERTY_NAME) + .withLabel("Pilight Bridge (" + server + ")").build(); + + thingDiscovered(result); + }).count() == 0; + } + } catch (IOException e) { + // nothing to do } } } } - } catch (IOException e) { - if (e.getMessage() != null && !"Receive timed out".equals(e.getMessage())) { - logger.warn("Unable to enumerate the local network interfaces {}", e.getMessage()); - } + } catch (SocketException e) { + logger.warn("Unable to enumerate the local network interfaces", e); } } From 821d2fd79a959ebb9566dd1207082908e037597b Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Fri, 3 Nov 2023 19:29:05 +0100 Subject: [PATCH 040/146] Process operations asynchronously (#15801) Resolves #14927 Signed-off-by: Jacob Laursen --- .../jdbc/internal/JdbcPersistenceService.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java index 6ba41ba039cd2..73f3f29e51c49 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java @@ -21,10 +21,13 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.NamedThreadFactory; import org.openhab.core.config.core.ConfigurableService; import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.GroupItem; @@ -71,6 +74,9 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers private final ItemRegistry itemRegistry; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, + new NamedThreadFactory(JdbcPersistenceServiceConstants.SERVICE_ID)); + @Activate public JdbcPersistenceService(final @Reference ItemRegistry itemRegistry, final @Reference TimeZoneProvider timeZoneProvider) { @@ -131,21 +137,21 @@ public String getLabel(@Nullable Locale locale) { @Override public void store(Item item) { - internalStore(item, null, item.getState()); + scheduler.execute(() -> internalStore(item, null, item.getState())); } @Override public void store(Item item, @Nullable String alias) { // alias is not supported - internalStore(item, null, item.getState()); + scheduler.execute(() -> internalStore(item, null, item.getState())); } @Override public void store(Item item, ZonedDateTime date, State state) { - internalStore(item, date, state); + scheduler.execute(() -> internalStore(item, date, state)); } - private void internalStore(Item item, @Nullable ZonedDateTime date, State state) { + private synchronized void internalStore(Item item, @Nullable ZonedDateTime date, State state) { // Do not store undefined/uninitialized data if (state instanceof UnDefType) { logger.debug("JDBC::store: ignore Item '{}' because it is UnDefType", item.getName()); From 76e64bb792bae5e8061abcfe734e4b3132035cef Mon Sep 17 00:00:00 2001 From: Christian Kittel Date: Fri, 3 Nov 2023 19:35:19 +0100 Subject: [PATCH 041/146] Adjust handling empty values (#15760) Signed-off-by: Christian Kittel --- .../converter/type/AbstractTypeConverter.java | 6 ++--- .../internal/converter/BaseConverterTest.java | 1 + .../converter/ConvertFromBindingTest.java | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/converter/type/AbstractTypeConverter.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/converter/type/AbstractTypeConverter.java index 288eee826c1ee..2036d023766cc 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/converter/type/AbstractTypeConverter.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/converter/type/AbstractTypeConverter.java @@ -115,9 +115,9 @@ public T convertFromBinding(HmDatapoint dp) throws ConverterException { if (dp.getValue() == null) { return (T) UnDefType.NULL; } else if (!fromBindingValidation(dp)) { - String errorMessage = String.format("Can't convert %s value '%s' with %s for '%s'", dp.getType(), - dp.getValue(), this.getClass().getSimpleName(), new HmDatapointInfo(dp)); - throw new ConverterTypeException(errorMessage); + logger.debug("Can't convert {} value '{}' with {} for '{}'", dp.getType(), dp.getValue(), + this.getClass().getSimpleName(), new HmDatapointInfo(dp)); + return (T) UnDefType.NULL; } return fromBinding(dp); diff --git a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/BaseConverterTest.java b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/BaseConverterTest.java index 4ca3e53469816..5b1d54f5f51cf 100644 --- a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/BaseConverterTest.java +++ b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/BaseConverterTest.java @@ -39,5 +39,6 @@ public void setup() { HmChannel stubChannel = new HmChannel("stubChannel", 0); stubChannel.setDevice(new HmDevice("LEQ123456", HmInterface.RF, "HM-STUB-DEVICE", "", "", "")); floatDp.setChannel(stubChannel); + integerDp.setChannel(stubChannel); } } diff --git a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/ConvertFromBindingTest.java b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/ConvertFromBindingTest.java index 4b7e8ef68e2f1..2dfc759f9a64e 100644 --- a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/ConvertFromBindingTest.java +++ b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/converter/ConvertFromBindingTest.java @@ -18,11 +18,13 @@ import org.junit.jupiter.api.Test; import org.openhab.binding.homematic.internal.model.HmDatapoint; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.Units; import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; /** * Tests for @@ -99,4 +101,29 @@ public void testQuantityTypeConverter() throws ConverterException { assertThat(((QuantityType) convertedState).getUnit(), is(Units.PERCENT)); assertThat(((QuantityType) convertedState).toUnit(Units.ONE).doubleValue(), is(0.7)); } + + @Test + public void testPercentTypeConverter() throws ConverterException { + State convertedState; + TypeConverter percentTypeConverter = ConverterFactory.createConverter("Dimmer"); + + // the binding is backwards compatible, so clients may still use DecimalType, even if a unit is used + integerDp.setUnit("%"); + + integerDp.setValue(99.9); + integerDp.setMaxValue(100); + convertedState = percentTypeConverter.convertFromBinding(integerDp); + assertThat(convertedState, instanceOf(PercentType.class)); + assertThat(((PercentType) convertedState).doubleValue(), is(99.0)); + + integerDp.setValue(77.77777778); + convertedState = percentTypeConverter.convertFromBinding(integerDp); + assertThat(convertedState, instanceOf(PercentType.class)); + assertThat(((PercentType) convertedState).doubleValue(), is(77.0)); + + integerDp.setValue(""); + convertedState = percentTypeConverter.convertFromBinding(integerDp); + assertThat(convertedState, instanceOf(UnDefType.class)); + assertThat(((UnDefType) convertedState), is(UnDefType.NULL)); + } } From 020de9fb1887c7397cac2469caf44c4e254447a6 Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Fri, 3 Nov 2023 12:45:48 -0700 Subject: [PATCH 042/146] [androiddebugbridge] Reconnect on max timeouts and improve volume channel (#15788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [androiddebugbridge] Reconnect on max timeouts and improve volume channel --------- Signed-off-by: Miguel Álvarez --- .../README.md | 53 ++++++++++--------- .../AndroidDebugBridgeConfiguration.java | 8 +++ .../internal/AndroidDebugBridgeDevice.java | 46 ++++++++-------- .../internal/AndroidDebugBridgeHandler.java | 42 +++++++++++++-- .../AndroidDebugBridgeHandlerFactory.java | 1 + .../OH-INF/i18n/androiddebugbridge.properties | 4 ++ .../resources/OH-INF/thing/thing-types.xml | 14 ++++- 7 files changed, 113 insertions(+), 55 deletions(-) diff --git a/bundles/org.openhab.binding.androiddebugbridge/README.md b/bundles/org.openhab.binding.androiddebugbridge/README.md index 860fcfab21c89..c7cb11b203357 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/README.md +++ b/bundles/org.openhab.binding.androiddebugbridge/README.md @@ -10,11 +10,12 @@ If you are not familiar with adb I suggest you to search "How to enable adb over This binding was tested on : -| Device | Android version | Comments | -|--------------------|-----------------|----------------------------| -| Fire TV Stick | 7.1.2 | Volume control not working | -| Nexus5x | 8.1.0 | Everything works nice | -| Freebox Pop Player | 9 | Everything works nice | +| Device | Android version | Comments | +|------------------------|-----------------|------------------------------------| +| Fire TV Stick | 7.1.2 | Volume control not working | +| Nexus5x | 8.1.0 | Everything works nice | +| Freebox Pop Player | 9 | Everything works nice | +| ChromeCast Google TV | 12 | Volume control partially working | Please update this document if you tested it with other android versions to reflect the compatibility of the binding. @@ -30,29 +31,31 @@ You could customize the discovery process through the binding options. ## Binding Configuration -| Config | Type | description | -|----------|----------|------------------------------| -| discoveryPort | int | Port used on discovery to connect to the device through adb | -| discoveryReachableMs | int | Milliseconds to wait while discovering to determine if the ip is reachable | -| discoveryIpRangeMin | int | Used to limit the number of IPs checked while discovering | -| discoveryIpRangeMax | int | Used to limit the number of IPs checked while discovering | +| Config | Type | description | +|---------------------|----------|-----------------------------------------------------------------------------------| +| discoveryPort | int | Port used on discovery to connect to the device through adb | +| discoveryReachableMs| int | Milliseconds to wait while discovering to determine if the ip is reachable | +| discoveryIpRangeMin | int | Used to limit the number of IPs checked while discovering | +| discoveryIpRangeMax | int | Used to limit the number of IPs checked while discovering | ## Thing Configuration -| ThingTypeID | description | -|----------|------------------------------| -| android | Android device | - -| Config | Type | description | -|----------|----------|------------------------------| -| ip | String | Device ip address | -| port | int | Device port listening to adb connections (default: 5555) | -| refreshTime | int | Seconds between device status refreshes (default: 30) | -| timeout | int | Command timeout in seconds (default: 5) | -| recordDuration | int | Record input duration in seconds | -| deviceMaxVolume | int | Assumed max volume for devices with android versions that do not expose this value. | -| volumeSettingKey | String | Settings key for android versions where volume is gather using settings command (>=android 11). | -| mediaStateJSONConfig | String | Expects a JSON array. Allow to configure the media state detection method per app. Described in the following section | +| ThingTypeID | Description | +|---------------|-------------------------| +| android | Android device | + +| Config | Type | Description | +|----------------------|--------|------------------------------------------------------------------------------------------------------------------------| +| ip | String | Device ip address. | +| port | int | Device port listening to adb connections. (default: 5555) | +| refreshTime | int | Seconds between device status refreshes. (default: 30) | +| timeout | int | Command timeout in seconds. (default: 5) | +| recordDuration | int | Record input duration in seconds. | +| deviceMaxVolume | int | Assumed max volume for devices with android versions that do not expose this value. | +| volumeSettingKey | String | Settings key for android versions where volume is gather using settings command. (>=android 11) | +| volumeStepPercent | int | Percent to increase/decrease volume. | +| mediaStateJSONConfig | String | Expects a JSON array. Allow to configure the media state detection method per app. Described in the following section. | +| maxADBTimeouts | int | Max ADB command consecutive timeouts to force to reset the connection. | ## Media State Detection diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeConfiguration.java b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeConfiguration.java index acd8a776007f3..9e64d7a6d71cb 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeConfiguration.java +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeConfiguration.java @@ -42,10 +42,18 @@ public class AndroidDebugBridgeConfiguration { * Record input duration in seconds. */ public int recordDuration = 5; + /** + * Percent to increase/decrease volume. + */ + public int volumeStepPercent = 15; /** * Assumed max volume for devices with android versions that do not expose this value (>=android 11). */ public int deviceMaxVolume = 25; + /** + * Max ADB command consecutive timeouts to force to reset the connection. (0 for disabled) + */ + public int maxADBTimeouts; /** * Settings key for android versions where volume is gather using settings command (>=android 11). */ diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java index 8765072b52414..c00c93b4c4695 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java @@ -13,14 +13,14 @@ package org.openhab.binding.androiddebugbridge.internal; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URI; import java.net.URLEncoder; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; @@ -56,8 +56,8 @@ */ @NonNullByDefault public class AndroidDebugBridgeDevice { + private static final Path ADB_FOLDER = Path.of(OpenHAB.getUserDataFolder(), ".adb"); public static final int ANDROID_MEDIA_STREAM = 3; - private static final String ADB_FOLDER = OpenHAB.getUserDataFolder() + File.separator + ".adb"; private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeDevice.class); private static final Pattern VOLUME_PATTERN = Pattern .compile("volume is (?\\d.*) in range \\[(?\\d.*)\\.\\.(?\\d.*)]"); @@ -76,20 +76,6 @@ public class AndroidDebugBridgeDevice { private static @Nullable AdbCrypto adbCrypto; - static { - var logger = LoggerFactory.getLogger(AndroidDebugBridgeDevice.class); - try { - File directory = new File(ADB_FOLDER); - if (!directory.exists()) { - directory.mkdir(); - } - adbCrypto = loadKeyPair(ADB_FOLDER + File.separator + "adb_pub.key", - ADB_FOLDER + File.separator + "adb.key"); - } catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) { - logger.warn("Unable to setup adb keys: {}", e.getMessage()); - } - } - private final ScheduledExecutorService scheduler; private final ReentrantLock commandLock = new ReentrantLock(); @@ -793,20 +779,30 @@ private String runAdbShell(int commandTimeout, String... args) } } + public static void initADB() { + Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeDevice.class); + try { + if (!Files.exists(ADB_FOLDER) || !Files.isDirectory(ADB_FOLDER)) { + Files.createDirectory(ADB_FOLDER); + logger.info("Binding folder {} created", ADB_FOLDER); + } + adbCrypto = loadKeyPair(ADB_FOLDER.resolve("adb_pub.key"), ADB_FOLDER.resolve("adb.key")); + } catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) { + logger.warn("Unable to setup adb keys: {}", e.getMessage()); + } + } + private static AdbBase64 getBase64Impl() { - Charset asciiCharset = Charset.forName("ASCII"); - return bytes -> new String(Base64.getEncoder().encode(bytes), asciiCharset); + return bytes -> new String(Base64.getEncoder().encode(bytes), StandardCharsets.US_ASCII); } - private static AdbCrypto loadKeyPair(String pubKeyFile, String privKeyFile) + private static AdbCrypto loadKeyPair(Path pubKey, Path privKey) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { - File pub = new File(pubKeyFile); - File priv = new File(privKeyFile); AdbCrypto c = null; // load key pair - if (pub.exists() && priv.exists()) { + if (Files.exists(pubKey) && Files.exists(privKey)) { try { - c = AdbCrypto.loadAdbKeyPair(getBase64Impl(), priv, pub); + c = AdbCrypto.loadAdbKeyPair(getBase64Impl(), privKey.toFile(), pubKey.toFile()); } catch (IOException ignored) { // Keys don't exits } @@ -814,7 +810,7 @@ private static AdbCrypto loadKeyPair(String pubKeyFile, String privKeyFile) if (c == null) { // generate key pair c = AdbCrypto.generateAdbKeyPair(getBase64Impl()); - c.saveAdbKeyPair(priv, pub); + c.saveAdbKeyPair(privKey.toFile(), pubKey.toFile()); } return c; } diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java index 69ac4cb5a1bfc..ceec6ca78c3f1 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java @@ -27,6 +27,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.NextPreviousType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; @@ -74,6 +75,7 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler { private @Nullable ScheduledFuture connectionCheckerSchedule; private AndroidDebugBridgeMediaStatePackageConfig @Nullable [] packageConfigs = null; private boolean deviceAwake = false; + private int consecutiveTimeouts = 0; public AndroidDebugBridgeHandler(Thing thing, AndroidDebugBridgeDynamicCommandDescriptionProvider commandDescriptionProvider) { @@ -101,6 +103,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.warn("{} - read error: {}", currentConfig.ip, e.getMessage()); } catch (TimeoutException e) { logger.warn("{} - timeout error", currentConfig.ip); + disconnectOnMaxADBTimeouts(); } } @@ -196,6 +199,7 @@ private void handleCommandInternal(ChannelUID channelUID, Command command) } break; } + consecutiveTimeouts = 0; } private void recordDeviceInput(Command recordNameCommand) @@ -236,6 +240,16 @@ private void handleMediaVolume(ChannelUID channelUID, Command command) var volumeInfo = adbConnection.getMediaVolume(); maxMediaVolume = volumeInfo.max; updateState(channelUID, new PercentType((int) Math.round(toPercent(volumeInfo.current, volumeInfo.max)))); + } else if (command instanceof IncreaseDecreaseType) { + var volumeInfo = adbConnection.getMediaVolume(); + var volumeStep = fromPercent(config.volumeStepPercent, volumeInfo.max); + logger.debug("Device {} volume step: {}", getThing().getUID(), volumeStep); + var targetVolume = (int) Math + .round(IncreaseDecreaseType.INCREASE.equals(command) ? volumeInfo.current + volumeStep + : volumeInfo.current - volumeStep); + var newVolume = Integer.max(0, Integer.min(targetVolume, volumeInfo.max)); + logger.debug("Device {} new volume : {}", getThing().getUID(), newVolume); + adbConnection.setMediaVolume(newVolume); } else { if (maxMediaVolume == 0) { return; // We can not transform percentage @@ -250,8 +264,8 @@ private double toPercent(double value, double maxValue) { return (value / maxValue) * 100; } - private double fromPercent(double value, double maxValue) { - return (value / 100) * maxValue; + private double fromPercent(double percent, double maxValue) { + return (percent / 100) * maxValue; } private void handleMediaControlCommand(ChannelUID channelUID, Command command) @@ -398,8 +412,16 @@ private void refreshProperties() throws InterruptedException, AndroidDebugBridge // Add some information about the device try { Map editProperties = editProperties(); - editProperties.put(Thing.PROPERTY_SERIAL_NUMBER, adbConnection.getSerialNo()); - editProperties.put(Thing.PROPERTY_MODEL_ID, adbConnection.getModel()); + try { + editProperties.put(Thing.PROPERTY_SERIAL_NUMBER, adbConnection.getSerialNo()); + } catch (AndroidDebugBridgeDeviceReadException ignored) { + // Allow devices without serial number. + } + try { + editProperties.put(Thing.PROPERTY_MODEL_ID, adbConnection.getModel()); + } catch (AndroidDebugBridgeDeviceReadException ignored) { + // Allow devices without model id. + } var androidVersion = adbConnection.getAndroidVersion(); editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, androidVersion); // refresh android version to use @@ -426,8 +448,10 @@ private void refreshStatus() throws InterruptedException, AndroidDebugBridgeDevi } catch (TimeoutException e) { // happen a lot when device is sleeping; abort refresh other channels logger.debug("Unable to refresh awake state: Timeout; aborting channels refresh"); + disconnectOnMaxADBTimeouts(); return; } + consecutiveTimeouts = 0; var awakeStateChannelUID = new ChannelUID(this.thing.getUID(), AWAKE_STATE_CHANNEL); if (isLinked(awakeStateChannelUID)) { updateState(awakeStateChannelUID, OnOffType.from(awakeState)); @@ -474,6 +498,16 @@ private void refreshStatus() throws InterruptedException, AndroidDebugBridgeDevi } } + private void disconnectOnMaxADBTimeouts() { + consecutiveTimeouts++; + if (config.maxADBTimeouts > 0 && consecutiveTimeouts >= config.maxADBTimeouts) { + logger.debug("Max consecutive timeouts reached, aborting connection"); + adbConnection.disconnect(); + checkConnection(); + consecutiveTimeouts = 0; + } + } + static class AndroidDebugBridgeMediaStatePackageConfig { public String name = ""; public @Nullable String label; diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandlerFactory.java b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandlerFactory.java index 2dbe5e5bf3cf6..bc2c02b1c114f 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandlerFactory.java +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandlerFactory.java @@ -41,6 +41,7 @@ public class AndroidDebugBridgeHandlerFactory extends BaseThingHandlerFactory { public AndroidDebugBridgeHandlerFactory( final @Reference AndroidDebugBridgeDynamicCommandDescriptionProvider commandDescriptionProvider) { this.commandDescriptionProvider = commandDescriptionProvider; + AndroidDebugBridgeDevice.initADB(); } @Override diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge.properties b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge.properties index 4d5386d08188c..49b922c42d860 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge.properties +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge.properties @@ -25,6 +25,8 @@ thing-type.config.androiddebugbridge.android.deviceMaxVolume.label = Device Max thing-type.config.androiddebugbridge.android.deviceMaxVolume.description = Assumed max volume for devices with android versions that do not expose this value (>=android 11). thing-type.config.androiddebugbridge.android.ip.label = IP Address thing-type.config.androiddebugbridge.android.ip.description = Device ip address. +thing-type.config.androiddebugbridge.android.maxADBTimeouts.label = Max ADB Timeouts +thing-type.config.androiddebugbridge.android.maxADBTimeouts.description = Max ADB command consecutive timeouts to force to reset the connection. (0 for disabled) thing-type.config.androiddebugbridge.android.mediaStateJSONConfig.label = Media State Config thing-type.config.androiddebugbridge.android.mediaStateJSONConfig.description = JSON config that allows to modify the media state detection strategy for each app. Refer to the binding documentation. thing-type.config.androiddebugbridge.android.port.label = Port @@ -45,6 +47,8 @@ thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_musi thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_music_headset = volume music headset thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_music_usb_headset = volume music usb headset thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_system = volume system +thing-type.config.androiddebugbridge.android.volumeStepPercent.label = Volume Step Percent +thing-type.config.androiddebugbridge.android.volumeStepPercent.description = Percent to increase/decrease volume. # channel types diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml index 4814599276e38..d05c289108a03 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml @@ -38,7 +38,7 @@ Device port listening to adb connections. 5555 - + Seconds between device status refreshes. 30 @@ -75,12 +75,24 @@ true + + + Percent to increase/decrease volume. + 15 + true + Assumed max volume for devices with android versions that do not expose this value (>=android 11). 25 true + + + Max ADB command consecutive timeouts to force to reset the connection. (0 for disabled) + 0 + true + From b4c1dbf05821ae538501928884faebc925beecb5 Mon Sep 17 00:00:00 2001 From: Simon Spielmann Date: Fri, 3 Nov 2023 20:48:39 +0100 Subject: [PATCH 043/146] [keba] Split channel ENABLED to USER and SYSTEM channels (#15531) * Split channel ENABLED to USER and SYSTEM channels --------- Signed-off-by: Simon Spielmann --- bundles/org.openhab.binding.keba/README.md | 9 ++++++--- .../keba/internal/KebaBindingConstants.java | 3 ++- .../internal/handler/KeContactHandler.java | 18 ++++++------------ .../resources/OH-INF/i18n/keba.properties | 6 ++++-- .../main/resources/OH-INF/thing/kecontact.xml | 19 +++++++++++++++---- .../OH-INF/update/kecontact-enabled.xml | 18 ++++++++++++++++++ 6 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 bundles/org.openhab.binding.keba/src/main/resources/OH-INF/update/kecontact-enabled.xml diff --git a/bundles/org.openhab.binding.keba/README.md b/bundles/org.openhab.binding.keba/README.md index 66ceb65677f52..1250d0246cba3 100644 --- a/bundles/org.openhab.binding.keba/README.md +++ b/bundles/org.openhab.binding.keba/README.md @@ -18,7 +18,8 @@ All devices support the following channels: | Channel ID | Item Type | Read-only | Description | | ----------------------- | ------------------------ | --------- | ----------------------------------------------------------------------- | | state | Number | yes | current operational state of the wallbox | -| enabled | Switch | no | activation state of the wallbox | +| enabledsystem | Switch | yes | activation state of the wallbox (System) | +| enableduser | Switch | no | activation state of the wallbox (User) | | maxpresetcurrent | Number:ElectricCurrent | no | maximum current the charging station should deliver to the EV in A | | maxpresetcurrentrange | Number:Dimensionless | no | maximum current the charging station should deliver to the EV in % | | power | Number:Power | yes | active power delivered by the charging station | @@ -63,7 +64,8 @@ Number:ElectricCurrent KebaCurrent "Maximum supply current [%.3f A] Number:ElectricCurrent KebaSystemCurrent "Maximum system supply current [%.3f A]" {channel="keba:kecontact:1:maxsystemcurrent"} Number:ElectricCurrent KebaFailSafeCurrent "Failsafe supply current [%.3f A]" {channel="keba:kecontact:1:failsafecurrent"} Number KebaState "Operating State [%s]" {channel="keba:kecontact:1:state"} -Switch KebaSwitch "Enabled" {channel="keba:kecontact:1:enabled"} +Switch KebaEnabledSystem "Enabled (System)" {channel="keba:kecontact:1:enabledsystem"} +Switch KebaEnabledUser "Enabled (User)" {channel="keba:kecontact:1:enableduser"} Switch KebaWallboxPlugged "Plugged into wallbox" {channel="keba:kecontact:1:wallbox"} Switch KebaVehiclePlugged "Plugged into vehicle" {channel="keba:kecontact:1:vehicle"} Switch KebaPlugLocked "Plug locked" {channel="keba:kecontact:1:locked"} @@ -90,7 +92,8 @@ sitemap demo label="Main Menu" Text label="Charging Station" { Text item=KebaState Text item=KebaUptime - Switch item=KebaSwitch + Switch item=KebaEnabledSystem + Switch item=KebaEnabledUser Switch item=KebaWallboxPlugged Switch item=KebaVehiclePlugged Switch item=KebaPlugLocked diff --git a/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/KebaBindingConstants.java b/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/KebaBindingConstants.java index 417225b934828..e722399f57934 100644 --- a/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/KebaBindingConstants.java +++ b/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/KebaBindingConstants.java @@ -41,7 +41,8 @@ public class KebaBindingConstants { public static final String CHANNEL_WALLBOX = "wallbox"; public static final String CHANNEL_VEHICLE = "vehicle"; public static final String CHANNEL_PLUG_LOCKED = "locked"; - public static final String CHANNEL_ENABLED = "enabled"; + public static final String CHANNEL_ENABLED_SYSTEM = "enabledsystem"; + public static final String CHANNEL_ENABLED_USER = "enableduser"; public static final String CHANNEL_PILOT_CURRENT = "maxpilotcurrent"; public static final String CHANNEL_PILOT_PWM = "maxpilotcurrentdutycyle"; public static final String CHANNEL_MAX_SYSTEM_CURRENT = "maxsystemcurrent"; diff --git a/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/handler/KeContactHandler.java b/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/handler/KeContactHandler.java index 49bb3335290a9..5e62eada3a3d0 100644 --- a/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/handler/KeContactHandler.java +++ b/bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/handler/KeContactHandler.java @@ -323,17 +323,11 @@ protected void onData(ByteBuffer byteBuffer) { break; } case "Enable sys": { - int state = entry.getValue().getAsInt(); - switch (state) { - case 1: { - updateState(CHANNEL_ENABLED, OnOffType.ON); - break; - } - default: { - updateState(CHANNEL_ENABLED, OnOffType.OFF); - break; - } - } + updateState(CHANNEL_ENABLED_SYSTEM, OnOffType.from(entry.getValue().getAsInt() == 1)); + break; + } + case "Enable user": { + updateState(CHANNEL_ENABLED_USER, OnOffType.from(entry.getValue().getAsInt() == 1)); break; } case "Curr HW": { @@ -558,7 +552,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; } - case CHANNEL_ENABLED: { + case CHANNEL_ENABLED_USER: { if (command instanceof OnOffType) { if (command == OnOffType.ON) { transceiver.send("ena 1", this); diff --git a/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/i18n/keba.properties b/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/i18n/keba.properties index a43c6673c1e09..a3b59b6d13c7e 100644 --- a/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/i18n/keba.properties +++ b/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/i18n/keba.properties @@ -35,8 +35,10 @@ channel-type.keba.current_settable.label = Preset Current channel-type.keba.current_settable.description = Preset Current channel-type.keba.display.label = Display channel-type.keba.display.description = Text to show on the P30 Series C or X display -channel-type.keba.enabled.label = Enabled -channel-type.keba.enabled.description = Activation state of the wallbox +channel-type.keba.enabledsystem.label = Enabled (System) +channel-type.keba.enabledsystem.description = Activation state of the wallbox (System) +channel-type.keba.enableduser.label = Enabled (User) +channel-type.keba.enableduser.description = Activation state of the wallbox (User) channel-type.keba.energy.label = Energy Session channel-type.keba.energy.description = Power consumption channel-type.keba.error1.label = Error Code 1 diff --git a/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/thing/kecontact.xml b/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/thing/kecontact.xml index cccbe07bc666a..c474648c7dc38 100644 --- a/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/thing/kecontact.xml +++ b/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/thing/kecontact.xml @@ -9,7 +9,8 @@ A KeContact EV Charging Station - + + @@ -57,6 +58,10 @@ + + 1 + + @@ -115,10 +120,16 @@ Indicator if the plug is locked by the electrical vehicle - + + Switch + + Activation state of the wallbox (System) + + + Switch - - Activation state of the wallbox + + Activation state of the wallbox (User) diff --git a/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/update/kecontact-enabled.xml b/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/update/kecontact-enabled.xml new file mode 100644 index 0000000000000..015e98f119446 --- /dev/null +++ b/bundles/org.openhab.binding.keba/src/main/resources/OH-INF/update/kecontact-enabled.xml @@ -0,0 +1,18 @@ + + + + + + + + keba:enabledsystem + + + keba:enableduser + + + + + From 3f989797055e44b34a2a558cd2c0be400c2d5d38 Mon Sep 17 00:00:00 2001 From: uqs Date: Fri, 3 Nov 2023 20:55:20 +0100 Subject: [PATCH 044/146] [astro] Update README.md (#15648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Help users detangle #start, #end and #event trigger channels and their respective offsets. I was struggling with this and it took reading https://community.openhab.org/t/solved-open-rollershutters-based-on-astro-binding-with-offset-does-not-work/123336 to figure out where I went wrong. While here, maybe fix the Markdown rendering of QuantityType Signed-off-by: Ulrich Spörlein --- bundles/org.openhab.binding.astro/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.astro/README.md b/bundles/org.openhab.binding.astro/README.md index c1559911694a9..e20ecf2c913ad 100644 --- a/bundles/org.openhab.binding.astro/README.md +++ b/bundles/org.openhab.binding.astro/README.md @@ -94,6 +94,8 @@ This is done by setting `useMeteorologicalSeason` to true in the advanced settin ### Trigger Channels +Only these can be used in rule triggers as shown below. Note that they have their own offset configurations that are independent from offsets configured on the start or end times of e.g. the `rise` or `set` channels. + - **thing** `sun` - **group** `rise, set, noon, night, morningNight, astroDawn, nauticDawn, civilDawn, astroDusk, nauticDusk, civilDusk, eveningNight, daylight` - **event** `START, END` @@ -245,14 +247,14 @@ Example : ### getElevation(timeStamp) -Retrieves the elevation (QuantityType) of the sun at the requested instant. +Retrieves the elevation (QuantityType\) of the sun at the requested instant. Thing method applies to Sun and Moon. - `timeStamp` (ZonedDateTime) - defaulted to now() if null. ### getAzimuth(timeStamp) -Retrieves the azimuth (QuantityType) of the sun at the requested instant. +Retrieves the azimuth (QuantityType\) of the sun at the requested instant. Thing method applies to Sun and Moon. - `timeStamp` (ZonedDateTime) - defaulted to now() if null. @@ -267,7 +269,7 @@ Example : ### getTotalRadiation(timeStamp) -Retrieves the total radiation (QuantityType) of the sun at the requested instant. +Retrieves the total radiation (QuantityType\) of the sun at the requested instant. Thing method only applies to Sun thing type. ```java From 2d5ce5a577a37693f93bdd440dcd982f1595c0ae Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 3 Nov 2023 21:29:39 +0100 Subject: [PATCH 045/146] [sonnen] Fix channel types, Energy should be Power (#15384) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also adapt the documentation and drop the batteryFeedIn in the documentation which is not part of the binding. Fix #15365 Signed-off-by: Tim Lochmüller --- bundles/org.openhab.binding.sonnen/README.md | 14 ++++++------- .../resources/OH-INF/thing/thing-types.xml | 14 ++++++------- .../resources/OH-INF/update/instructions.xml | 21 +++++++++++++++++++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/bundles/org.openhab.binding.sonnen/README.md b/bundles/org.openhab.binding.sonnen/README.md index 90b0156f96f39..0388a5cf1a540 100644 --- a/bundles/org.openhab.binding.sonnen/README.md +++ b/bundles/org.openhab.binding.sonnen/README.md @@ -23,15 +23,13 @@ The following channels are yet supported: | Channel | Type | Access | Description | | ------------------------------ | ------------- | ------ | --------------------------------------------------------------------------------------- | | batteryChargingState | Switch | read | Indicates if the Battery is charging at that moment | -| batteryCharging | Number:Energy | read | Indicates the actual current charging the Battery. Otherwise 0. | +| batteryCharging | Number:Power | read | Indicates the actual current charging the Battery. Otherwise 0. | | batteryDischargingState | Switch | read | Indicates if the Battery is discharging at that moment | -| batteryDischarging | Number:Energy | read | Indicates the actual current discharging the Battery. Otherwise 0. | -| batteryFeedIn | Number:Energy | read | Indicates the actual charging current of the Battery in watt | -| batteryDischarging | Number:Energy | read | Indicates the actual current discharging the Battery in watt | -| consumption | Number:Energy | read | Indicates the actual consumption of the consumer in watt | -| gridFeedIn | Number:Energy | read | Indicates the actual current feeding to the Grid in watt.0 if nothing is feeded | -| gridConsumption | Number:Energy | read | Indicates the actual current consumption from the Grid in watt.0 if nothing is received | -| solarProduction | Number:Energy | read | Indicates the actual production of the Solar system in watt | +| batteryDischarging | Number:Power | read | Indicates the actual current discharging the Battery. Otherwise 0. | +| consumption | Number:Power | read | Indicates the actual consumption of the consumer in watt | +| gridFeedIn | Number:Power | read | Indicates the actual current feeding to the Grid in watt.0 if nothing is feeded | +| gridConsumption | Number:Power | read | Indicates the actual current consumption from the Grid in watt.0 if nothing is received | +| solarProduction | Number:Power | read | Indicates the actual production of the Solar system in watt | | batteryLevel | Number | read | Indicates the actual Battery Level in % from 0 - 100 | | flowConsumptionBatteryState | Switch | read | Indicates if there is a current flow from Battery towards Consumption | | flowConsumptionGridState | Switch | read | Indicates if there is a current flow from Grid towards Consumption | diff --git a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml index 705804c20508d..e456835919876 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/thing/thing-types.xml @@ -32,7 +32,7 @@ sonnen - 1 + 2 @@ -71,37 +71,37 @@ - Number:Energy + Number:Power Indicates the actual current charging the Battery. Otherwise 0. - Number:Energy + Number:Power Indicates the actual current discharging the Battery. Otherwise 0. - Number:Energy + Number:Power Indicates the actual consumption of the House. - Number:Energy + Number:Power Indicates the actual current feeding to the Grid. Otherwise 0. - Number:Energy + Number:Power Indicates the actual current consumption from the the Grid. Otherwise 0. - Number:Energy + Number:Power Indicates the actual production of the Solar system. diff --git a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml index 61699a648f5e8..2412b9a02bbe7 100644 --- a/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml +++ b/bundles/org.openhab.binding.sonnen/src/main/resources/OH-INF/update/instructions.xml @@ -19,5 +19,26 @@ sonnen:energyExportedStateConsumption + + + + sonnen:batteryCharging + + + sonnen:batteryDischarging + + + sonnen:consumption + + + sonnen:gridFeedIn + + + sonnen:gridConsumption + + + sonnen:solarProduction + + From 19cfbcf67a660556c63c8bedd71bc8cb14d0b0f3 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Fri, 3 Nov 2023 22:05:31 +0100 Subject: [PATCH 046/146] gson fields alias (#15829) Signed-off-by: Leo Siepel --- .../api/dto/MessageRecognizerLoopRecordBegin.java | 5 ++++- .../internal/api/dto/MessageRecognizerLoopRecordEnd.java | 5 ++++- .../internal/api/dto/MessageRecognizerLoopUtterance.java | 7 +++++-- .../binding/mycroft/internal/api/dto/MessageSpeak.java | 8 ++++++-- .../mycroft/internal/api/dto/MessageVolumeDecrease.java | 5 ++++- .../mycroft/internal/api/dto/MessageVolumeIncrease.java | 5 ++++- .../mycroft/internal/api/dto/MessageVolumeMute.java | 5 ++++- .../mycroft/internal/api/dto/MessageVolumeUnmute.java | 5 ++++- 8 files changed, 35 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordBegin.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordBegin.java index 44386f267b35f..a1cadc4dddecf 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordBegin.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordBegin.java @@ -14,6 +14,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message informs the bus clients that Mycroft * is actively listening and trying to do STT. @@ -29,7 +31,8 @@ public MessageRecognizerLoopRecordBegin() { } public static class Context { - public String client_name = ""; + @SerializedName("client_name") + public String clientName = ""; public String source = ""; public String destination = ""; } diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordEnd.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordEnd.java index c6998b8dae429..3da0b2aefa462 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordEnd.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopRecordEnd.java @@ -14,6 +14,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message informs the bus clients that Mycroft * finished listening to the mic. @@ -29,7 +31,8 @@ public MessageRecognizerLoopRecordEnd() { } public static class Context { - public String client_name = ""; + @SerializedName("client_name") + public String clientName = ""; public String source = ""; public String destination = ""; } diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopUtterance.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopUtterance.java index b6d6ab4939db7..243e7c59f0f39 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopUtterance.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageRecognizerLoopUtterance.java @@ -17,6 +17,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message is sent to the skills * module to trigger an intent from a text. @@ -36,7 +38,7 @@ public MessageRecognizerLoopUtterance() { public MessageRecognizerLoopUtterance(String utterance) { this(); this.data.utterances.add(utterance); - this.context.client_name = "java_api"; + this.context.clientName = "java_api"; this.context.source = "audio"; this.context.destination.add("skills"); } @@ -46,7 +48,8 @@ public static class Data { } public static class Context { - public String client_name = ""; + @SerializedName("client_name") + public String clientName = ""; public String source = ""; public List destination = new ArrayList<>(); } diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageSpeak.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageSpeak.java index b2ceb9bcfe9e0..c6335ccf578cc 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageSpeak.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageSpeak.java @@ -17,6 +17,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message is sent to the Mycroft audio module * to trigger a TTS action. @@ -41,11 +43,13 @@ public MessageSpeak(String textToSay) { public static class Data { public String utterance = ""; - public String expect_response = ""; + @SerializedName("expect_response") + public String expectResponse = ""; }; public static class Context { - public String client_name = ""; + @SerializedName("client_name") + public String clientName = ""; public List source = new ArrayList<>(); public String destination = ""; } diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeDecrease.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeDecrease.java index 2f1e2c8946752..e46fedb1b01d1 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeDecrease.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeDecrease.java @@ -14,6 +14,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message asks Mycroft to decrease the volume by 10% * @@ -28,6 +30,7 @@ public MessageVolumeDecrease() { } public static class Data { - public Boolean play_sound = true; + @SerializedName("play_sound") + public Boolean playSound = true; } } diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeIncrease.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeIncrease.java index bd95c180caa7a..5b38d7852aa46 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeIncrease.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeIncrease.java @@ -14,6 +14,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message asks Mycroft to increase the volume by 10% * @@ -28,6 +30,7 @@ public MessageVolumeIncrease() { } public static class Data { - public Boolean play_sound = true; + @SerializedName("play_sound") + public Boolean playSound = true; } } diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeMute.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeMute.java index 4c2ab434af01c..7013f85346943 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeMute.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeMute.java @@ -14,6 +14,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message asks Mycroft to mute the volume * @@ -28,6 +30,7 @@ public MessageVolumeMute() { } public static class Data { - public Boolean speak_message = false; + @SerializedName("speak_messsage") + public Boolean speakMesssage = false; } } diff --git a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeUnmute.java b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeUnmute.java index 91602e87256d2..7334cc6c9a7b4 100644 --- a/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeUnmute.java +++ b/bundles/org.openhab.binding.mycroft/src/main/java/org/openhab/binding/mycroft/internal/api/dto/MessageVolumeUnmute.java @@ -14,6 +14,8 @@ import org.openhab.binding.mycroft.internal.api.MessageType; +import com.google.gson.annotations.SerializedName; + /** * This message asks Mycroft to unmute the volume * @@ -28,6 +30,7 @@ public MessageVolumeUnmute() { } public static class Data { - public Boolean speak_message = false; + @SerializedName("speak_messsage") + public Boolean speakMessage = false; } } From d92d97cfda0a0e1e5817f1539b1ae410c2ac7e38 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Fri, 3 Nov 2023 22:51:23 +0100 Subject: [PATCH 047/146] Warning and codeanalysis (#15828) Signed-off-by: Leo Siepel --- .../org/openhab/binding/bluetooth/BluetoothUtils.java | 10 ++++------ .../discovery/internal/BluetoothDeviceSnapshot.java | 1 - .../discovery/internal/BluetoothDiscoveryService.java | 4 +++- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java index 2b75d337626b1..33c9e8b32783e 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java @@ -28,7 +28,7 @@ @NonNullByDefault public class BluetoothUtils { - public static final Logger logger = LoggerFactory.getLogger(BluetoothUtils.class); + public static final Logger LOGGER = LoggerFactory.getLogger(BluetoothUtils.class); public static final int FORMAT_UINT8 = 0x11; public static final int FORMAT_UINT16 = 0x12; @@ -47,9 +47,7 @@ public class BluetoothUtils { */ public static int[] toIntArray(byte[] value) { int[] ret = new int[value.length]; - for (int i = 0; i < value.length; i++) { - ret[i] = value[i]; - } + System.arraycopy(value, 0, ret, 0, value.length); return ret; } @@ -90,7 +88,7 @@ public static byte[] toByteArray(int[] value) { return unsignedToSigned( unsignedBytesToInt(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]), 32); default: - logger.error("Unknown format type {} - no int value can be provided for it.", formatType); + LOGGER.error("Unknown format type {} - no int value can be provided for it.", formatType); } return null; @@ -111,7 +109,7 @@ public static byte[] toByteArray(int[] value) { case FORMAT_FLOAT: return bytesToFloat(value[offset], value[offset + 1], value[offset + 2], value[offset + 3]); default: - logger.error("Unknown format type {} - no float value can be provided for it.", formatType); + LOGGER.error("Unknown format type {} - no float value can be provided for it.", formatType); } return null; diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDeviceSnapshot.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDeviceSnapshot.java index b8537c4f64537..915611d24330c 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDeviceSnapshot.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDeviceSnapshot.java @@ -165,7 +165,6 @@ public int hashCode() { return result; } - @SuppressWarnings("PMD.SimplifyBooleanReturns") @Override public boolean equals(@Nullable Object obj) { if (this == obj) { diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java index 129873f0e75fc..85c530b33d5dd 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java @@ -153,7 +153,9 @@ public void deviceDiscovered(BluetoothDevice device) { logger.debug("Discovered bluetooth device '{}': {}", device.getName(), device); DiscoveryCache cache = discoveryCaches.computeIfAbsent(device.getAddress(), addr -> new DiscoveryCache()); - cache.handleDiscovery(device); + if (cache != null) { + cache.handleDiscovery(device); + } } private static ThingUID createThingUIDWithBridge(DiscoveryResult result, BluetoothAdapter adapter) { From 15b1dec56f709eba40464ff552679f314544129d Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Fri, 3 Nov 2023 23:16:41 +0100 Subject: [PATCH 048/146] [knx] Fix typos in code comments (#15839) Signed-off-by: Holger Friedrich --- .../binding/knx/internal/client/DeviceInspector.java | 2 +- .../org/openhab/binding/knx/internal/client/KNXClient.java | 2 +- .../openhab/binding/knx/internal/client/SerialClient.java | 6 +++--- .../binding/knx/internal/handler/IPBridgeThingHandler.java | 2 +- .../binding/knx/internal/i18n/KNXTranslationProvider.java | 2 +- .../knx/internal/i18n/KNXTranslationProviderTest.java | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/DeviceInspector.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/DeviceInspector.java index 75b66ebc8444e..0c9ea4b3c5dc2 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/DeviceInspector.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/DeviceInspector.java @@ -284,7 +284,7 @@ private Map readDeviceProperties(IndividualAddress address) thro /** * @implNote {@link readDeviceDescription(address)} tries to read device description from the KNX device. - * According to KNX specification, eihter device descriptor DD0 or DD2 must be implemented. + * According to KNX specification, either device descriptor DD0 or DD2 must be implemented. * Currently only data from DD0 is returned; DD2 is just logged in debug mode. * * @param address Individual address of KNX device diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/KNXClient.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/KNXClient.java index c54991c902a31..22dc1c62b9d38 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/KNXClient.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/KNXClient.java @@ -48,7 +48,7 @@ public interface KNXClient { /** * Get the {@link DeviceInfoClient} which allows further device inspection. * - * @return the device infor client + * @return the device info client * @throws IllegalStateException in case the client is not connected */ DeviceInfoClient getDeviceInfoClient(); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java index bf1db21b64825..686d87cfb0c98 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java @@ -63,7 +63,7 @@ public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTime } /** - * try autodetection of cEMI devices via the PEI identification frame + * try automatic detection of cEMI devices via the PEI identification frame * * @implNote This is based on an vendor specific extension and may not work for other devices. */ @@ -92,7 +92,7 @@ protected boolean detectCemi() throws InterruptedException { // content[1..2] physical address // content[3..8] serial no // - // Weinzierl adds 2 extra bytes, 0x0004 for capablity cEMI, + // Weinzierl adds 2 extra bytes, 0x0004 for capability cEMI, // see "Weinzierl KNX BAOS Starter Kit, User Guide" if (0 == content[9] && 4 == content[10]) { logger.debug("Detected device with cEMI support"); @@ -118,7 +118,7 @@ protected KNXNetworkLink establishConnection() throws KNXException, InterruptedE } logger.debug("Establishing connection to KNX bus through FT1.2 on serial port {}{}{}", serialPort, (useCemiL ? " using cEMI" : ""), ((useCemiL != useCemi) ? " (autodetected)" : "")); - // CEMI support by Calimero library, userful for newer serial devices like KNX RF sticks, kBerry, + // CEMI support by Calimero library, useful for newer serial devices like KNX RF sticks, kBerry, // etc.; default is still old EMI frame format if (useCemiL) { return KNXNetworkLinkFT12.newCemiLink(serialPort, new TPSettings()); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java index dcf6ad80fd78e..a181dada31281 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java @@ -62,7 +62,7 @@ public IPBridgeThingHandler(Bridge bridge, @Nullable NetworkAddressService netwo @Override public void initialize() { - // initialisation would take too long and show a warning during binding startup + // initialization would take too long and show a warning during binding startup // KNX secure is adding serious delay updateStatus(ThingStatus.UNKNOWN); initJob = scheduler.submit(this::initializeLater); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java index fa538bdca23f8..99ac92bde23ff 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java @@ -51,7 +51,7 @@ private KNXTranslationProvider() { * * @param text text to be translated, may contain placeholders \{n\} for the n-th optional argument of this function * @param arguments any optional arguments, will be inserted - * @return translated text with subsitutions if translationprovide is set and provides a translation, otherwise + * @return translated text with substitutions if translationProvider is set and provides a translation, otherwise * returns original text with substitutions */ public String get(final String text, @Nullable Object @Nullable... arguments) { diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java index 69b7069fe7fd5..3cab3cf1e3c89 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java @@ -40,7 +40,7 @@ class KNXTranslationProviderTest { @Test void testGetBeforeInit() { - // initial state, should not crash and preferrably return original strings (w. pattern substitution) + // initial state, should not crash and preferably return original strings (w. pattern substitution) assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN)); assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN, 5)); assertEquals(UNKNOWN_NULL, KNXTranslationProvider.I18N.get(UNKNOWN_PATTERN, null, null)); From b0b3cd2d5f49956da39383ab6d337e4f41c1f95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Sat, 4 Nov 2023 10:28:46 +0100 Subject: [PATCH 049/146] [Freeboxos] npe when updating inactive Player (#15813) * Correcting npe * Added status update --------- Signed-off-by: clinique --- .../internal/api/rest/PlayerManager.java | 29 ++++++++++---- .../internal/handler/ActivePlayerHandler.java | 39 ++++++++++++------- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java index 0326c1e0ff8b4..31256516c750d 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/PlayerManager.java @@ -197,21 +197,30 @@ private static record Channel(@SerializedName("bouquetId") long bouquetId, public static record TvContext(@Nullable Channel channel, @Nullable PlayerDetails player) { } - private final Map subPaths = new HashMap<>(); + private final Map subPaths = new HashMap<>(); public PlayerManager(FreeboxOsSession session) throws FreeboxException { super(session, LoginManager.Permission.PLAYER, PlayerResponse.class, session.getUriBuilder().path(THING_PLAYER)); + } + + private @Nullable String getSubPath(int id) throws FreeboxException { + String subPath = subPaths.get(id); + if (subPath != null) { + return subPath; + } getDevices().stream().filter(Player::apiAvailable).forEach(player -> { String baseUrl = player.baseUrl(); if (baseUrl != null) { subPaths.put(player.id, baseUrl); } }); + return subPaths.get(id); } - public Status getPlayerStatus(int id) throws FreeboxException { - return getSingle(StatusResponse.class, subPaths.get(id), STATUS_PATH); + public @Nullable Status getPlayerStatus(int id) throws FreeboxException { + String subPath = getSubPath(id); + return subPath != null ? getSingle(StatusResponse.class, subPath, STATUS_PATH) : null; } // The player API does not allow to directly request a given player like others api parts @@ -220,8 +229,9 @@ public Player getDevice(int id) throws FreeboxException { return getDevices().stream().filter(player -> player.id == id).findFirst().orElse(null); } - public Configuration getConfig(int id) throws FreeboxException { - return getSingle(ConfigurationResponse.class, subPaths.get(id), SYSTEM_PATH); + public @Nullable Configuration getConfig(int id) throws FreeboxException { + String subPath = getSubPath(id); + return subPath != null ? getSingle(ConfigurationResponse.class, subPath, SYSTEM_PATH) : null; } public void sendKey(String ip, String code, String key, boolean longPress, int count) { @@ -240,7 +250,12 @@ public void sendKey(String ip, String code, String key, boolean longPress, int c } } - public void reboot(int id) throws FreeboxException { - post(subPaths.get(id), SYSTEM_PATH, REBOOT_ACTION); + public boolean reboot(int id) throws FreeboxException { + String subPath = getSubPath(id); + if (subPath != null) { + post(subPath, SYSTEM_PATH, REBOOT_ACTION); + return true; + } + return false; } } diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ActivePlayerHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ActivePlayerHandler.java index 52c8063664735..63a02eb4b64dc 100644 --- a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ActivePlayerHandler.java +++ b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ActivePlayerHandler.java @@ -35,8 +35,8 @@ import org.slf4j.LoggerFactory; /** - * The {@link ActivePlayerHandler} is responsible for handling everything associated to Freebox Player with api - * capabilities. + * The {@link ActivePlayerHandler} is responsible for handling everything associated to Freebox Player + * with api capabilities. * * @author Gaël L'hopital - Initial contribution */ @@ -59,8 +59,10 @@ void initializeProperties(Map properties) throws FreeboxExceptio Player player = getManager(PlayerManager.class).getDevice(getClientId()); if (player.reachable()) { Configuration config = getManager(PlayerManager.class).getConfig(player.id()); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, config.serial()); - properties.put(Thing.PROPERTY_FIRMWARE_VERSION, config.firmwareVersion()); + if (config != null) { + properties.put(Thing.PROPERTY_SERIAL_NUMBER, config.serial()); + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, config.firmwareVersion()); + } } } @@ -68,15 +70,24 @@ void initializeProperties(Map properties) throws FreeboxExceptio protected void internalPoll() throws FreeboxException { super.internalPoll(); if (thing.getStatus().equals(ThingStatus.ONLINE)) { - Status status = getManager(PlayerManager.class).getPlayerStatus(getClientId()); - updateChannelString(PLAYER_STATUS, PLAYER_STATUS, status.powerState().name()); - ForegroundApp foreground = status.foregroundApp(); - if (foreground != null) { - updateChannelString(PLAYER_STATUS, PACKAGE, foreground._package()); + Player player = getManager(PlayerManager.class).getDevice(getClientId()); + updateStatus(player.reachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE); + if (player.reachable()) { + Status status = getManager(PlayerManager.class).getPlayerStatus(getClientId()); + if (status != null) { + updateChannelString(PLAYER_STATUS, PLAYER_STATUS, status.powerState().name()); + ForegroundApp foreground = status.foregroundApp(); + if (foreground != null) { + updateChannelString(PLAYER_STATUS, PACKAGE, foreground._package()); + } + } + Configuration config = getManager(PlayerManager.class).getConfig(getClientId()); + if (config != null) { + uptime = checkUptimeAndFirmware(config.uptimeVal(), uptime, config.firmwareVersion()); + } else { + uptime = 0; + } } - Configuration config = getManager(PlayerManager.class).getConfig(getClientId()); - - uptime = checkUptimeAndFirmware(config.uptimeVal(), uptime, config.firmwareVersion()); updateChannelQuantity(SYS_INFO, UPTIME, uptime, Units.SECOND); } } @@ -84,7 +95,9 @@ protected void internalPoll() throws FreeboxException { public void reboot() { processReboot(() -> { try { - getManager(PlayerManager.class).reboot(getClientId()); + if (!getManager(PlayerManager.class).reboot(getClientId())) { + logger.warn("Unable to reboot the player - probably not reachable"); + } } catch (FreeboxException e) { logger.warn("Error rebooting: {}", e.getMessage()); } From c1029f4a98e719a9357ddf52ebd6557d2bdcefe0 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Sat, 4 Nov 2023 10:29:43 +0100 Subject: [PATCH 050/146] null check (#15841) Signed-off-by: Leo Siepel --- .../discovery/internal/BluetoothDiscoveryService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java index 85c530b33d5dd..ccdf31874b725 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryService.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -152,10 +153,9 @@ public void deviceRemoved(BluetoothDevice device) { public void deviceDiscovered(BluetoothDevice device) { logger.debug("Discovered bluetooth device '{}': {}", device.getName(), device); - DiscoveryCache cache = discoveryCaches.computeIfAbsent(device.getAddress(), addr -> new DiscoveryCache()); - if (cache != null) { - cache.handleDiscovery(device); - } + DiscoveryCache cache = Objects + .requireNonNull(discoveryCaches.computeIfAbsent(device.getAddress(), addr -> new DiscoveryCache())); + cache.handleDiscovery(device); } private static ThingUID createThingUIDWithBridge(DiscoveryResult result, BluetoothAdapter adapter) { From 8ac6d37714bdb0addf9cc5723f2ae423dc369be9 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Sat, 4 Nov 2023 10:31:52 +0100 Subject: [PATCH 051/146] [doorbird] Warning and SAT cleanup (#15824) * Warning and SAT clenaup --------- Signed-off-by: Leo Siepel --- .../internal/action/DoorbirdActions.java | 6 ++++++ .../doorbird/internal/api/DoorbirdAPI.java | 2 +- .../internal/handler/DoorbellHandler.java | 17 ++++++++++++----- .../internal/listener/DoorbirdUdpListener.java | 3 ++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/action/DoorbirdActions.java b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/action/DoorbirdActions.java index 6e1a1decc5e4c..d335e194bdfcf 100644 --- a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/action/DoorbirdActions.java +++ b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/action/DoorbirdActions.java @@ -54,6 +54,7 @@ public void setThingHandler(@Nullable ThingHandler handler) { @RuleAction(label = "restart the Doorbird", description = "Restarts the Doorbird device.") public void restart() { logger.debug("Doorbird action 'restart' called"); + DoorbellHandler handler = this.handler; if (handler != null) { handler.actionRestart(); } else { @@ -68,6 +69,7 @@ public static void restart(ThingActions actions) { @RuleAction(label = "hangup a SIP call", description = "Hangup SIP call.") public void sipHangup() { logger.debug("Doorbird action 'sipHangup' called"); + DoorbellHandler handler = this.handler; if (handler != null) { handler.actionSIPHangup(); } else { @@ -82,6 +84,7 @@ public static void sipHangup(ThingActions actions) { @RuleAction(label = "get the ring time limit", description = "Get the value of RING_TIME_LIMIT.") public @ActionOutput(name = "getRingTimeLimit", type = "java.lang.String") String getRingTimeLimit() { logger.debug("Doorbird action 'getRingTimeLimit' called"); + DoorbellHandler handler = this.handler; if (handler != null) { return handler.actionGetRingTimeLimit(); } else { @@ -97,6 +100,7 @@ public static String getRingTimeLimit(ThingActions actions) { @RuleAction(label = "get the call time limit", description = "Get the value of CALL_TIME_LIMIT.") public @ActionOutput(name = "getCallTimeLimit", type = "java.lang.String") String getCallTimeLimit() { logger.debug("Doorbird action 'getCallTimeLimit' called"); + DoorbellHandler handler = this.handler; if (handler != null) { return handler.actionGetCallTimeLimit(); } else { @@ -112,6 +116,7 @@ public static String getCallTimeLimit(ThingActions actions) { @RuleAction(label = "get the last error code", description = "Get the value of LASTERRORCODE.") public @ActionOutput(name = "getLastErrorCode", type = "java.lang.String") String getLastErrorCode() { logger.debug("Doorbird action 'getLastErrorCode' called"); + DoorbellHandler handler = this.handler; if (handler != null) { return handler.actionGetLastErrorCode(); } else { @@ -127,6 +132,7 @@ public static String getLastErrorCode(ThingActions actions) { @RuleAction(label = "get the last error text", description = "Get the value of LASTERRORTEXT.") public @ActionOutput(name = "getLastErrorText", type = "java.lang.String") String getLastErrorText() { logger.debug("Doorbird action 'getLastErrorText' called"); + DoorbellHandler handler = this.handler; if (handler != null) { return handler.actionGetLastErrorText(); } else { diff --git a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/api/DoorbirdAPI.java b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/api/DoorbirdAPI.java index 51ba942577cda..94af5c4368625 100644 --- a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/api/DoorbirdAPI.java +++ b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/api/DoorbirdAPI.java @@ -54,6 +54,7 @@ public final class DoorbirdAPI { private static final Gson GSON = new Gson(); private final Logger logger = LoggerFactory.getLogger(DoorbirdAPI.class); + private static final int CHUNK_SIZE = 256; private @Nullable Authorization authorization; private @Nullable HttpClient httpClient; @@ -191,7 +192,6 @@ public void sendAudio(InputStream audioInputStream) { // It is crucial to send data in small chunks to not overload the doorbird // It means that we have to wait the appropriate amount of time between chunk to send // real time data, as if it were live spoken. - int CHUNK_SIZE = 256; int nbByteRead = -1; long nextChunkSendTimeStamp = 0; do { diff --git a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/handler/DoorbellHandler.java b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/handler/DoorbellHandler.java index e625fa8a5e400..45dadd0f78b08 100644 --- a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/handler/DoorbellHandler.java +++ b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/handler/DoorbellHandler.java @@ -365,9 +365,10 @@ private void startImageRefreshJob() { } private void stopImageRefreshJob() { + ScheduledFuture imageRefreshJob = this.imageRefreshJob; if (imageRefreshJob != null) { imageRefreshJob.cancel(true); - imageRefreshJob = null; + this.imageRefreshJob = null; logger.debug("Canceling image refresh job"); } } @@ -378,9 +379,11 @@ private void startUDPListenerJob() { } private void stopUDPListenerJob() { + ScheduledFuture listenerJob = this.listenerJob; if (listenerJob != null) { listenerJob.cancel(true); udpListener.shutdown(); + this.listenerJob = null; logger.debug("Canceling listener job"); } } @@ -390,19 +393,21 @@ private void startDoorbellOffJob() { if (offDelay == null) { return; } + ScheduledFuture doorbellOffJob = this.doorbellOffJob; if (doorbellOffJob != null) { doorbellOffJob.cancel(true); } - doorbellOffJob = scheduler.schedule(() -> { + this.doorbellOffJob = scheduler.schedule(() -> { logger.debug("Update channel 'doorbell' to OFF for thing {}", getThing().getUID()); triggerChannel(CHANNEL_DOORBELL, CommonTriggerEvents.RELEASED); }, offDelay, TimeUnit.SECONDS); } private void stopDoorbellOffJob() { + ScheduledFuture doorbellOffJob = this.doorbellOffJob; if (doorbellOffJob != null) { doorbellOffJob.cancel(true); - doorbellOffJob = null; + this.doorbellOffJob = null; logger.debug("Canceling doorbell off job"); } } @@ -412,19 +417,21 @@ private void startMotionOffJob() { if (offDelay == null) { return; } + ScheduledFuture motionOffJob = this.motionOffJob; if (motionOffJob != null) { motionOffJob.cancel(true); } - motionOffJob = scheduler.schedule(() -> { + this.motionOffJob = scheduler.schedule(() -> { logger.debug("Update channel 'motion' to OFF for thing {}", getThing().getUID()); updateState(CHANNEL_MOTION, OnOffType.OFF); }, offDelay, TimeUnit.SECONDS); } private void stopMotionOffJob() { + ScheduledFuture motionOffJob = this.motionOffJob; if (motionOffJob != null) { motionOffJob.cancel(true); - motionOffJob = null; + this.motionOffJob = null; logger.debug("Canceling motion off job"); } } diff --git a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/listener/DoorbirdUdpListener.java b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/listener/DoorbirdUdpListener.java index 271fe803af941..572938ed24806 100644 --- a/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/listener/DoorbirdUdpListener.java +++ b/bundles/org.openhab.binding.doorbird/src/main/java/org/openhab/binding/doorbird/internal/listener/DoorbirdUdpListener.java @@ -66,10 +66,11 @@ public void run() { } public void shutdown() { + DatagramSocket socket = this.socket; if (socket != null) { socket.close(); logger.debug("Listener closing listener socket"); - socket = null; + this.socket = null; } } From 57c6016393b47c5cdfd6b524fc54c4f4db487f42 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Sat, 4 Nov 2023 11:38:27 +0100 Subject: [PATCH 052/146] [plugwise] Prevent possible chomp bug (#15339) * Improve chomp * Adapt to core Stringutils * Improve minor null check Signed-off-by: Leo Siepel --- .../internal/PlugwiseMessageProcessor.java | 3 +- .../plugwise/internal/PlugwiseUtils.java | 16 -------- .../internal/config/PlugwiseRelayConfig.java | 9 +++-- .../internal/config/PlugwiseScanConfig.java | 8 ++-- .../internal/config/PlugwiseSenseConfig.java | 13 ++++--- .../plugwise/internal/PlugwiseUtilsTest.java | 38 ------------------- 6 files changed, 21 insertions(+), 66 deletions(-) delete mode 100644 bundles/org.openhab.binding.plugwise/src/test/java/org/openhab/binding/plugwise/internal/PlugwiseUtilsTest.java diff --git a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseMessageProcessor.java b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseMessageProcessor.java index 27fb280dc19f9..c348a6930f658 100644 --- a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseMessageProcessor.java +++ b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseMessageProcessor.java @@ -30,6 +30,7 @@ import org.openhab.core.io.transport.serial.SerialPort; import org.openhab.core.io.transport.serial.SerialPortEvent; import org.openhab.core.io.transport.serial.SerialPortEventListener; +import org.openhab.core.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,7 +94,7 @@ public PlugwiseMessageProcessor(PlugwiseCommunicationContext context) { */ private void parseAndQueue(ByteBuffer readBuffer) { String response = new String(readBuffer.array(), 0, readBuffer.limit()); - response = response.replace("\r", "").replace("\n", ""); + response = StringUtils.chomp(response); Matcher matcher = RESPONSE_PATTERN.matcher(response); diff --git a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseUtils.java b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseUtils.java index ceb722888a138..dfbdb872a02bf 100644 --- a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseUtils.java +++ b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/PlugwiseUtils.java @@ -99,22 +99,6 @@ public static void stopBackgroundThread(@Nullable Thread thread) { } } - public static String upperUnderscoreToLowerCamel(String text) { - final String delimiter = "_"; - StringBuilder upperCamelBuilder = new StringBuilder(text.length()); - for (String str : text.split(delimiter)) { - if (upperCamelBuilder.isEmpty() && !str.isEmpty()) { - upperCamelBuilder.append(str.substring(0, 1).toLowerCase()); - } else if (!str.isEmpty()) { - upperCamelBuilder.append(str.substring(0, 1).toUpperCase()); - } - if (str.length() > 1) { - upperCamelBuilder.append(str.substring(1).toLowerCase()); - } - } - return upperCamelBuilder.toString(); - } - public static boolean updateProperties(Map properties, InformationResponseMessage message) { boolean update = false; diff --git a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseRelayConfig.java b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseRelayConfig.java index 9477ad0a177bf..d6fde718a9d5c 100644 --- a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseRelayConfig.java +++ b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseRelayConfig.java @@ -12,13 +12,15 @@ */ package org.openhab.binding.plugwise.internal.config; -import static org.openhab.binding.plugwise.internal.PlugwiseUtils.*; import static org.openhab.binding.plugwise.internal.config.PlugwiseRelayConfig.PowerStateChanging.COMMAND_SWITCHING; import java.time.Duration; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.plugwise.internal.PlugwiseUtils; import org.openhab.binding.plugwise.internal.protocol.field.MACAddress; +import org.openhab.core.util.StringUtils; /** * The {@link PlugwiseRelayConfig} class represents the configuration for a Plugwise relay device (Circle, Circle+, @@ -36,7 +38,8 @@ public enum PowerStateChanging { } private String macAddress = ""; - private String powerStateChanging = upperUnderscoreToLowerCamel(COMMAND_SWITCHING.name()); + private String powerStateChanging = Objects + .requireNonNull(StringUtils.capitalizeByUnderscore(COMMAND_SWITCHING.name())); private boolean suppliesPower = false; private int measurementInterval = 60; // minutes private boolean temporarilyNotInNetwork = false; @@ -47,7 +50,7 @@ public MACAddress getMACAddress() { } public PowerStateChanging getPowerStateChanging() { - return PowerStateChanging.valueOf(lowerCamelToUpperUnderscore(powerStateChanging)); + return PowerStateChanging.valueOf(PlugwiseUtils.lowerCamelToUpperUnderscore(powerStateChanging)); } public boolean isSuppliesPower() { diff --git a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseScanConfig.java b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseScanConfig.java index fa06bdc616ea9..d9e2783204ba7 100644 --- a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseScanConfig.java +++ b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseScanConfig.java @@ -12,14 +12,16 @@ */ package org.openhab.binding.plugwise.internal.config; -import static org.openhab.binding.plugwise.internal.PlugwiseUtils.*; import static org.openhab.binding.plugwise.internal.protocol.field.Sensitivity.MEDIUM; import java.time.Duration; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.plugwise.internal.PlugwiseUtils; import org.openhab.binding.plugwise.internal.protocol.field.MACAddress; import org.openhab.binding.plugwise.internal.protocol.field.Sensitivity; +import org.openhab.core.util.StringUtils; /** * The {@link PlugwiseScanConfig} class represents the configuration for a Plugwise Scan. @@ -30,7 +32,7 @@ public class PlugwiseScanConfig { private String macAddress = ""; - private String sensitivity = upperUnderscoreToLowerCamel(MEDIUM.name()); + private String sensitivity = Objects.requireNonNull(StringUtils.capitalizeByUnderscore(MEDIUM.name())); private int switchOffDelay = 5; // minutes private boolean daylightOverride = false; private int wakeupInterval = 1440; // minutes (1 day) @@ -43,7 +45,7 @@ public MACAddress getMACAddress() { } public Sensitivity getSensitivity() { - return Sensitivity.valueOf(lowerCamelToUpperUnderscore(sensitivity)); + return Sensitivity.valueOf(PlugwiseUtils.lowerCamelToUpperUnderscore(sensitivity)); } public Duration getSwitchOffDelay() { diff --git a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseSenseConfig.java b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseSenseConfig.java index 342ac8f4692d3..acde2175ba67f 100644 --- a/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseSenseConfig.java +++ b/bundles/org.openhab.binding.plugwise/src/main/java/org/openhab/binding/plugwise/internal/config/PlugwiseSenseConfig.java @@ -12,18 +12,20 @@ */ package org.openhab.binding.plugwise.internal.config; -import static org.openhab.binding.plugwise.internal.PlugwiseUtils.*; import static org.openhab.binding.plugwise.internal.protocol.field.BoundaryAction.OFF_BELOW_ON_ABOVE; import static org.openhab.binding.plugwise.internal.protocol.field.BoundaryType.NONE; import java.time.Duration; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.plugwise.internal.PlugwiseUtils; import org.openhab.binding.plugwise.internal.protocol.field.BoundaryAction; import org.openhab.binding.plugwise.internal.protocol.field.BoundaryType; import org.openhab.binding.plugwise.internal.protocol.field.Humidity; import org.openhab.binding.plugwise.internal.protocol.field.MACAddress; import org.openhab.binding.plugwise.internal.protocol.field.Temperature; +import org.openhab.core.util.StringUtils; /** * The {@link PlugwiseScanConfig} class represents the configuration for a Plugwise Sense. @@ -35,8 +37,9 @@ public class PlugwiseSenseConfig { private String macAddress = ""; private int measurementInterval = 15; // minutes - private String boundaryType = upperUnderscoreToLowerCamel(NONE.name()); - private String boundaryAction = upperUnderscoreToLowerCamel(OFF_BELOW_ON_ABOVE.name()); + private String boundaryType = Objects.requireNonNull(StringUtils.capitalizeByUnderscore(NONE.name())); + private String boundaryAction = Objects + .requireNonNull(StringUtils.capitalizeByUnderscore(OFF_BELOW_ON_ABOVE.name())); private int temperatureBoundaryMin = 15; // degrees Celsius private int temperatureBoundaryMax = 25; // degrees Celsius private int humidityBoundaryMin = 45; // relative humidity (RH) @@ -54,11 +57,11 @@ public Duration getMeasurementInterval() { } public BoundaryType getBoundaryType() { - return BoundaryType.valueOf(lowerCamelToUpperUnderscore(boundaryType)); + return BoundaryType.valueOf(PlugwiseUtils.lowerCamelToUpperUnderscore(boundaryType)); } public BoundaryAction getBoundaryAction() { - return BoundaryAction.valueOf(lowerCamelToUpperUnderscore(boundaryAction)); + return BoundaryAction.valueOf(PlugwiseUtils.lowerCamelToUpperUnderscore(boundaryAction)); } public Temperature getTemperatureBoundaryMin() { diff --git a/bundles/org.openhab.binding.plugwise/src/test/java/org/openhab/binding/plugwise/internal/PlugwiseUtilsTest.java b/bundles/org.openhab.binding.plugwise/src/test/java/org/openhab/binding/plugwise/internal/PlugwiseUtilsTest.java deleted file mode 100644 index a72f84037acdb..0000000000000 --- a/bundles/org.openhab.binding.plugwise/src/test/java/org/openhab/binding/plugwise/internal/PlugwiseUtilsTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ - -package org.openhab.binding.plugwise.internal; - -import static org.junit.jupiter.api.Assertions.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.Test; - -/** - * The {@link PlugwiseUtilsTest} class tests some static string utility methods - * - * @author Leo Siepel - Initial contribution - */ -@NonNullByDefault -public class PlugwiseUtilsTest { - - @Test - public void upperUnderscoreToLowerCamelBaseTest() { - assertEquals("nodelimiterpresent", PlugwiseUtils.upperUnderscoreToLowerCamel("NoDelimiterPresent")); - assertEquals("delimiterIsPresent", PlugwiseUtils.upperUnderscoreToLowerCamel("DeliMiter_iS_PreSent")); - assertEquals("doubleDelimitersDouble", PlugwiseUtils.upperUnderscoreToLowerCamel("DOUBLE__DELIMITERS__DOUBLE")); - assertEquals("helloWorld", PlugwiseUtils.upperUnderscoreToLowerCamel("HELLO_WORLD")); - assertEquals("", PlugwiseUtils.upperUnderscoreToLowerCamel("")); - assertEquals("", PlugwiseUtils.upperUnderscoreToLowerCamel("_")); - } -} From 70b6d354aca79edccf8b33a2548fe239fc4b0c71 Mon Sep 17 00:00:00 2001 From: Christian Date: Sun, 5 Nov 2023 16:02:08 +0100 Subject: [PATCH 053/146] Update README.md (#15843) Fixed copy-paste error Signed-off-by: Christian --- bundles/org.openhab.binding.folderwatcher/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.folderwatcher/README.md b/bundles/org.openhab.binding.folderwatcher/README.md index 0ae5d8168bd3c..0fcb8a4d66f4b 100644 --- a/bundles/org.openhab.binding.folderwatcher/README.md +++ b/bundles/org.openhab.binding.folderwatcher/README.md @@ -70,7 +70,7 @@ Local folder example: ```java rule "New Local file" when - Channel "folderwatcher:localfolder:myFTPFolder:newfile" triggered + Channel "folderwatcher:localfolder:myLocalFolder:newfile" triggered then logInfo("NewLocalFile", receivedEvent.toString()) end @@ -81,7 +81,7 @@ FTP example: ```java rule "New FTP file" when - Channel "folderwatcher:ftpfolder:myLocalFolder:newfile" triggered + Channel "folderwatcher:ftpfolder:myFTPFolder:newfile" triggered then logInfo("NewFTPFile", receivedEvent.toString()) end From 6cab4f5e275e448898ee81745e3e47fb3fee2508 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Sun, 5 Nov 2023 16:36:33 +0100 Subject: [PATCH 054/146] [sensorcommunity] State pattern for dB, Percent and Microgram/m3 added (#15692) * State pattern for dB, Percent and Microgram/m3 added --------- Signed-off-by: Bernd Weymann --- .../src/main/resources/OH-INF/thing/thing-types.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.sensorcommunity/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sensorcommunity/src/main/resources/OH-INF/thing/thing-types.xml index 89684f8b3ce47..19e8eaa9043b9 100644 --- a/bundles/org.openhab.binding.sensorcommunity/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sensorcommunity/src/main/resources/OH-INF/thing/thing-types.xml @@ -76,12 +76,12 @@ Number:Density - + Number:Density - + Number:Temperature @@ -93,7 +93,7 @@ Number:Dimensionless Humidity from the selected Sensor ID - + Number:Pressure @@ -111,18 +111,18 @@ Number:Dimensionless Average noise level from the selected Sensor ID - + Number:Dimensionless Minimum noise level (last 2.5 minutes) from the selected Sensor ID - + Number:Dimensionless Maximum noise level (last 2.5 minutes) from the selected Sensor ID - + From 669b7b0fbebadab55ce306f4d220954fc07971c0 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Sun, 5 Nov 2023 16:49:27 +0100 Subject: [PATCH 055/146] New Crowdin updates (#15842) * New translations sonos.properties (Italian) * New translations androiddebugbridge.properties (Italian) --- .../resources/OH-INF/i18n/androiddebugbridge_it.properties | 4 ++++ .../src/main/resources/OH-INF/i18n/sonos_it.properties | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge_it.properties b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge_it.properties index c784eef56542d..c547164304067 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge_it.properties +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/i18n/androiddebugbridge_it.properties @@ -25,6 +25,8 @@ thing-type.config.androiddebugbridge.android.deviceMaxVolume.label = Livello mas thing-type.config.androiddebugbridge.android.deviceMaxVolume.description = Presunto volume massimo per dispositivi con versioni Android che non rendono disponibile questo valore (>\=Android 11). thing-type.config.androiddebugbridge.android.ip.label = Indirizzo IP thing-type.config.androiddebugbridge.android.ip.description = Indirizzo IP del dispositivo. +thing-type.config.androiddebugbridge.android.maxADBTimeouts.label = Timeout Max ADB +thing-type.config.androiddebugbridge.android.maxADBTimeouts.description = Timeout massimi consecutivi del comando ADB per forzare il reset della connessione. (0 per disabilitato) thing-type.config.androiddebugbridge.android.mediaStateJSONConfig.label = Configurazione Stato Media thing-type.config.androiddebugbridge.android.mediaStateJSONConfig.description = Configurazione JSON che permette di modificare la strategia di rilevamento dello stato dei media per ogni app. Fare riferimento alla documentazione del binding. thing-type.config.androiddebugbridge.android.port.label = Porta @@ -45,6 +47,8 @@ thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_musi thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_music_headset = Volume musica auricolari thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_music_usb_headset = Volume musica auricolari BT thing-type.config.androiddebugbridge.android.volumeSettingKey.option.volume_system = Volume di sistema +thing-type.config.androiddebugbridge.android.volumeStepPercent.label = Percentuale Passo Volume +thing-type.config.androiddebugbridge.android.volumeStepPercent.description = Percentuale di aumento/diminuzione del volume. # channel types diff --git a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_it.properties b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_it.properties index 90cb6c51c52ca..000c2db4b92aa 100644 --- a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_it.properties +++ b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_it.properties @@ -91,11 +91,13 @@ channel-type.sonos.codec.description = Nome del codec attualmente decodificato channel-type.sonos.codec.state.option.noSignal = Nessun Segnale channel-type.sonos.codec.state.option.silence = Silenzio channel-type.sonos.codec.state.option.DTS = DTS -channel-type.sonos.codec.state.option.dolbyAtmos = Dolby Atmos +channel-type.sonos.codec.state.option.Atmos = Dolby Atmos channel-type.sonos.codec.state.option.DD20 = Dolby Digital 2.0 channel-type.sonos.codec.state.option.PCM20 = Dolby Multichannel PCM 2.0 channel-type.sonos.codec.state.option.DD51 = Dolby Digital 5.1 +channel-type.sonos.codec.state.option.DDPlus20 = Dolby Digital Plus 2.0 channel-type.sonos.codec.state.option.DDPlus51 = Dolby Digital Plus 5.1 +channel-type.sonos.codec.state.option.TrueHD51 = Dolby TrueHD 5.1 channel-type.sonos.codec.state.option.PCM51 = Dolby Multichannel PCM 5.1 channel-type.sonos.codec.state.option.DTS51 = DTS Surround 5.1 channel-type.sonos.coordinator.label = Coordinatore From cd2ee572fdd8bf09cf42fa9a9964b2e3555b1754 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sun, 5 Nov 2023 21:12:18 +0100 Subject: [PATCH 056/146] Remove maintainers (#15849) Signed-off-by: Jacob Laursen --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 9b6080bfe4609..f41c1b47ff210 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -380,7 +380,7 @@ /bundles/org.openhab.binding.webexteams/ @tdeckers /bundles/org.openhab.binding.webthing/ @grro /bundles/org.openhab.binding.wemo/ @hmerk @jlaur -/bundles/org.openhab.binding.wifiled/ @rvt @xylo +/bundles/org.openhab.binding.wifiled/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.windcentrale/ @marcelrv @wborn /bundles/org.openhab.binding.wlanthermo/ @CSchlipp /bundles/org.openhab.binding.wled/ @Skinah From d882a493b6b82c9145cca017e5e6db78b38bebb1 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Mon, 6 Nov 2023 04:46:18 +0100 Subject: [PATCH 057/146] [knx] Fix all compiler warnings (#15840) Signed-off-by: Holger Friedrich --- .../internal/channel/KNXChannelFactory.java | 3 +- .../client/SerialTransportAdapter.java | 4 ++- .../binding/knx/internal/dpt/DPTUnits.java | 2 +- .../channel/KNXChannelFactoryTest.java | 7 ++-- .../knx/internal/channel/KNXChannelTest.java | 3 +- .../binding/knx/internal/dpt/DPTTest.java | 33 ++++++++----------- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/channel/KNXChannelFactory.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/channel/KNXChannelFactory.java index 826ed573d002c..b29a1f1c8ecd7 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/channel/KNXChannelFactory.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/channel/KNXChannelFactory.java @@ -56,6 +56,7 @@ public static KNXChannel createKnxChannel(Channel channel) throws IllegalArgumen .map(Map.Entry::getValue).findFirst() .orElseThrow(() -> new IllegalArgumentException(channelTypeUID + " is not a valid channel type ID")); - return supplier.apply(channel); + // typecast to avoid warning about unsafe return type; we know that the lookup returns non null values + return (KNXChannel) supplier.apply(channel); } } diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java index 7028d4b668fbd..d80b5f3ee09b2 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java @@ -156,7 +156,9 @@ public List portIdentifiers() { if (tmpSerialPortManager == null) { return Collections.emptyList(); } - return tmpSerialPortManager.getIdentifiers().map(SerialPortIdentifier::getName).collect(Collectors.toList()); + // typecast only required to avoid warning about less-annotated type + return (List) tmpSerialPortManager.getIdentifiers().map(SerialPortIdentifier::getName) + .collect(Collectors.toList()); } @Override diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/DPTUnits.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/DPTUnits.java index c6a06dda511f0..a550f2027bf89 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/DPTUnits.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/DPTUnits.java @@ -116,7 +116,7 @@ static Stream getAllUnitStrings() { DPT_UNIT_MAP.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_100.getID(), "ms"); // according to spec, it is ms // two byte signed (DPT 8) - DPT_UNIT_MAP.remove(DptXlator2ByteSigned.DptValueCount.getID()); // pulses habe no unit + DPT_UNIT_MAP.remove(DptXlator2ByteSigned.DptValueCount.getID()); // pulses have no unit // 4 byte unsigned (DPT 12) DPT_UNIT_MAP.remove(DPTXlator4ByteUnsigned.DPT_VALUE_4_UCOUNT.getID()); // counts have no unit diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelFactoryTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelFactoryTest.java index 78aa0586de09b..e6bf0a50ccbfd 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelFactoryTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelFactoryTest.java @@ -18,6 +18,7 @@ import static org.openhab.binding.knx.internal.KNXBindingConstants.*; import java.util.Map; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; @@ -42,7 +43,7 @@ class KNXChannelFactoryTest { */ @Test public void testNullChannelUidFails() { - Channel channel = mock(Channel.class); + Channel channel = Objects.requireNonNull(mock(Channel.class)); assertThrows(IllegalArgumentException.class, () -> { KNXChannelFactory.createKnxChannel(channel); @@ -51,7 +52,7 @@ public void testNullChannelUidFails() { @Test public void testInvalidChannelUidFails() { - Channel channel = mock(Channel.class); + Channel channel = Objects.requireNonNull(mock(Channel.class)); when(channel.getChannelTypeUID()).thenReturn(new ChannelTypeUID("a:b:c")); assertThrows(IllegalArgumentException.class, () -> { @@ -65,7 +66,7 @@ public void testInvalidChannelUidFails() { CHANNEL_NUMBER_CONTROL, CHANNEL_ROLLERSHUTTER, CHANNEL_ROLLERSHUTTER_CONTROL, CHANNEL_STRING, CHANNEL_STRING_CONTROL, CHANNEL_SWITCH, CHANNEL_SWITCH_CONTROL }) public void testSuccess(String channeltype) { - Channel channel = mock(Channel.class); + Channel channel = Objects.requireNonNull(mock(Channel.class)); Configuration configuration = new Configuration( Map.of("key1", "5.001:<1/2/3+4/5/6+1/5/6", "key2", "1.001:7/1/9+1/1/2")); when(channel.getChannelTypeUID()).thenReturn(new ChannelTypeUID("knx:" + channeltype)); diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelTest.java index 866a1410f1cf9..560fbe09fce73 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/channel/KNXChannelTest.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -139,7 +140,7 @@ void testParseFreeLevel() throws KNXFormatException { @Test public void testChannelGaParsing() throws KNXFormatException { - Channel channel = mock(Channel.class); + Channel channel = Objects.requireNonNull(mock(Channel.class)); Configuration configuration = new Configuration( Map.of("key1", "5.001:<1/2/3+4/5/6+1/5/6", "key2", "1.001:7/1/9+1/1/2")); when(channel.getChannelTypeUID()).thenReturn(new ChannelTypeUID("a:b:c")); diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java index 71c3b7ab56de0..cda7f4c796c91 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java @@ -59,17 +59,16 @@ public void testToDPTValueDecimalType() { } @Test - @SuppressWarnings("null") void testToDPT5ValueFromQuantityType() { assertEquals("80", ValueEncoder.encode(new QuantityType<>("80 %"), "5.001")); assertEquals("180", ValueEncoder.encode(new QuantityType<>("180 °"), "5.003")); - assertTrue(ValueEncoder.encode(new QuantityType<>("3.14 rad"), "5.003").startsWith("179.")); + assertTrue(Objects.requireNonNullElse(ValueEncoder.encode(new QuantityType<>("3.14 rad"), "5.003"), "") + .startsWith("179.")); assertEquals("80", ValueEncoder.encode(new QuantityType<>("80 %"), "5.004")); } @Test - @SuppressWarnings("null") void testToDPT7ValueFromQuantityType() { assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "7.002")); assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "7.003")); @@ -86,7 +85,6 @@ void testToDPT7ValueFromQuantityType() { } @Test - @SuppressWarnings("null") void testToDPT8ValueFromQuantityType() { assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "8.002")); assertEquals("1000", ValueEncoder.encode(new QuantityType<>("1000 ms"), "8.003")); @@ -100,7 +98,6 @@ void testToDPT8ValueFromQuantityType() { } @Test - @SuppressWarnings("null") void testToDPT9ValueFromQuantityType() { assertEquals("23.1", ValueEncoder.encode(new QuantityType<>("23.1 °C"), "9.001")); assertEquals(5.0, @@ -109,14 +106,16 @@ void testToDPT9ValueFromQuantityType() { assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K"), "9.002")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1000 mK"), "9.002")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °C"), "9.002")); - assertTrue(ValueEncoder.encode(new QuantityType<>("1 °F"), "9.002").startsWith("0.55")); + assertTrue(Objects.requireNonNullElse(ValueEncoder.encode(new QuantityType<>("1 °F"), "9.002"), "") + .startsWith("0.55")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K/h"), "9.003")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °C/h"), "9.003")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1000 mK/h"), "9.003")); assertEquals("600", ValueEncoder.encode(new QuantityType<>("10 K/min"), "9.003")); assertEquals("100", ValueEncoder.encode(new QuantityType<>("100 lx"), "9.004")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m/s"), "9.005")); - assertTrue(ValueEncoder.encode(new QuantityType<>("1.94 kn"), "9.005").startsWith("0.99")); + assertTrue(Objects.requireNonNullElse(ValueEncoder.encode(new QuantityType<>("1.94 kn"), "9.005"), "") + .startsWith("0.99")); assertEquals(1.0, Double .parseDouble(Objects.requireNonNull(ValueEncoder.encode(new QuantityType<>("3.6 km/h"), "9.005")))); assertEquals("456", ValueEncoder.encode(new QuantityType<>("456 Pa"), "9.006")); @@ -133,34 +132,33 @@ void testToDPT9ValueFromQuantityType() { assertEquals("12", ValueEncoder.encode(new QuantityType<>("12 W/m²"), "9.022")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K/%"), "9.023")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °C/%"), "9.023")); - assertTrue(ValueEncoder.encode(new QuantityType<>("1 °F/%"), "9.023").startsWith("0.55")); + assertTrue(Objects.requireNonNullElse(ValueEncoder.encode(new QuantityType<>("1 °F/%"), "9.023"), "") + .startsWith("0.55")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 kW"), "9.024")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 l/h"), "9.025")); assertEquals("60", ValueEncoder.encode(new QuantityType<>("1 l/min"), "9.025")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 l/m²"), "9.026")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 °F"), "9.027")); - assertTrue(ValueEncoder.encode(new QuantityType<>("-12 °C"), "9.027").startsWith("10.")); + assertTrue(Objects.requireNonNullElse(ValueEncoder.encode(new QuantityType<>("-12 °C"), "9.027"), "") + .startsWith("10.")); assertEquals("10", ValueEncoder.encode(new QuantityType<>("10 km/h"), "9.028")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 g/m³"), "9.029")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 µg/m³"), "9.030")); } @Test - @SuppressWarnings("null") void testToDPT10ValueFromQuantityType() { - // DateTimeTyype, not QuantityType + // DateTimeType, not QuantityType assertEquals("Wed, 17:30:00", ValueEncoder.encode(new DateTimeType("2019-06-12T17:30:00Z"), "10.001")); } @Test - @SuppressWarnings("null") void testToDPT11ValueFromQuantityType() { - // DateTimeTyype, not QuantityType + // DateTimeType, not QuantityType assertEquals("2019-06-12", ValueEncoder.encode(new DateTimeType("2019-06-12T17:30:00Z"), "11.001")); } @Test - @SuppressWarnings("null") void testToDPT12ValueFromQuantityType() { // 12.001: dimensionless @@ -174,7 +172,6 @@ void testToDPT12ValueFromQuantityType() { } @Test - @SuppressWarnings("null") void testToDPT13ValueFromQuantityType() { // 13.001 dimensionless assertEquals("24", ValueEncoder.encode(new QuantityType<>("24 m³/h"), "13.002")); @@ -195,7 +192,6 @@ void testToDPT13ValueFromQuantityType() { } @Test - @SuppressWarnings("null") void testToDPT14ValueFromQuantityType() { assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m/s²"), "14.000")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 rad/s²"), "14.001")); @@ -287,9 +283,8 @@ void testToDPT14ValueFromQuantityType() { } @Test - @SuppressWarnings("null") void testToDPT19ValueFromQuantityType() { - // DateTimeTyype, not QuantityType + // DateTimeType, not QuantityType assertEquals("2019-06-12 17:30:00", ValueEncoder.encode(new DateTimeType("2019-06-12T17:30:00Z"), "19.001")); } @@ -358,7 +353,7 @@ public void unitFixes() { assertNotEquals(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_100.getUnit(), "ms"); // according to spec, it is ms // two byte signed (DPT 8, DPTXlator is missing in calimero 2.5-M1) - assertNotEquals("", DptXlator2ByteSigned.DptValueCount.getUnit()); // pulses habe no unit + assertNotEquals("", DptXlator2ByteSigned.DptValueCount.getUnit()); // pulses have no unit // 4 byte unsigned (DPT 12) assertNotEquals("", DPTXlator4ByteUnsigned.DPT_VALUE_4_UCOUNT.getUnit()); // counts have no unit From 01987c3b417073b4a7e39e11def64ac6fd044318 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Mon, 6 Nov 2023 15:30:57 +0100 Subject: [PATCH 058/146] [irtrans] Remove org.apache.common (#14408) * Fix warnings Signed-off-by: Leo Siepel --- .../binding/irtrans/internal/IrCommand.java | 8 +-- .../internal/handler/BlasterHandler.java | 12 ++-- .../handler/EthernetBridgeHandler.java | 59 +++++++++---------- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/IrCommand.java b/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/IrCommand.java index b1497e8aa255c..657cd551ceeb1 100644 --- a/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/IrCommand.java +++ b/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/IrCommand.java @@ -43,8 +43,8 @@ public class IrCommand { * */ private class PulsePair { - public int Pulse; - public int Pause; + public int pulse; + public int pause; } private String remote; @@ -254,7 +254,7 @@ public ByteBuffer toByteBuffer() { // Pause timings - 8 Shorts = 16 bytes for (int i = 0; i < pulsePairs.size(); i++) { - byteBuffer.putShort((short) Math.round(pulsePairs.get(i).Pause / 8)); + byteBuffer.putShort((short) Math.round(pulsePairs.get(i).pause / 8)); } for (int i = pulsePairs.size(); i <= 7; i++) { byteBuffer.putShort((short) 0); @@ -262,7 +262,7 @@ public ByteBuffer toByteBuffer() { // Pulse timings - 8 Shorts = 16 bytes for (int i = 0; i < pulsePairs.size(); i++) { - byteBuffer.putShort((short) Math.round(pulsePairs.get(i).Pulse / 8)); + byteBuffer.putShort((short) Math.round(pulsePairs.get(i).pulse / 8)); } for (int i = pulsePairs.size(); i <= 7; i++) { byteBuffer.putShort((short) 0); diff --git a/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/BlasterHandler.java b/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/BlasterHandler.java index 1be5e4d08c58c..78a8cbfe891ab 100644 --- a/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/BlasterHandler.java +++ b/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/BlasterHandler.java @@ -14,7 +14,6 @@ import static org.openhab.binding.irtrans.internal.IRtransBindingConstants.CHANNEL_IO; -import org.apache.commons.lang3.StringUtils; import org.openhab.binding.irtrans.internal.IRtransBindingConstants.Led; import org.openhab.binding.irtrans.internal.IrCommand; import org.openhab.core.library.types.StringType; @@ -70,12 +69,15 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (!(command instanceof RefreshType)) { if (channelUID.getId().equals(CHANNEL_IO)) { if (command instanceof StringType) { - String remoteName = StringUtils.substringBefore(command.toString(), ","); - String irCommandName = StringUtils.substringAfter(command.toString(), ","); + String[] remoteCommand = command.toString().split(",", 2); + if (remoteCommand.length < 2) { + logger.warn("Ignoring invalid command '{}'", command); + return; + } IrCommand ircommand = new IrCommand(); - ircommand.setRemote(remoteName); - ircommand.setCommand(irCommandName); + ircommand.setRemote(remoteCommand[0]); + ircommand.setCommand(remoteCommand[1]); IrCommand thingCompatibleCommand = new IrCommand(); thingCompatibleCommand.setRemote((String) getConfig().get(REMOTE)); diff --git a/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/EthernetBridgeHandler.java b/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/EthernetBridgeHandler.java index 7c179477462b3..825616e8d668b 100644 --- a/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/EthernetBridgeHandler.java +++ b/bundles/org.openhab.binding.irtrans/src/main/java/org/openhab/binding/irtrans/internal/handler/EthernetBridgeHandler.java @@ -35,8 +35,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.irtrans.internal.IRtransBindingConstants; import org.openhab.binding.irtrans.internal.IRtransBindingConstants.Led; import org.openhab.binding.irtrans.internal.IrCommand; @@ -51,6 +49,7 @@ import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.util.HexUtils; +import org.openhab.core.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -179,11 +178,11 @@ public void dispose() { } } - public boolean registerTransceiverStatusListener(@NonNull TransceiverStatusListener transceiverStatusListener) { + public boolean registerTransceiverStatusListener(TransceiverStatusListener transceiverStatusListener) { return transceiverStatusListeners.add(transceiverStatusListener); } - public boolean unregisterTransceiverStatusListener(@NonNull TransceiverStatusListener transceiverStatusListener) { + public boolean unregisterTransceiverStatusListener(TransceiverStatusListener transceiverStatusListener) { return transceiverStatusListeners.remove(transceiverStatusListener); } @@ -277,12 +276,15 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (channel.getChannelTypeUID() != null && channel.getChannelTypeUID().getId().equals(IRtransBindingConstants.BLASTER_CHANNEL_TYPE)) { if (command instanceof StringType) { - String remoteName = StringUtils.substringBefore(command.toString(), ","); - String irCommandName = StringUtils.substringAfter(command.toString(), ","); + String[] remoteCommand = command.toString().split(",", 2); + if (remoteCommand.length < 2) { + logger.warn("Ignoring invalid command '{}'", command); + return; + } IrCommand ircommand = new IrCommand(); - ircommand.setRemote(remoteName); - ircommand.setCommand(irCommandName); + ircommand.setRemote(remoteCommand[0]); + ircommand.setCommand(remoteCommand[1]); IrCommand thingCompatibleCommand = new IrCommand(); thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE)); @@ -343,14 +345,12 @@ protected void configureTransceiver() { if (response != null) { String message = stripByteCount(response).split("\0")[0]; - if (message != null) { - if (message.contains("VERSION")) { - logger.info("'{}' matches an IRtrans device with firmware {}", getThing().getUID(), message); - getConfig().put(FIRMWARE_VERSION, message); - } else { - logger.debug("Received some non-compliant garbage ({})", message); - onConnectionLost(); - } + if (message.contains("VERSION")) { + logger.info("'{}' matches an IRtrans device with firmware {}", getThing().getUID(), message); + getConfig().put(FIRMWARE_VERSION, message); + } else { + logger.debug("Received some non-compliant garbage ({})", message); + onConnectionLost(); } } else { try { @@ -441,20 +441,17 @@ private String[] getRemoteCommands(String remote, int index) { if (response != null) { String message = stripByteCount(response).split("\0")[0]; logger.trace("commands returned {}", message); - if (message != null) { - if (message.contains("COMMANDLIST")) { - commandList = message.split(","); - } else { - logger.debug("Received some non-compliant command ({})", message); - onConnectionLost(); - } + if (message.contains("COMMANDLIST")) { + commandList = message.split(","); + } else { + logger.debug("Received some non-compliant command ({})", message); + onConnectionLost(); } } else { logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped", getThing().getUID()); onConnectionLost(); } - return commandList; } @@ -466,13 +463,11 @@ private String[] getRemoteList(int index) { if (response != null) { String message = stripByteCount(response).split("\0")[0]; logger.trace("remotes returned {}", message); - if (message != null) { - if (message.contains("REMOTELIST")) { - remoteList = message.split(","); - } else { - logger.debug("Received some non-compliant command ({})", message); - onConnectionLost(); - } + if (message.contains("REMOTELIST")) { + remoteList = message.split(","); + } else { + logger.debug("Received some non-compliant command ({})", message); + onConnectionLost(); } } else { logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped", @@ -493,7 +488,7 @@ public boolean sendIRcommand(IrCommand command, Led led) { if (response != null) { String message = stripByteCount(response).split("\0")[0]; - if (message != null && message.contains("RESULT OK")) { + if (message.contains("RESULT OK")) { return true; } else { logger.debug("Received an unexpected response from the IRtrans transceiver: '{}'", message); From dd6f43ba9487d5993791fd97ea645f74a216c451 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Mon, 6 Nov 2023 21:31:53 +0100 Subject: [PATCH 059/146] [deconz] Support window covering controller (#15856) Some devices report "Window covering controller" instead of "Window covering device". They are controlled in the same way. Signed-off-by: Jan N. Klug --- .../deconz/internal/discovery/ThingDiscoveryService.java | 2 +- .../org/openhab/binding/deconz/internal/types/LightType.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java index 653b80641404d..ce61da92d741c 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/discovery/ThingDiscoveryService.java @@ -193,7 +193,7 @@ private void addLight(String lightId, LightMessage light) { case COLOR_TEMPERATURE_LIGHT -> thingTypeUID = THING_TYPE_COLOR_TEMPERATURE_LIGHT; case COLOR_DIMMABLE_LIGHT, COLOR_LIGHT -> thingTypeUID = THING_TYPE_COLOR_LIGHT; case EXTENDED_COLOR_LIGHT -> thingTypeUID = THING_TYPE_EXTENDED_COLOR_LIGHT; - case WINDOW_COVERING_DEVICE -> thingTypeUID = THING_TYPE_WINDOW_COVERING; + case WINDOW_COVERING_DEVICE, WINDOW_COVERING_CONTROLLER -> thingTypeUID = THING_TYPE_WINDOW_COVERING; case WARNING_DEVICE -> thingTypeUID = THING_TYPE_WARNING_DEVICE; case DOORLOCK -> thingTypeUID = THING_TYPE_DOORLOCK; case CONFIGURATION_TOOL -> { diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/types/LightType.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/types/LightType.java index d611b2fabcfaf..0c0c4e2336ec5 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/types/LightType.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/types/LightType.java @@ -38,6 +38,7 @@ public enum LightType { DIMMABLE_LIGHT("Dimmable light"), DIMMABLE_PLUGIN_UNIT("Dimmable plug-in unit"), WINDOW_COVERING_DEVICE("Window covering device"), + WINDOW_COVERING_CONTROLLER("Window covering controller"), CONFIGURATION_TOOL("Configuration tool"), WARNING_DEVICE("Warning device"), DOORLOCK("Door Lock"), From 3f9b91c2f2b67ff20e54447dd6b0af8715d0c3c8 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Tue, 7 Nov 2023 08:27:51 +1000 Subject: [PATCH 060/146] [jrubyscripting] Update to jruby 9.4.5.0 (#15852) Signed-off-by: Jimmy Tanagra --- bundles/org.openhab.automation.jrubyscripting/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.automation.jrubyscripting/pom.xml b/bundles/org.openhab.automation.jrubyscripting/pom.xml index 5228b4590cf8a..98fa0bef3ce24 100644 --- a/bundles/org.openhab.automation.jrubyscripting/pom.xml +++ b/bundles/org.openhab.automation.jrubyscripting/pom.xml @@ -16,7 +16,7 @@ com.sun.nio.*;resolution:=optional,com.sun.security.*;resolution:=optional,org.apache.tools.ant.*;resolution:=optional,org.bouncycastle.*;resolution:=optional,org.joda.*;resolution:=optional,sun.management.*;resolution:=optional,sun.nio.*;resolution:=optional,jakarta.annotation;resolution:=optional - 9.4.3.0 + 9.4.5.0 From 10e782f6b888ca366a8d079c64a7c939162a5ef2 Mon Sep 17 00:00:00 2001 From: Konstantin Polihronov Date: Tue, 7 Nov 2023 09:54:08 +0200 Subject: [PATCH 061/146] [solax] Support for three phase inverter X3 Hybrid G4 (#15710) Signed-off-by: Konstantin Polihronov --- bundles/org.openhab.binding.solax/README.md | 45 +++- .../solax/internal/SolaxBindingConstants.java | 75 ++++-- .../internal/SolaxLocalAccessHandler.java | 248 ++++++++++++++---- .../rawdata/LocalConnectRawDataBean.java | 147 +---------- .../solax/internal/model/InverterData.java | 191 +++++++++++--- .../solax/internal/model/InverterType.java | 38 ++- .../model/impl/CommonInverterData.java | 81 ++++++ .../model/impl/X1HybridG4InverterData.java | 110 ++++++++ .../model/impl/X3HybridG4InverterData.java | 223 ++++++++++++++++ .../internal/model/parsers/RawDataParser.java | 33 +++ .../model/parsers/X1HybridG4DataParser.java | 50 ++++ .../model/parsers/X3HybridG4DataParser.java | 58 ++++ .../resources/OH-INF/i18n/solax.properties | 53 +++- .../OH-INF/thing/localConnectInverter.xml | 112 +++++++- .../local_connect_inverter_type_update.xml | 134 ++++++++++ .../solax/internal/TestX1HybridG4Parser.java | 104 ++++++++ .../solax/internal/TestX3HybridG4Parser.java | 114 ++++++++ 17 files changed, 1554 insertions(+), 262 deletions(-) create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java create mode 100644 bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml create mode 100644 bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java create mode 100644 bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java diff --git a/bundles/org.openhab.binding.solax/README.md b/bundles/org.openhab.binding.solax/README.md index 6b35fb62ea100..9d914802ad3c9 100644 --- a/bundles/org.openhab.binding.solax/README.md +++ b/bundles/org.openhab.binding.solax/README.md @@ -16,6 +16,9 @@ In case the parsed information that comes with the binding out of the box differ |------------------------|------------|-------------------------------------------------------------------------------------| | local-connect-inverter | Thing | This is model representation of inverter with all the data available as a channels | +Note: Channels may vary depending on the inverter type and the availability of information for parsing the raw data. +If you're missing a channel this means that it's not supported for your inverter type. + ## Thing Configuration ### Local Connect Inverter Configuration @@ -28,12 +31,25 @@ In case the parsed information that comes with the binding out of the box differ ### Inverter Output Channels -| Channel | Type | Description | -|--------------------------|----------------------------|--------------------------------------------------| -| inverter-output-power | Number:Power | The output power of the inverter [W] | -| inverter-current | Number:ElectricCurrent | The output current of the inverter [A] | -| inverter-voltage | Number:ElectricPotential | The output voltage of the inverter [V] | -| inverter-frequency | Number:Frequency | The frequency of the output voltage [Hz] | +| Channel | Type | Description | +|---------------------------------|----------------------------|----------------------------------------------------------------| +| inverter-output-power | Number:Power | The output power of the inverter [W] | +| inverter-current | Number:ElectricCurrent | The output current of the inverter [A] | +| inverter-voltage | Number:ElectricPotential | The output voltage of the inverter [V] | +| inverter-frequency | Number:Frequency | The frequency of the electricity of the inverter [Hz] | +| inverter-output-power-phase1 | Number:Power | The output power of phase 1 of the inverter [W] | +| inverter-output-power-phase2 | Number:Power | The output power of phase 2 of the inverter [W] | +| inverter-output-power-phase3 | Number:Power | The output power of phase 3 of the inverter [W] | +| inverter-total-output-power | Number:Power | The total output power of all phases of the inverter [W] | +| inverter-current-phase1 | Number:ElectricCurrent | The output current of phase 1 of the inverter [A] | +| inverter-current-phase2 | Number:ElectricCurrent | The output current of phase 2 of the inverter [A] | +| inverter-current-phase3 | Number:ElectricCurrent | The output current of phase 3 of the inverter [A] | +| inverter-voltage-phase1 | Number:ElectricPotential | The output voltage of phase 1 of the inverter [V] | +| inverter-voltage-phase2 | Number:ElectricPotential | The output voltage of phase 2 of the inverter [V] | +| inverter-voltage-phase3 | Number:ElectricPotential | The output voltage of phase 3 of the inverter [V] | +| inverter-frequency-phase1 | Number:Frequency | The frequency of phase 1 of the inverter [Hz] | +| inverter-frequency-phase2 | Number:Frequency | The frequency of phase 2 of the inverter [Hz] | +| inverter-frequency-phase3 | Number:Frequency | The frequency of phase 3 of the inverter [Hz] | ### Photovoltaic Panels Production Channels @@ -71,6 +87,23 @@ In case the parsed information that comes with the binding out of the box differ | last-update-time | DateTime | Last time when a call has been made to the inverter | | raw-data | String | The raw data retrieved from inverter in JSON format. (Usable for channels not implemented. Can be consumed with the JSONpath transformation | +### Statistics / Usage related Channels + +| Channel | Type | Description | +|----------------------------------|----------------------------|-----------------------------------------------------------| +| power-usage | Number:Power | Current power usage / consumption of the building [W] | +| total-energy | Number:Energy | Total energy output from the inverter [kWh] | +| total-battery-discharge-energy | Number:Energy | Total energy from the battery [kWh] | +| total-battery-charge-energy | Number:Energy | Total energy to the battery [kWh] | +| total-pv-energy | Number:Energy | Total energy from the PV [kWh] | +| total-consumption | Number:Energy | Total energy consumed for the building [kWh] | +| total-feed-in-energy | Number:Energy | Total energy consumed from the electricity provider [kWh] | +| today-energy | Number:Energy | Energy output from the inverter for the day [kWh] | +| today-battery-discharge-energy | Number:Energy | Total energy from the battery output for the day [kWh] | +| today-battery-charge-energy | Number:Energy | Total energy charged to the battery for the day [kWh] | +| today-feed-in-energy | Number:Energy | Total energy charged to the battery for the day [kWh] | +| today-consumption | Number:Energy | Total energy consumed for the day [kWh] | + ### Properties | Property | Description | diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java index 74151b3aecbc7..52c871747c288 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java @@ -39,32 +39,67 @@ public class SolaxBindingConstants { public static final String PROPERTY_INVERTER_TYPE = "inverterType"; // List of all Channel ids - public static final String INVERTER_OUTPUT_POWER = "inverter-output-power"; - public static final String INVERTER_OUTPUT_CURRENT = "inverter-current"; - public static final String INVERTER_OUTPUT_VOLTAGE = "inverter-voltage"; - public static final String INVERTER_OUTPUT_FREQUENCY = "inverter-frequency"; + // Single phase specific + public static final String CHANNEL_INVERTER_OUTPUT_POWER = "inverter-output-power"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT = "inverter-current"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE = "inverter-voltage"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY = "inverter-frequency"; + public static final Set SINGLE_CHANNEL_SPECIFIC_CHANNEL_IDS = Set.of(CHANNEL_INVERTER_OUTPUT_POWER, + CHANNEL_INVERTER_OUTPUT_CURRENT, CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY); - public static final String INVERTER_PV1_POWER = "pv1-power"; - public static final String INVERTER_PV1_VOLTAGE = "pv1-voltage"; - public static final String INVERTER_PV1_CURRENT = "pv1-current"; + // Three phase specific + public static final String CHANNEL_INVERTER_OUTPUT_POWER_PHASE1 = "inverter-output-power-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_POWER_PHASE2 = "inverter-output-power-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_POWER_PHASE3 = "inverter-output-power-phase3"; + public static final String CHANNEL_INVERTER_TOTAL_OUTPUT_POWER = "inverter-total-output-power"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1 = "inverter-current-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2 = "inverter-current-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3 = "inverter-current-phase3"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1 = "inverter-voltage-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2 = "inverter-voltage-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3 = "inverter-voltage-phase3"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1 = "inverter-frequency-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2 = "inverter-frequency-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3 = "inverter-frequency-phase3"; - public static final String INVERTER_PV2_POWER = "pv2-power"; - public static final String INVERTER_PV2_VOLTAGE = "pv2-voltage"; - public static final String INVERTER_PV2_CURRENT = "pv2-current"; + // Generic + public static final String CHANNEL_INVERTER_PV1_POWER = "pv1-power"; + public static final String CHANNEL_INVERTER_PV1_VOLTAGE = "pv1-voltage"; + public static final String CHANNEL_INVERTER_PV1_CURRENT = "pv1-current"; - public static final String INVERTER_PV_TOTAL_POWER = "pv-total-power"; - public static final String INVERTER_PV_TOTAL_CURRENT = "pv-total-current"; + public static final String CHANNEL_INVERTER_PV2_POWER = "pv2-power"; + public static final String CHANNEL_INVERTER_PV2_VOLTAGE = "pv2-voltage"; + public static final String CHANNEL_INVERTER_PV2_CURRENT = "pv2-current"; - public static final String BATTERY_POWER = "battery-power"; - public static final String BATTERY_VOLTAGE = "battery-voltage"; - public static final String BATTERY_CURRENT = "battery-current"; - public static final String BATTERY_TEMPERATURE = "battery-temperature"; - public static final String BATTERY_STATE_OF_CHARGE = "battery-level"; + public static final String CHANNEL_INVERTER_PV_TOTAL_POWER = "pv-total-power"; + public static final String CHANNEL_INVERTER_PV_TOTAL_CURRENT = "pv-total-current"; - public static final String FEED_IN_POWER = "feed-in-power"; + public static final String CHANNEL_BATTERY_POWER = "battery-power"; + public static final String CHANNEL_BATTERY_VOLTAGE = "battery-voltage"; + public static final String CHANNEL_BATTERY_CURRENT = "battery-current"; + public static final String CHANNEL_BATTERY_TEMPERATURE = "battery-temperature"; + public static final String CHANNEL_BATTERY_STATE_OF_CHARGE = "battery-level"; - public static final String TIMESTAMP = "last-update-time"; - public static final String RAW_DATA = "raw-data"; + public static final String CHANNEL_FEED_IN_POWER = "feed-in-power"; + + public static final String CHANNEL_TIMESTAMP = "last-update-time"; + public static final String CHANNEL_RAW_DATA = "raw-data"; + + // Totals + public static final String CHANNEL_POWER_USAGE = "power-usage"; + public static final String CHANNEL_TOTAL_ENERGY = "total-energy"; + public static final String CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY = "total-battery-discharge-energy"; + public static final String CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY = "total-battery-charge-energy"; + public static final String CHANNEL_TOTAL_PV_ENERGY = "total-pv-energy"; + public static final String CHANNEL_TOTAL_FEED_IN_ENERGY = "total-feed-in-energy"; + public static final String CHANNEL_TOTAL_CONSUMPTION = "total-consumption"; + + // Today totals + public static final String CHANNEL_TODAY_ENERGY = "today-energy"; + public static final String CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY = "today-battery-discharge-energy"; + public static final String CHANNEL_TODAY_BATTERY_CHARGE_ENERGY = "today-battery-charge-energy"; + public static final String CHANNEL_TODAY_FEED_IN_ENERGY = "today-feed-in-energy"; + public static final String CHANNEL_TODAY_CONSUMPTION = "today-consumption"; // I18N Keys protected static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved"; diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java index 7edcfaf216cf2..d53e7aeb40ae2 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java @@ -14,25 +14,35 @@ import java.io.IOException; import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import javax.measure.Quantity; +import javax.measure.Unit; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector; import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,6 +65,10 @@ public class SolaxLocalAccessHandler extends BaseThingHandler { private @Nullable ScheduledFuture schedule; + private boolean alreadyRemovedUnsupportedChannels; + + private final Set unsupportedExistingChannels = new HashSet(); + public SolaxLocalAccessHandler(Thing thing) { super(thing); } @@ -79,7 +93,7 @@ private void retrieveData() { logger.debug("Raw data retrieved = {}", rawJsonData); if (rawJsonData != null && !rawJsonData.isEmpty()) { - updateData(rawJsonData); + updateFromData(rawJsonData); } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, SolaxBindingConstants.I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED); @@ -90,68 +104,181 @@ private void retrieveData() { } } - private void updateData(String rawJsonData) { + private void updateFromData(String rawJsonData) { try { - LocalConnectRawDataBean inverterParsedData = parseJson(rawJsonData); - updateThing(inverterParsedData); + LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData); + InverterType inverterType = calculateInverterType(rawDataBean); + RawDataParser parser = inverterType.getParser(); + if (parser != null) { + if (!alreadyRemovedUnsupportedChannels) { + removeUnsupportedChannels(inverterType.getSupportedChannels()); + alreadyRemovedUnsupportedChannels = true; + } + + InverterData genericInverterData = parser.getData(rawDataBean); + updateChannels(parser, genericInverterData); + updateProperties(genericInverterData); + + if (getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + } else { + cancelSchedule(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]"); + } } catch (JsonParseException e) { logger.debug("Unable to deserialize from JSON.", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } - private void updateThing(LocalConnectRawDataBean inverterParsedData) { - transferInverterDataToChannels(inverterParsedData); - - if (getThing().getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } - } - private LocalConnectRawDataBean parseJson(String rawJsonData) { LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData); - logger.debug("Received a new inverter data object. Data = {}", inverterParsedData.toStringDetailed()); + logger.debug("Received a new inverter JSON object. Data = {}", inverterParsedData.toString()); return inverterParsedData; } - private void transferInverterDataToChannels(InverterData data) { - updateProperty(Thing.PROPERTY_SERIAL_NUMBER, data.getWifiSerial()); - updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, data.getInverterType().name()); - - updateState(SolaxBindingConstants.INVERTER_OUTPUT_POWER, - new QuantityType<>(data.getInverterOutputPower(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_OUTPUT_CURRENT, - new QuantityType<>(data.getInverterCurrent(), Units.AMPERE)); - updateState(SolaxBindingConstants.INVERTER_OUTPUT_VOLTAGE, - new QuantityType<>(data.getInverterVoltage(), Units.VOLT)); - updateState(SolaxBindingConstants.INVERTER_OUTPUT_FREQUENCY, - new QuantityType<>(data.getInverterFrequency(), Units.HERTZ)); - - updateState(SolaxBindingConstants.INVERTER_PV1_POWER, new QuantityType<>(data.getPV1Power(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_PV1_CURRENT, new QuantityType<>(data.getPV1Current(), Units.AMPERE)); - updateState(SolaxBindingConstants.INVERTER_PV1_VOLTAGE, new QuantityType<>(data.getPV1Voltage(), Units.VOLT)); - - updateState(SolaxBindingConstants.INVERTER_PV2_POWER, new QuantityType<>(data.getPV2Power(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_PV2_CURRENT, new QuantityType<>(data.getPV2Current(), Units.AMPERE)); - updateState(SolaxBindingConstants.INVERTER_PV2_VOLTAGE, new QuantityType<>(data.getPV2Voltage(), Units.VOLT)); - - updateState(SolaxBindingConstants.INVERTER_PV_TOTAL_POWER, - new QuantityType<>(data.getPVTotalPower(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_PV_TOTAL_CURRENT, - new QuantityType<>(data.getPVTotalCurrent(), Units.AMPERE)); - - updateState(SolaxBindingConstants.BATTERY_POWER, new QuantityType<>(data.getBatteryPower(), Units.WATT)); - updateState(SolaxBindingConstants.BATTERY_CURRENT, new QuantityType<>(data.getBatteryCurrent(), Units.AMPERE)); - updateState(SolaxBindingConstants.BATTERY_VOLTAGE, new QuantityType<>(data.getBatteryVoltage(), Units.VOLT)); - updateState(SolaxBindingConstants.BATTERY_TEMPERATURE, - new QuantityType<>(data.getBatteryTemperature(), SIUnits.CELSIUS)); - updateState(SolaxBindingConstants.BATTERY_STATE_OF_CHARGE, - new QuantityType<>(data.getBatterySoC(), Units.PERCENT)); - - updateState(SolaxBindingConstants.FEED_IN_POWER, new QuantityType<>(data.getFeedInPower(), Units.WATT)); - - updateState(SolaxBindingConstants.TIMESTAMP, new DateTimeType(ZonedDateTime.now())); - updateState(SolaxBindingConstants.RAW_DATA, new StringType(data.getRawData())); + private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) { + int type = rawDataBean.getType(); + return InverterType.fromIndex(type); + } + + private void updateProperties(InverterData genericInverterData) { + updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial()); + updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name()); + } + + private void updateChannels(RawDataParser parser, InverterData inverterData) { + updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData())); + + Set supportedChannels = parser.getSupportedChannels(); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT, + supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT, + supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(), + Units.AMPERE, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(), + SIUnits.CELSIUS, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(), + Units.PERCENT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT, + supportedChannels); + + // Totals + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY, + inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, + inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(), + Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(), + Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(), + Units.KILOWATT_HOUR, supportedChannels); + + // Today's + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, + inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, + inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(), + Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(), + Units.KILOWATT_HOUR, supportedChannels); + + // Single phase specific channels + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(), + Units.AMPERE, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(), + Units.VOLT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(), + Units.HERTZ, supportedChannels); + + // Three phase specific channels + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(), + Units.WATT, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(), + Units.AMPERE, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(), + Units.AMPERE, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(), + Units.AMPERE, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(), + Units.VOLT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(), + Units.VOLT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(), + Units.VOLT, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(), + Units.HERTZ, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(), + Units.HERTZ, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(), + Units.HERTZ, supportedChannels); + + // Binding provided data + updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now())); + } + + private void removeUnsupportedChannels(Set supportedChannels) { + if (supportedChannels.isEmpty()) { + return; + } + List channels = getThing().getChannels(); + List channelsToRemove = channels.stream() + .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList(); + + if (!channelsToRemove.isEmpty()) { + if (logger.isDebugEnabled()) { + logRemovedChannels(channelsToRemove); + } + updateThing(editThing().withoutChannels(channelsToRemove).build()); + } + } + + private void logRemovedChannels(List channelsToRemove) { + List channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId()) + .toList(); + logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}", + channelsToRemoveForLog); } @Override @@ -162,10 +289,29 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void dispose() { super.dispose(); + cancelSchedule(); + } + + private void cancelSchedule() { ScheduledFuture schedule = this.schedule; if (schedule != null) { schedule.cancel(true); this.schedule = null; } } + + private > void updateChannel(String channelID, double value, Unit unit, + Set supportedChannels) { + if (supportedChannels.contains(channelID)) { + if (value > Short.MIN_VALUE) { + updateState(channelID, new QuantityType<>(value, unit)); + } else if (!unsupportedExistingChannels.contains(channelID)) { + updateState(channelID, UnDefType.UNDEF); + unsupportedExistingChannels.add(channelID); + logger.warn( + "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.", + channelID, value); + } + } + } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java index b6c978418ef35..7bf9fba12f7f2 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java @@ -16,11 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.solax.internal.model.InverterData; -import org.openhab.binding.solax.internal.model.InverterType; import org.openhab.binding.solax.internal.util.GsonSupplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; @@ -32,9 +28,7 @@ * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public class LocalConnectRawDataBean implements RawDataBean, InverterData { - - private final Logger logger = LoggerFactory.getLogger(LocalConnectRawDataBean.class); +public class LocalConnectRawDataBean implements RawDataBean { private @Nullable String sn; private @Nullable String ver; @@ -113,143 +107,4 @@ public static LocalConnectRawDataBean fromJson(String json) { deserializedObject.setRawData(json); return deserializedObject; } - - // Parsed inverter data interface implementation starts here - - @Override - public @Nullable String getWifiSerial() { - return getSn(); - } - - @Override - public @Nullable String getWifiVersion() { - return getVer(); - } - - @Override - public InverterType getInverterType() { - return InverterType.fromIndex(type); - } - - @Override - public short getInverterVoltage() { - return (short) (getData(0) / 10); - } - - @Override - public short getInverterCurrent() { - return (short) (getData(1) / 10); - } - - @Override - public short getInverterOutputPower() { - return getData(2); - } - - @Override - public short getInverterFrequency() { - return (short) (getData(3) / 100); - } - - @Override - public short getPV1Voltage() { - return (short) (getData(4) / 10); - } - - @Override - public short getPV1Current() { - return (short) (getData(6) / 10); - } - - @Override - public short getPV1Power() { - return getData(8); - } - - @Override - public short getPV2Voltage() { - return (short) (getData(5) / 10); - } - - @Override - public short getPV2Current() { - return (short) (getData(7) / 10); - } - - @Override - public short getPV2Power() { - return getData(9); - } - - @Override - public short getBatteryVoltage() { - return (short) (getData(14) / 100); - } - - @Override - public short getBatteryCurrent() { - return (short) (getData(15) / 100); - } - - @Override - public short getBatteryPower() { - return getData(16); - } - - @Override - public short getBatteryTemperature() { - return getData(17); - } - - @Override - public short getBatterySoC() { - return getData(18); - } - - @Override - public long getOnGridTotalYield() { - return packU16(11, 12) / 100; - } - - @Override - public short getOnGridDailyYield() { - return (short) (getData(13) / 10); - } - - @Override - public short getFeedInPower() { - return getData(32); - } - - @Override - public long getTotalFeedInEnergy() { - return packU16(34, 35) / 100; - } - - @Override - public long getTotalConsumption() { - return packU16(36, 37) / 100; - } - - private short getData(int index) { - try { - short[] dataArray = data; - if (dataArray != null) { - return dataArray[index]; - } - } catch (IndexOutOfBoundsException e) { - logger.debug("Tried to get data out of bounds of the raw data array.", e); - } - return 0; - } - - private long packU16(int indexMajor, int indexMinor) { - short major = getData(indexMajor); - short minor = getData(indexMinor); - if (major == 0) { - return minor; - } - - return ((major << 16) & 0xFFFF0000) | minor & 0xFFFF; - } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java index 64d8375ad4ec4..834c75285de19 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java @@ -14,16 +14,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.solax.internal.connectivity.rawdata.RawDataBean; /** - * The {@link InverterData} interface should implement the interface that returns the parsed data in human readable code - * and format. + * The {@link InverterData} Interface for the parsed inverter data in meaningful format * * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public interface InverterData extends RawDataBean { +public interface InverterData { + @Nullable String getWifiSerial(); @@ -32,61 +31,185 @@ public interface InverterData extends RawDataBean { InverterType getInverterType(); - short getInverterVoltage(); + @Nullable + String getRawData(); + + default double getPV1Voltage() { + return Short.MIN_VALUE; + } + + default double getPV1Current() { + return Short.MIN_VALUE; + } + + default short getPV1Power() { + return Short.MIN_VALUE; + } + + default double getPV2Voltage() { + return Short.MIN_VALUE; + } + + default double getPV2Current() { + return Short.MIN_VALUE; + } + + default short getPV2Power() { + return Short.MIN_VALUE; + } + + default double getPVTotalPower() { + return getPV1Power() + getPV2Power(); + } - short getInverterCurrent(); + default double getPVTotalCurrent() { + return getPV1Current() + getPV2Current(); + } - short getInverterOutputPower(); + default double getBatteryVoltage() { + return Short.MIN_VALUE; + }; - short getInverterFrequency(); + default double getBatteryCurrent() { + return Short.MIN_VALUE; + }; - short getPV1Voltage(); + default short getBatteryPower() { + return Short.MIN_VALUE; + } - short getPV1Current(); + default short getBatteryTemperature() { + return Short.MIN_VALUE; + } - short getPV1Power(); + default short getBatteryLevel() { + return Short.MIN_VALUE; + } - short getPV2Voltage(); + default short getFeedInPower() { + return Short.MIN_VALUE; + } - short getPV2Current(); + default short getPowerUsage() { + return Short.MIN_VALUE; + } - short getPV2Power(); + default double getTotalEnergy() { + return Short.MIN_VALUE; + } - default short getPVTotalPower() { - return (short) (getPV1Power() + getPV2Power()); + default short getTotalBatteryDischargeEnergy() { + return Short.MIN_VALUE; } - default short getPVTotalCurrent() { - return (short) (getPV1Current() + getPV2Current()); + default short getTotalBatteryChargeEnergy() { + return Short.MIN_VALUE; } - short getBatteryVoltage(); // V / 100 + default double getTotalPVEnergy() { + return Short.MIN_VALUE; + } - short getBatteryCurrent(); // A / 100 + default short getTotalFeedInEnergy() { + return Short.MIN_VALUE; + } - short getBatteryPower(); // W + default double getTotalConsumption() { + return Short.MIN_VALUE; + } - short getBatteryTemperature(); // temperature C + default double getTodayEnergy() { + return Short.MIN_VALUE; + } - short getBatterySoC(); // % battery SoC + default double getTodayFeedInEnergy() { + return Short.MIN_VALUE; + } - long getOnGridTotalYield(); // KWh total Yeld from the sun (to the grid?) + default double getTodayConsumption() { + return Short.MIN_VALUE; + } - short getOnGridDailyYield(); // KWh daily Yeld from the sun (to the grid?) + default double getTodayBatteryDischargeEnergy() { + return Short.MIN_VALUE; + } - long getTotalFeedInEnergy(); // KWh all times + default double getTodayBatteryChargeEnergy() { + return Short.MIN_VALUE; + } - long getTotalConsumption(); // KWh all times + default double getInverterVoltage() { + return Short.MIN_VALUE; + } - short getFeedInPower(); + default double getInverterCurrent() { + return Short.MIN_VALUE; + } + + default short getInverterOutputPower() { + return Short.MIN_VALUE; + } + + default double getInverterFrequency() { + return Short.MIN_VALUE; + } + + default double getVoltagePhase1() { + return Short.MIN_VALUE; + } + + default double getVoltagePhase2() { + return Short.MIN_VALUE; + } + + default double getVoltagePhase3() { + return Short.MIN_VALUE; + } + + default double getCurrentPhase1() { + return Short.MIN_VALUE; + } + + default double getCurrentPhase2() { + return Short.MIN_VALUE; + } + + default double getCurrentPhase3() { + return Short.MIN_VALUE; + } + + default short getOutputPowerPhase1() { + return Short.MIN_VALUE; + } + + default short getOutputPowerPhase2() { + return Short.MIN_VALUE; + } + + default short getOutputPowerPhase3() { + return Short.MIN_VALUE; + } + + default short getTotalOutputPower() { + return Short.MIN_VALUE; + } + + default double getFrequencyPhase1() { + return Short.MIN_VALUE; + } + + default double getFrequencyPhase2() { + return Short.MIN_VALUE; + } + + default double getFrequencyPhase3() { + return Short.MIN_VALUE; + } default String toStringDetailed() { return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = " - + getInverterType() + ", InverterVoltage = " + getInverterVoltage() + "V, InverterCurrent = " - + getInverterCurrent() + "A, InverterPower = " + getInverterOutputPower() + "W, BatteryPower = " - + getBatteryPower() + "W, Battery SoC = " + getBatterySoC() + "%, FeedIn Power = " + getFeedInPower() - + "W, Total PV Power = " + (getPV1Power() + getPV2Power()) + "W, Total Consumption = " - + getTotalConsumption() + "kWh, Total Feed-in Energy = " + getTotalFeedInEnergy() - + "kWh, Total On-Grid Yield = " + getOnGridTotalYield() + "kWh."; + + getInverterType() + ", BatteryPower = " + getBatteryPower() + "W, Battery SoC = " + getBatteryLevel() + + "%, FeedIn Power = " + getFeedInPower() + "W, Total PV Power = " + (getPV1Power() + getPV2Power()) + + "W"; } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java index b8131fb16baf0..f8bf2a097ff72 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java @@ -12,9 +12,15 @@ */ package org.openhab.binding.solax.internal.model; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; +import org.openhab.binding.solax.internal.model.parsers.X1HybridG4DataParser; +import org.openhab.binding.solax.internal.model.parsers.X3HybridG4DataParser; /** * The {@link InverterType} class is enum representing the different inverter types with a simple logic to convert from @@ -38,18 +44,46 @@ public enum InverterType { A1_FIT(11), A1_GRID(12), J1_ESS(13), - X3_HYBRID_G4(14), - X1_HYBRID_G4(15), + X3_HYBRID_G4(14, new X3HybridG4DataParser()), + X1_HYBRID_G4(15, new X1HybridG4DataParser()), + X3_MIC_OR_PRO_G2(16), + X1_SPT(17), + X1_BOOST_OR_MINI_G4(18), + A1_HYB_G2(19), + A1_AC_G2(20), + A1_SMT_G2(21), + X3_FTH(22), + X3_MGA_G2(23), UNKNOWN(-1); private int typeIndex; + private @Nullable RawDataParser parser; + + private Set supportedChannels = new HashSet<>(); + InverterType(int typeIndex) { + this(typeIndex, null); + } + + InverterType(int typeIndex, @Nullable RawDataParser parser) { this.typeIndex = typeIndex; + this.parser = parser; + if (parser != null) { + this.supportedChannels = parser.getSupportedChannels(); + } } public static InverterType fromIndex(int index) { InverterType[] values = InverterType.values(); return Stream.of(values).filter(value -> value.typeIndex == index).findFirst().orElse(UNKNOWN); } + + public @Nullable RawDataParser getParser() { + return parser; + } + + public Set getSupportedChannels() { + return supportedChannels; + } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java new file mode 100644 index 0000000000000..a737d2478ac9e --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.impl; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link CommonInverterData} is an abstract class that contains the common information, applicable for all + * inverters. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public abstract class CommonInverterData implements InverterData { + + private final Logger logger = LoggerFactory.getLogger(CommonInverterData.class); + + private LocalConnectRawDataBean data; + + public CommonInverterData(LocalConnectRawDataBean data) { + this.data = data; + } + + @Override + public @Nullable String getRawData() { + return data.getRawData(); + } + + @Override + public @Nullable String getWifiSerial() { + return data.getSn(); + } + + @Override + public @Nullable String getWifiVersion() { + return data.getVer(); + } + + @Override + public InverterType getInverterType() { + return InverterType.fromIndex(data.getType()); + } + + protected short getData(int index) { + try { + short[] dataArray = data.getData(); + if (dataArray != null) { + return dataArray[index]; + } + } catch (IndexOutOfBoundsException e) { + logger.debug("Tried to get data out of bounds of the raw data array.", e); + } + return 0; + } + + public long packU16(int indexMajor, int indexMinor) { + short major = getData(indexMajor); + short minor = getData(indexMinor); + if (major == 0) { + return minor; + } + + return Integer.toUnsignedLong(major << 16 | minor & 0xFFFF); + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java new file mode 100644 index 0000000000000..792187f467e5a --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.impl; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; + +/** + * The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4 + * inverter. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X1HybridG4InverterData extends CommonInverterData { + + public X1HybridG4InverterData(LocalConnectRawDataBean data) { + super(data); + } + + @Override + public double getInverterVoltage() { + return ((double) getData(0)) / 10; + } + + @Override + public double getInverterCurrent() { + return ((double) getData(1)) / 10; + } + + @Override + public short getInverterOutputPower() { + return getData(2); + } + + @Override + public double getInverterFrequency() { + return ((double) getData(3)) / 100; + } + + @Override + public short getFeedInPower() { + return getData(32); + } + + @Override + public double getPV1Voltage() { + return ((double) getData(4)) / 10; + } + + @Override + public double getPV2Voltage() { + return ((double) getData(5)) / 10; + } + + @Override + public double getPV1Current() { + return ((double) getData(6)) / 10; + } + + @Override + public double getPV2Current() { + return ((double) getData(7)) / 10; + } + + @Override + public short getPV1Power() { + return getData(8); + } + + @Override + public short getPV2Power() { + return getData(9); + } + + @Override + public double getBatteryVoltage() { + return ((double) getData(14)) / 100; + } + + @Override + public double getBatteryCurrent() { + return ((double) getData(15)) / 100; + } + + @Override + public short getBatteryPower() { + return getData(16); + } + + @Override + public short getBatteryTemperature() { + return getData(17); + } + + @Override + public short getBatteryLevel() { + return getData(18); + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java new file mode 100644 index 0000000000000..4c5673cb502b6 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.impl; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; + +/** + * The {@link X3HybridG4InverterData} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X3HybridG4InverterData extends CommonInverterData { + + public X3HybridG4InverterData(LocalConnectRawDataBean data) { + super(data); + } + + // Inverter data + + @Override + public double getVoltagePhase1() { + return ((double) getData(0)) / 10; + } + + @Override + public double getVoltagePhase2() { + return ((double) getData(1)) / 10; + } + + @Override + public double getVoltagePhase3() { + return ((double) getData(2)) / 10; + } + + @Override + public double getCurrentPhase1() { + return ((double) getData(3)) / 10; + } + + @Override + public double getCurrentPhase2() { + return ((double) getData(4)) / 10; + } + + @Override + public double getCurrentPhase3() { + return ((double) getData(5)) / 10; + } + + @Override + public short getOutputPowerPhase1() { + return getData(6); + } + + @Override + public short getOutputPowerPhase2() { + return getData(7); + } + + @Override + public short getOutputPowerPhase3() { + return getData(8); + } + + @Override + public short getTotalOutputPower() { + return getData(9); + } + + @Override + public double getPV1Voltage() { + return ((double) getData(10)) / 10; + } + + @Override + public double getPV2Voltage() { + return ((double) getData(11)) / 10; + } + + @Override + public double getPV1Current() { + return ((double) getData(12)) / 10; + } + + @Override + public double getPV2Current() { + return ((double) getData(13)) / 10; + } + + @Override + public short getPV1Power() { + return getData(14); + } + + @Override + public short getPV2Power() { + return getData(15); + } + + @Override + public double getFrequencyPhase1() { + return ((double) getData(16)) / 100; + } + + @Override + public double getFrequencyPhase2() { + return ((double) getData(17)) / 100; + } + + @Override + public double getFrequencyPhase3() { + return ((double) getData(18)) / 100; + } + + // Battery + + @Override + public double getBatteryVoltage() { + return ((double) getData(39)) / 100; + } + + @Override + public double getBatteryCurrent() { + return ((double) getData(40)) / 100; + } + + @Override + public short getBatteryPower() { + return getData(41); + } + + @Override + public short getBatteryTemperature() { + return getData(105); + } + + @Override + public short getBatteryLevel() { + return getData(103); + } + + // Feed in power + + @Override + public short getFeedInPower() { + return (short) (getData(34) - getData(35)); + } + + // Totals + + @Override + public short getPowerUsage() { + return getData(47); + } + + @Override + public double getTotalEnergy() { + return ((double) getData(68)) / 10; + } + + @Override + public short getTotalBatteryDischargeEnergy() { + return getData(74); + } + + @Override + public short getTotalBatteryChargeEnergy() { + return getData(76); + } + + @Override + public double getTotalPVEnergy() { + return ((double) getData(80)) / 10; + } + + @Override + public short getTotalFeedInEnergy() { + return getData(86); + } + + @Override + public double getTotalConsumption() { + return ((double) getData(88)) / 10; + } + + @Override + public double getTodayEnergy() { + return ((double) getData(82)) / 10; + } + + @Override + public double getTodayFeedInEnergy() { + return ((double) getData(90)) / 100; + } + + @Override + public double getTodayConsumption() { + return ((double) getData(92)) / 100; + } + + @Override + public double getTodayBatteryDischargeEnergy() { + return ((double) getData(78)) / 10; + } + + @Override + public double getTodayBatteryChargeEnergy() { + return ((double) getData(79)) / 10; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java new file mode 100644 index 0000000000000..c7547ce7641ab --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.parsers; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; + +/** + * The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which + * is common for all inverters. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public interface RawDataParser { + + InverterData getData(LocalConnectRawDataBean bean); + + Set getSupportedChannels(); +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java new file mode 100644 index 0000000000000..39444ba218bca --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.parsers; + +import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.impl.X1HybridG4InverterData; + +/** + * The {@link SinglePhaseDataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the + * X1 Hybrid G4 inverter. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X1HybridG4DataParser implements RawDataParser { + + private static final Set X1_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER, + CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER, + CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER, + CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT, + CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP, + CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER, CHANNEL_INVERTER_OUTPUT_CURRENT, + CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY); + + @Override + public InverterData getData(LocalConnectRawDataBean rawData) { + return new X1HybridG4InverterData(rawData); + } + + @Override + public Set getSupportedChannels() { + return X1_HYBRID_G4_SUPPORTED_CHANNELS; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java new file mode 100644 index 0000000000000..ff6cd90631e2f --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.parsers; + +import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.impl.X3HybridG4InverterData; + +/** + * The {@link X3HybridG4DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the + * X3 Hybrid G4 inverter. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X3HybridG4DataParser implements RawDataParser { + + private static final Set X3_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER, + CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER, + CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER, + CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT, + CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP, + CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, + CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, + CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, + CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, + CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, + CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, + CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_POWER_USAGE, CHANNEL_TOTAL_ENERGY, + CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, CHANNEL_TOTAL_PV_ENERGY, CHANNEL_TOTAL_CONSUMPTION, + CHANNEL_TODAY_ENERGY, CHANNEL_TODAY_FEED_IN_ENERGY, CHANNEL_TODAY_CONSUMPTION, + CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY); + + @Override + public InverterData getData(LocalConnectRawDataBean rawData) { + return new X3HybridG4InverterData(rawData); + } + + @Override + public Set getSupportedChannels() { + return X3_HYBRID_G4_SUPPORTED_CHANNELS; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties index 3b132065b4704..518f83a8399be 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties @@ -17,14 +17,42 @@ thing-type.solax.local-connect-inverter.channel.battery-temperature.label = Batt thing-type.solax.local-connect-inverter.channel.battery-temperature.description = Temperature of the battery thing-type.solax.local-connect-inverter.channel.battery-voltage.label = Battery Voltage thing-type.solax.local-connect-inverter.channel.battery-voltage.description = Electric voltage of the battery -thing-type.solax.local-connect-inverter.channel.feed-in-power.label = Feed-in Power +thing-type.solax.local-connect-inverter.channel.feed-in-power.label = Feed-In Power thing-type.solax.local-connect-inverter.channel.feed-in-power.description = Power to/from the electricity network. thing-type.solax.local-connect-inverter.channel.inverter-current.label = Inverter Input/Output Current thing-type.solax.local-connect-inverter.channel.inverter-current.description = Current to/from the inverter +thing-type.solax.local-connect-inverter.channel.inverter-current-phase1.label = Inverter Input/Output Current Phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase1.description = Current to/from the inverter phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase2.label = Inverter Input/Output Current Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase2.description = Current to/from the inverter phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase3.label = Inverter Input/Output Current Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase3.description = Current to/from the inverter phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.label = Inverter Voltage Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.label = Inverter Voltage Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.label = Inverter Voltage Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.description = Voltage of the inverter's phase 3 thing-type.solax.local-connect-inverter.channel.inverter-output-power.label = Inverter Input/Output Power thing-type.solax.local-connect-inverter.channel.inverter-output-power.description = Power to/from the inverter +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase1.label = Inverter Input/Output Power Phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase1.description = Power to/from the inverter phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase2.label = Inverter Input/Output Power Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase2.description = Power to/from the inverter phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase3.label = Inverter Input/Output Power Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase3.description = Power to/from the inverter phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-total-output-power.label = Inverter Input/Output Total Power +thing-type.solax.local-connect-inverter.channel.inverter-total-output-power.description = Power to/from the inverter on all phases thing-type.solax.local-connect-inverter.channel.inverter-voltage.label = Inverter Voltage thing-type.solax.local-connect-inverter.channel.inverter-voltage.description = Voltage of the inverter +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase1.label = Inverter Voltage Phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase1.description = Voltage of the inverter's phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase2.label = Inverter Voltage Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase2.description = Voltage of the inverter's phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase3.label = Inverter Voltage Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase3.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.power-usage.label = Power Usage +thing-type.solax.local-connect-inverter.channel.power-usage.description = Current power consumption of the building thing-type.solax.local-connect-inverter.channel.pv-total-current.label = PV Total Current thing-type.solax.local-connect-inverter.channel.pv-total-current.description = The sum of PV currents from all strings thing-type.solax.local-connect-inverter.channel.pv-total-power.label = PV Total Power @@ -41,6 +69,28 @@ thing-type.solax.local-connect-inverter.channel.pv2-power.label = PV 2 Power thing-type.solax.local-connect-inverter.channel.pv2-power.description = Electric power of PV String 2 thing-type.solax.local-connect-inverter.channel.pv2-voltage.label = PV 2 Voltage thing-type.solax.local-connect-inverter.channel.pv2-voltage.description = Electric voltage of PV String 2 +thing-type.solax.local-connect-inverter.channel.today-battery-charge-energy.label = Today Battery Charge Energy +thing-type.solax.local-connect-inverter.channel.today-battery-charge-energy.description = Total energy charged to the battery for the day +thing-type.solax.local-connect-inverter.channel.today-battery-discharge-energy.label = Today Battery Discharge Energy +thing-type.solax.local-connect-inverter.channel.today-battery-discharge-energy.description = Total energy from the battery output for the day +thing-type.solax.local-connect-inverter.channel.today-consumption.label = Today Consumption +thing-type.solax.local-connect-inverter.channel.today-consumption.description = Energy consumed for the day +thing-type.solax.local-connect-inverter.channel.today-energy.label = Today Energy +thing-type.solax.local-connect-inverter.channel.today-energy.description = Energy output from the inverter for the day +thing-type.solax.local-connect-inverter.channel.today-feed-in-energy.label = Today Feed-In Energy +thing-type.solax.local-connect-inverter.channel.today-feed-in-energy.description = Energy consumed from the electricity provider for the day +thing-type.solax.local-connect-inverter.channel.total-battery-charge-energy.label = Total Battery Charge Energy +thing-type.solax.local-connect-inverter.channel.total-battery-charge-energy.description = Total energy charged to the battery +thing-type.solax.local-connect-inverter.channel.total-battery-discharge-energy.label = Total Battery Discharge Energy +thing-type.solax.local-connect-inverter.channel.total-battery-discharge-energy.description = Total energy from the battery output +thing-type.solax.local-connect-inverter.channel.total-consumption.label = Total Consumption +thing-type.solax.local-connect-inverter.channel.total-consumption.description = Total energy consumed from the building +thing-type.solax.local-connect-inverter.channel.total-energy.label = Total Energy +thing-type.solax.local-connect-inverter.channel.total-energy.description = Total energy output from the inverter +thing-type.solax.local-connect-inverter.channel.total-feed-in-energy.label = Total Feed-In Consumption +thing-type.solax.local-connect-inverter.channel.total-feed-in-energy.description = Total energy consumed from the electricity provider +thing-type.solax.local-connect-inverter.channel.total-pv-energy.label = Total PV Energy +thing-type.solax.local-connect-inverter.channel.total-pv-energy.description = Total energy produced by the PV # thing types config @@ -65,3 +115,4 @@ channel-type.solax.raw-data-type.description = The raw JSON data retrieved from # thing status descriptions offline.communication-error.json-cannot-be-retrieved = JSON data could not be retrieved. +offline.configuration-error.parser-not-implemented = Parser for inverter of type {0} is not implemented. diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml index 8a3c4f7982c48..67e4b20f18ad6 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml @@ -10,6 +10,7 @@ The inverter representation that supports local connections via HTTP + Power to/from the inverter @@ -24,6 +25,61 @@ + + + + Power to/from the inverter phase 1 + + + + Power to/from the inverter phase 2 + + + + Power to/from the inverter phase 3 + + + + Power to/from the inverter on all phases + + + + Current to/from the inverter phase 1 + + + + Current to/from the inverter phase 2 + + + + Current to/from the inverter phase 3 + + + + Voltage of the inverter's phase 1 + + + + Voltage of the inverter's phase 2 + + + + Voltage of the inverter's phase 3 + + + + Voltage of the inverter's phase 3 + + + + Voltage of the inverter's phase 3 + + + + Voltage of the inverter's phase 3 + + + Electric voltage of PV String 1 @@ -77,17 +133,69 @@ The battery state of charge in percent - - + Power to/from the electricity network. + + + Current power consumption of the building + + + + Total energy output from the inverter + + + + Total energy from the battery output + + + + Total energy charged to the battery + + + + Total energy produced by the PV + + + + Total energy consumed from the building + + + + Total energy consumed from the electricity provider + + + + Energy output from the inverter for the day + + + + Total energy from the battery output for the day + + + + Total energy charged to the battery for the day + + + + Energy consumed from the electricity provider for the day + + + + Energy consumed for the day + + + + 1 + + diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml new file mode 100644 index 0000000000000..1a1e54416d1fb --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml @@ -0,0 +1,134 @@ + + + + + + system:electric-power + + Power to/from the inverter phase 1 + + + system:electric-power + + Power to/from the inverter phase 2 + + + system:electric-power + + Power to/from the inverter phase 3 + + + system:electric-power + + Power to/from the inverter on all phases + + + system:electric-current + + Current to/from the inverter phase 1 + + + system:electric-current + + Current to/from the inverter phase 2 + + + system:electric-current + + Current to/from the inverter phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 1 + + + system:electric-voltage + + Voltage of the inverter's phase 2 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-power + + Current power consumption of the building + + + system:electric-energy + + Total energy output from the inverter + + + system:electric-energy + + Total energy from the battery output + + + system:electric-energy + + Total energy charged to the battery + + + system:electric-energy + + Total energy produced by the PV + + + system:electric-energy + + Total energy consumed from the building + + + system:electric-energy + + Total energy consumed from the electricity provider + + + system:electric-energy + + Energy output from the inverter for the day + + + system:electric-energy + + Total energy from the battery output for the day + + + system:electric-energy + + Total energy charged to the battery for the day + + + system:electric-energy + + Energy consumed from the electricity provider for the day + + + system:electric-energy + + Energy consumed for the day + + + + diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java new file mode 100644 index 0000000000000..5253a1275a25e --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; + +/** + * The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class TestX1HybridG4Parser { + + private static final String RAW_DATA = """ + { + sn:SOME_SERIAL_NUMBER, + ver:3.008.10, + type:15, + Data:[ + 2388,21,460,4998,4483,4483,10,1,487,65, + 2,59781,0,70,12180,500,605,33,99,12000, + 0,23159,0,57,100,0,39,4501,0,0, + 0,0,12,0,13240,0,63348,2,448,43, + 256,1314,900,0,350,311,279,33,33,279,1,1,652,0,708,1,65077,65535,65386,65535,0,0,0,0,0,0,0,0,0,0,0,0,65068,65535,4500,0,61036,65535,10,0,90,0,0,12,0,116,7,57,0,0,2320,0,110,0,0,0,0,0,0,12544,7440,5896,594,521,9252,0,0,0,0,0,1,1201,0,0,3342,3336,7296,54,21302,14389,18753,12852,16692,12355,13618,21302,14389,18753,12852,16692,12355,13618,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1025,4609,1026,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + Information:[7.500,15,H4752TI1063020,8,1.24,0.00,1.21,1.03,0.00,1]} + """; + + @Test + public void testParser() { + LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(RAW_DATA); + int type = bean.getType(); + InverterType inverterType = InverterType.fromIndex(type); + assertEquals(InverterType.X1_HYBRID_G4, inverterType, "Inverter type not recognized properly"); + + RawDataParser parser = inverterType.getParser(); + assertNotNull(parser); + + InverterData data = parser.getData(bean); + assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial()); + assertEquals("3.008.10", data.getWifiVersion()); + + assertEquals(238.8, data.getInverterVoltage()); // [0] + assertEquals(2.1, data.getInverterCurrent()); // [1] + assertEquals(460, data.getInverterOutputPower()); // [2] + assertEquals(49.98, data.getInverterFrequency()); // [3] + + assertEquals(448.3, data.getPV1Voltage()); // [4] + assertEquals(448.3, data.getPV2Voltage()); // [5] + assertEquals(1, data.getPV1Current()); // [6] + assertEquals(0.1, data.getPV2Current()); // [7] + assertEquals(487, data.getPV1Power()); // [8] + assertEquals(65, data.getPV2Power()); // [9] + + assertEquals(121.8, data.getBatteryVoltage()); // [14] + assertEquals(5, data.getBatteryCurrent()); // [15] + assertEquals(605, data.getBatteryPower()); // [16] + assertEquals(33, data.getBatteryTemperature()); // [17] + assertEquals(99, data.getBatteryLevel()); // [18] + + assertEquals(12, data.getFeedInPower()); // [32] + } + + // Yield_Today: Data[13] / 10, + // Yield_Total: read32BitUnsigned(Data[11], Data[12]) / 10, + // PowerDc1: Data[8], + // PowerDc2: Data[9], + // BAT_Power: read16BitSigned(Data[16]), + // feedInPower: read32BitSigned(Data[32], Data[33]), + // GridAPower: read16BitSigned(Data[2]), + // FeedInEnergy: read32BitUnsigned(Data[34], Data[35]) / 100, + // ConsumeEnergy: read32BitUnsigned(Data[36], Data[37]) / 100, + // RunMode: Data[10], + // EPSAPower: read16BitSigned(Data[28]), + // Vdc1: Data[4] / 10, + // Vdc2: Data[5] / 10, + // Idc1: Data[6] / 10, + // Idc2: Data[7] / 10, + // EPSAVoltage: Data[29] / 10, + // EPSACurrent: read16BitSigned(Data[30]) / 10, + // BatteryCapacity: Data[18], + // BatteryVoltage: Data[14] / 100, + // BatteryTemperature: read16BitSigned(Data[17]), + // GridAVoltage: Data[0] / 10, + // GridACurrent: read16BitSigned(Data[1]) / 10, + // FreqacA: Data[3] / 100, +} diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java new file mode 100644 index 0000000000000..55ee2723436ae --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; + +/** + * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class TestX3HybridG4Parser { + + String rawData = """ + { + sn:XYZ, + ver:3.005.01, + type:14,Data:[ + 2316,2329,2315,18,18,18,372,363,365,1100, + 12,23,34,45,56,67,4996,4996,4996,2, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,65494,65535,0,0,0,31330, + 320,1034,3078,1,44,1100,256,1294,0,0, + 7445,5895,100,0,38,0,0,0,0,0, + 0,0,0,0,0,0,0,0,505,0, + 396,0,0,0,102,0,142,0,62,110, + 570,0,463,0,0,0,1925,0,369,0, + 506,1925,304,309,0,0,0,0,0,0, + 0,0,0,45,1,59,1,34,54,256, + 3504,2400,300,300,295,276,33,33,2,1620,779,15163,15163,14906,0,0,0,3270,3264,45581,0,20564,12339,18753,12353,18742,12356,13625,20564,12339,18754,12866,18743,14151,13104,20564,12339,18754,12866,18743,14151,12592,20564,12339,18754,12865,18738,12871,13620,0,0,0,0,0,0,0,1025,8195,769,259,0,31460,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + Information:[12.000,14,XY,8,1.23,0.00,1.24,1.09,0.00,1] + } + """; + + @Test + public void testParser() { + LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData); + int type = bean.getType(); + InverterType inverterType = InverterType.fromIndex(type); + assertEquals(InverterType.X3_HYBRID_G4, inverterType, "Inverter type not recognized properly"); + + RawDataParser parser = inverterType.getParser(); + assertNotNull(parser); + + InverterData data = parser.getData(bean); + assertEquals("XYZ", data.getWifiSerial()); + assertEquals("3.005.01", data.getWifiVersion()); + + assertEquals(231.6, data.getVoltagePhase1()); // [0] + assertEquals(232.9, data.getVoltagePhase2()); // [1] + assertEquals(231.5, data.getVoltagePhase3()); // [2] + + assertEquals(1.8, data.getCurrentPhase1()); // [3] + assertEquals(1.8, data.getCurrentPhase2()); // [4] + assertEquals(1.8, data.getCurrentPhase3()); // [5] + + assertEquals(372, data.getOutputPowerPhase1()); // [6] + assertEquals(363, data.getOutputPowerPhase2()); // [7] + assertEquals(365, data.getOutputPowerPhase3()); // [8] + + assertEquals(1100, data.getTotalOutputPower()); // [9] + + assertEquals(1.2, data.getPV1Voltage()); // [10] + assertEquals(2.3, data.getPV2Voltage()); // [11] + assertEquals(3.4, data.getPV1Current()); // [12] + assertEquals(4.5, data.getPV2Current()); // [13] + assertEquals(56, data.getPV1Power()); // [14] + assertEquals(67, data.getPV2Power()); // [15] + + assertEquals(49.96, data.getFrequencyPhase1()); // [16] + assertEquals(49.96, data.getFrequencyPhase2()); // [17] + assertEquals(49.96, data.getFrequencyPhase3()); // [18] + + assertEquals(-41, data.getFeedInPower()); // [34] - [35] + + assertEquals(313.3, data.getBatteryVoltage()); // [39] + assertEquals(3.2, data.getBatteryCurrent()); // [40] + assertEquals(1034, data.getBatteryPower()); // [41] + assertEquals(45, data.getBatteryLevel()); // [103] + assertEquals(59, data.getBatteryTemperature()); // [105] + + // Totals + assertEquals(1294, data.getPowerUsage()); // [47] + assertEquals(50.5, data.getTotalEnergy()); // [68] + assertEquals(102, data.getTotalBatteryDischargeEnergy()); // [74] + assertEquals(142, data.getTotalBatteryChargeEnergy()); // [76] + assertEquals(57, data.getTotalPVEnergy()); // [80] + assertEquals(1925, data.getTotalFeedInEnergy()); // [86] + assertEquals(36.9, data.getTotalConsumption()); // [88] + assertEquals(46.3, data.getTodayEnergy()); // [82] / 10 + assertEquals(5.06, data.getTodayFeedInEnergy()); // [90] / 100 + assertEquals(3.04, data.getTodayConsumption()); // [92] / 100 + assertEquals(6.2, data.getTodayBatteryDischargeEnergy()); // [78] / 100 + assertEquals(11, data.getTodayBatteryChargeEnergy()); // [79] / 100 + } +} From cfa5e27e81a4125592cd0dc6512d087148b94779 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Wed, 8 Nov 2023 20:31:44 +0100 Subject: [PATCH 062/146] Fix build after core changes (#15869) Signed-off-by: Jan N. Klug --- .../test/java/org/openhab/binding/icloud/TestICloud.java | 5 +++++ .../persistence/influxdb/InfluxDBPersistenceService.java | 1 + .../inmemory/internal/InMemoryPersistenceService.java | 5 +++++ .../persistence/jdbc/internal/JdbcPersistenceService.java | 6 ++++++ 4 files changed, 17 insertions(+) diff --git a/bundles/org.openhab.binding.icloud/src/test/java/org/openhab/binding/icloud/TestICloud.java b/bundles/org.openhab.binding.icloud/src/test/java/org/openhab/binding/icloud/TestICloud.java index 86c9a238cd137..152a08eb8d619 100644 --- a/bundles/org.openhab.binding.icloud/src/test/java/org/openhab/binding/icloud/TestICloud.java +++ b/bundles/org.openhab.binding.icloud/src/test/java/org/openhab/binding/icloud/TestICloud.java @@ -53,6 +53,7 @@ import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.State; +import org.openhab.core.types.TimeSeries; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -465,6 +466,10 @@ public void stateUpdated(ChannelUID channelUID, State state) { public void postCommand(ChannelUID channelUID, Command command) { } + @Override + public void sendTimeSeries(ChannelUID channelUID, TimeSeries timeSeries) { + } + @Override public void migrateThingType(Thing thing, ThingTypeUID thingTypeUID, Configuration configuration) { } diff --git a/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java b/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java index b2f66ecf7ec91..358bd778a2eb2 100644 --- a/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java +++ b/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java @@ -199,6 +199,7 @@ public void store(Item item, ZonedDateTime date, State state) { store(item, date, state, null); } + @Override public void store(Item item, ZonedDateTime date, State state, @Nullable String alias) { if (!serviceActivated) { logger.warn("InfluxDB service not ready. Storing {} rejected.", item); diff --git a/bundles/org.openhab.persistence.inmemory/src/main/java/org/openhab/persistence/inmemory/internal/InMemoryPersistenceService.java b/bundles/org.openhab.persistence.inmemory/src/main/java/org/openhab/persistence/inmemory/internal/InMemoryPersistenceService.java index cffb2ce414b54..8564a7b6e9b97 100644 --- a/bundles/org.openhab.persistence.inmemory/src/main/java/org/openhab/persistence/inmemory/internal/InMemoryPersistenceService.java +++ b/bundles/org.openhab.persistence.inmemory/src/main/java/org/openhab/persistence/inmemory/internal/InMemoryPersistenceService.java @@ -131,6 +131,11 @@ public void store(Item item, ZonedDateTime date, State state) { internalStore(item.getName(), date, state); } + @Override + public void store(Item item, ZonedDateTime date, State state, @Nullable String alias) { + internalStore(Objects.requireNonNullElse(alias, item.getName()), date, state); + } + @Override public boolean remove(FilterCriteria filter) throws IllegalArgumentException { String itemName = filter.getItemName(); diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java index 73f3f29e51c49..a224aa2efaacc 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java @@ -151,6 +151,12 @@ public void store(Item item, ZonedDateTime date, State state) { scheduler.execute(() -> internalStore(item, date, state)); } + @Override + public void store(Item item, ZonedDateTime date, State state, @Nullable String alias) { + // alias is not supported + scheduler.execute(() -> internalStore(item, null, item.getState())); + } + private synchronized void internalStore(Item item, @Nullable ZonedDateTime date, State state) { // Do not store undefined/uninitialized data if (state instanceof UnDefType) { From cd8caeb0c9042f63288424c301ac7991dafb9adc Mon Sep 17 00:00:00 2001 From: truidix Date: Wed, 8 Nov 2023 21:39:59 +0100 Subject: [PATCH 063/146] [miio] Add a few new vacuum robots and station channels (#15704) * Added a few new channels: - cleaning the mop - check if mop drying is enabled - remaining time for mop drying Signed-off-by: David Kumar --- .../org.openhab.binding.miio/README.base.md | 7 ++ bundles/org.openhab.binding.miio/README.md | 7 ++ .../miio/internal/MiIoBindingConstants.java | 4 ++ .../binding/miio/internal/MiIoCommand.java | 3 + .../internal/handler/MiIoVacuumHandler.java | 59 +++++++++++++++-- .../miio/internal/robot/DockStatusType.java | 66 +++++++++++++++++++ .../internal/robot/RobotCababilities.java | 9 ++- .../miio/internal/robot/StatusDTO.java | 25 +++++++ .../resources/OH-INF/i18n/miio.properties | 35 ++++++++-- .../resources/OH-INF/thing/vacuumThing.xml | 34 ++++++++++ 10 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/DockStatusType.java diff --git a/bundles/org.openhab.binding.miio/README.base.md b/bundles/org.openhab.binding.miio/README.base.md index 943bf86a96634..454138ad94e96 100644 --- a/bundles/org.openhab.binding.miio/README.base.md +++ b/bundles/org.openhab.binding.miio/README.base.md @@ -252,6 +252,13 @@ Additionally depending on the capabilities of your robot vacuum other channels m | Switch | status#mop_forbidden_enable | Mop Forbidden | | Switch | status#is_locating | Robot is locating | | Number | actions#segment | Room Clean (enter room #) | +| Switch | actions#collect_dust | Start collecting dust | +| Switch | actions#clean_mop_start | Start mop wash | +| Switch | actions#clean_mop_stop | Stop mop wash | +| Number | status#mop_drying_time | Mop drying Time | +| Switch | status#is_mop_drying | Mop cleaning active | +| Number | status#dock_state_id | Dock status id | +| String | status#dock_state | Dock status message | Note: cleaning map is only available with cloud access. diff --git a/bundles/org.openhab.binding.miio/README.md b/bundles/org.openhab.binding.miio/README.md index 0af243ddc8070..761a80f3a2b3c 100644 --- a/bundles/org.openhab.binding.miio/README.md +++ b/bundles/org.openhab.binding.miio/README.md @@ -646,6 +646,13 @@ Additionally depending on the capabilities of your robot vacuum other channels m | Switch | status#mop_forbidden_enable | Mop Forbidden | | Switch | status#is_locating | Robot is locating | | Number | actions#segment | Room Clean (enter room #) | +| Switch | actions#collect_dust | Start collecting dust | +| Switch | actions#clean_mop_start | Start mop wash | +| Switch | actions#clean_mop_stop | Stop mop wash | +| Number | status#mop_drying_time | Mop drying Time | +| Switch | status#is_mop_drying | Mop cleaning active | +| Number | status#dock_state_id | Dock status id | +| String | status#dock_state | Dock status message | Note: cleaning map is only available with cloud access. diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java index a81c3a793a70c..e015c92a73498 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java @@ -61,6 +61,8 @@ public final class MiIoBindingConstants { public static final String CHANNEL_MAP_PRESENT = "status#map_present"; public static final String CHANNEL_STATE = "status#state"; public static final String CHANNEL_STATE_ID = "status#state_id"; + public static final String CHANNEL_DOCK_STATE = "status#dock_state"; + public static final String CHANNEL_DOCK_STATE_ID = "status#dock_state_id"; public static final String CHANNEL_CONTROL = "actions#control"; public static final String CHANNEL_COMMAND = "actions#commands"; @@ -94,6 +96,8 @@ public final class MiIoBindingConstants { public static final String CHANNEL_HISTORY_TOTALAREA = "history#total_clean_area"; public static final String CHANNEL_HISTORY_COUNT = "history#total_clean_count"; + public static final String CHANNEL_MOP_TOTALDRYTIME = "status#mop_drying_time"; + public static final String CHANNEL_HISTORY_START_TIME = "cleaning#last_clean_start_time"; public static final String CHANNEL_HISTORY_END_TIME = "cleaning#last_clean_end_time"; public static final String CHANNEL_HISTORY_AREA = "cleaning#last_clean_area"; diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoCommand.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoCommand.java index 4435a6a069608..6616fa51b3a96 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoCommand.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoCommand.java @@ -96,6 +96,9 @@ public enum MiIoCommand { GET_CUSTOMIZED_CLEAN_MODE("get_customize_clean_mode"), GET_MULTI_MAP_LIST("get_multi_maps_list"), GET_ROOM_MAPPING("get_room_mapping"), + SET_COLLECT_DUST("app_start_collect_dust"), + SET_CLEAN_MOP_START("app_start_wash"), + SET_CLEAN_MOP_STOP("app_stop_wash"), // Gateway & child device commands GET_ARMING("get_arming"), diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java index 1cb9d403fbc94..a36bcf4630441 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java @@ -46,6 +46,7 @@ import org.openhab.binding.miio.internal.cloud.HomeRoomDTO; import org.openhab.binding.miio.internal.cloud.MiCloudException; import org.openhab.binding.miio.internal.robot.ConsumablesType; +import org.openhab.binding.miio.internal.robot.DockStatusType; import org.openhab.binding.miio.internal.robot.FanModeType; import org.openhab.binding.miio.internal.robot.HistoryRecordDTO; import org.openhab.binding.miio.internal.robot.RRMapDraw; @@ -100,11 +101,12 @@ public class MiIoVacuumHandler extends MiIoAbstractHandler { private static final Gson GSON = new GsonBuilder().serializeNulls().create(); private final ChannelUID mapChannelUid; - private static final Set FEATURES_CHANNELS = Collections.unmodifiableSet(Stream - .of(RobotCababilities.SEGMENT_STATUS, RobotCababilities.MAP_STATUS, RobotCababilities.LED_STATUS, - RobotCababilities.CARPET_MODE, RobotCababilities.FW_FEATURES, RobotCababilities.ROOM_MAPPING, - RobotCababilities.MULTI_MAP_LIST, RobotCababilities.CUSTOMIZE_CLEAN_MODE) - .collect(Collectors.toSet())); + private static final Set FEATURES_CHANNELS = Collections.unmodifiableSet(Stream.of( + RobotCababilities.SEGMENT_STATUS, RobotCababilities.MAP_STATUS, RobotCababilities.LED_STATUS, + RobotCababilities.CARPET_MODE, RobotCababilities.FW_FEATURES, RobotCababilities.ROOM_MAPPING, + RobotCababilities.MULTI_MAP_LIST, RobotCababilities.CUSTOMIZE_CLEAN_MODE, RobotCababilities.COLLECT_DUST, + RobotCababilities.CLEAN_MOP_START, RobotCababilities.CLEAN_MOP_STOP, RobotCababilities.MOP_DRYING, + RobotCababilities.MOP_DRYING_REMAINING_TIME, RobotCababilities.DOCK_STATE_ID).collect(Collectors.toSet())); private ExpiringCache status; private ExpiringCache consumables; @@ -242,6 +244,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { forceStatusUpdate(); return; } + if (channelUID.getId().equals(RobotCababilities.WATERBOX_MODE.getChannel())) { sendCommand(MiIoCommand.SET_WATERBOX_MODE, "[" + command.toString() + "]"); forceStatusUpdate(); @@ -265,6 +268,26 @@ public void handleCommand(ChannelUID channelUID, Command command) { sendCommand(MiIoCommand.CONSUMABLES_RESET, "[" + command.toString() + "]"); updateState(CHANNEL_CONSUMABLE_RESET, new StringType("none")); } + + if (channelUID.getId().equals(RobotCababilities.COLLECT_DUST.getChannel()) && !command.toString().isEmpty() + && !command.toString().contentEquals("-")) { + sendCommand(MiIoCommand.SET_COLLECT_DUST); + forceStatusUpdate(); + return; + } + + if (channelUID.getId().equals(RobotCababilities.CLEAN_MOP_START.getChannel()) && !command.toString().isEmpty() + && !command.toString().contentEquals("-")) { + sendCommand(MiIoCommand.SET_CLEAN_MOP_START); + forceStatusUpdate(); + return; + } + if (channelUID.getId().equals(RobotCababilities.CLEAN_MOP_STOP.getChannel()) && !command.toString().isEmpty() + && !command.toString().contentEquals("-")) { + sendCommand(MiIoCommand.SET_CLEAN_MOP_STOP); + forceStatusUpdate(); + return; + } } private void forceStatusUpdate() { @@ -352,6 +375,11 @@ private boolean updateVacuumStatus(JsonObject statusData) { } updateState(CHANNEL_VACUUM, vacuum); } + if (this.deviceCapabilities.containsKey(RobotCababilities.DOCK_STATE_ID)) { + DockStatusType state = DockStatusType.getType(statusInfo.getDockErrorStatus().intValue()); + updateState(CHANNEL_DOCK_STATE, new StringType(state.getDescription())); + updateState(CHANNEL_DOCK_STATE_ID, new DecimalType(state.getId())); + } if (deviceCapabilities.containsKey(RobotCababilities.WATERBOX_MODE)) { safeUpdateState(RobotCababilities.WATERBOX_MODE.getChannel(), statusInfo.getWaterBoxMode()); } @@ -370,6 +398,22 @@ private boolean updateVacuumStatus(JsonObject statusData) { if (deviceCapabilities.containsKey(RobotCababilities.LOCATING)) { safeUpdateState(RobotCababilities.LOCATING.getChannel(), statusInfo.getIsLocating()); } + if (deviceCapabilities.containsKey(RobotCababilities.CLEAN_MOP_START)) { + safeUpdateState(RobotCababilities.CLEAN_MOP_START.getChannel(), 0); + } + if (deviceCapabilities.containsKey(RobotCababilities.CLEAN_MOP_STOP)) { + safeUpdateState(RobotCababilities.CLEAN_MOP_STOP.getChannel(), 0); + } + if (deviceCapabilities.containsKey(RobotCababilities.COLLECT_DUST)) { + safeUpdateState(RobotCababilities.COLLECT_DUST.getChannel(), 0); + } + if (deviceCapabilities.containsKey(RobotCababilities.MOP_DRYING)) { + safeUpdateState(RobotCababilities.MOP_DRYING.getChannel(), statusInfo.getIsMopDryingActive()); + } + if (deviceCapabilities.containsKey(RobotCababilities.MOP_DRYING_REMAINING_TIME)) { + updateState(CHANNEL_MOP_TOTALDRYTIME, + new QuantityType<>(TimeUnit.SECONDS.toMinutes(statusInfo.getMopDryTime()), Units.MINUTE)); + } return true; } @@ -690,6 +734,11 @@ public void onMessageReceived(MiIoSendCommand response) { case GET_FW_FEATURES: case GET_CUSTOMIZED_CLEAN_MODE: case GET_MULTI_MAP_LIST: + + case SET_COLLECT_DUST: + case SET_CLEAN_MOP_START: + case SET_CLEAN_MOP_STOP: + for (RobotCababilities cmd : FEATURES_CHANNELS) { if (response.getCommand().getCommand().contentEquals(cmd.getCommand())) { updateState(cmd.getChannel(), new StringType(response.getResult().toString())); diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/DockStatusType.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/DockStatusType.java new file mode 100644 index 0000000000000..4cd1d1b5cb54f --- /dev/null +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/DockStatusType.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.miio.internal.robot; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * List of dockingstation status + * + * @author David Kumar - Initial contribution + */ +@NonNullByDefault +public enum DockStatusType { + UNKNOWN(-1, "Unknown"), + OK(0, "OK"), + ERROR_SUCTION(34, "Suction Error"), + ERROR_FRESH_WATER_TANK(38, "Error fresh water tank"), + ERROR_FRESH_DIRTY_WATER_TANK(39, "Error dirty water tank"), + ERROR_DUST_CONTAINER(46, "Missing dust container/dust bag"); + + private final int id; + private final String description; + + DockStatusType(int id, String description) { + this.id = id; + this.description = description; + } + + public int getId() { + return this.id; + } + + public static DockStatusType getType(int value) { + byte b; + int i; + DockStatusType[] arrayOfDockStatusType; + for (i = (arrayOfDockStatusType = values()).length, b = 0; b < i;) { + DockStatusType st = arrayOfDockStatusType[b]; + if (st.getId() == value) { + return st; + } + b++; + } + + return UNKNOWN; + } + + public String getDescription() { + return this.description; + } + + @Override + public String toString() { + return "Status " + Integer.toString(this.id) + ": " + this.description; + } +} diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/RobotCababilities.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/RobotCababilities.java index 993209ee88f03..33ce0f8cac9aa 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/RobotCababilities.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/RobotCababilities.java @@ -38,7 +38,14 @@ public enum RobotCababilities { ROOM_MAPPING("", "info#room_mapping", "miio:room_mapping", "get_room_mapping"), MULTI_MAP_LIST("", "info#multi_maps_list", "miio:multi_maps_list", "get_multi_maps_list"), CUSTOMIZE_CLEAN_MODE("", "info#customize_clean_mode", "miio:customize_clean_mode", "get_customize_clean_mode"), - SEGMENT_CLEAN("", "actions#segment", "miio:segment", ""); + SEGMENT_CLEAN("", "actions#segment", "miio:segment", ""), + COLLECT_DUST("auto_dust_collection", "actions#collect_dust", "miio:collect_dust", ""), + CLEAN_MOP_START("dry_status", "actions#clean_mop_start", "miio:clean_mop_start", ""), + CLEAN_MOP_STOP("dry_status", "actions#clean_mop_stop", "miio:clean_mop_stop", ""), + MOP_DRYING("dry_status", "status#is_mop_drying", "miio:is_mop_drying", ""), + MOP_DRYING_REMAINING_TIME("dry_status", "status#mop_drying_time", "miio:mop_drying_time", ""), + DOCK_STATE("dock_error_status", "status#dock_state", "miio:dock_state", ""), + DOCK_STATE_ID("dock_error_status", "status#dock_state_id", "miio:dock_state_id", ""); private final String statusFieldName; private final String channel; diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/StatusDTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/StatusDTO.java index 0a9605f6f4da4..46c0c17f4c2a6 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/StatusDTO.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/StatusDTO.java @@ -85,6 +85,15 @@ public class StatusDTO { @SerializedName("mop_forbidden_enable") @Expose private Integer mopForbiddenEnable; + @SerializedName("dry_status") + @Expose + private Integer isMopDryingActive; + @SerializedName("rdt") + @Expose + private Long mopDryTime; + @SerializedName("dock_error_status") + @Expose + private Integer dockErrorStatus; public final Integer getMsgVer() { return msgVer; @@ -169,4 +178,20 @@ public final Integer getWaterBoxCarriageStatus() { public final Integer getMopForbiddenEnable() { return mopForbiddenEnable; } + + public Integer getIsMopDryingActive() { + return isMopDryingActive; + } + + public Long getMopDryTime() { + return mopDryTime; + } + + public Integer getDockErrorStatus() { + return this.dockErrorStatus; + } + + public void setDockErrorStatus(Integer dockErrorStatus) { + this.dockErrorStatus = dockErrorStatus; + } } diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/miio.properties b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/miio.properties index 289fd357ef65e..b841aa2926aae 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/miio.properties +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/miio.properties @@ -9,7 +9,7 @@ addon.config.miio.cloudDiscoveryMode.label = Cloud Discovery Mode addon.config.miio.cloudDiscoveryMode.description = Allow for discovery via the cloud. This may be used for devices that are not on the same network as OpenHAB server addon.config.miio.cloudDiscoveryMode.option.disabled = Local discovery only (Default) addon.config.miio.cloudDiscoveryMode.option.supportedOnly = Discover online supported devices from Xiaomi cloud -addon.config.miio.cloudDiscoveryMode.option.all = Discover all online devices from Xiaomi cloud +addon.config.miio.cloudDiscoveryMode.option.all = Discover all on & offline devices from Xiaomi cloud (advanced, see readme for usage) addon.config.miio.country.label = Xiaomi server country addon.config.miio.country.description = Xiaomi server country(s) (e.g. sg,de). Separate multiple servers with comma. Leave empty for all. See binding readme for country to server mapping addon.config.miio.password.label = Xiaomi cloud password @@ -19,13 +19,16 @@ addon.config.miio.username.description = Xiaomi cloud username. Typically your e # thing types thing-type.miio.basic.label = Xiaomi Mi Basic Device +thing-type.miio.gateway.label = Xiaomi Mi Gateway thing-type.miio.generic.label = Xiaomi Mi Device +thing-type.miio.lumi.label = Xiaomi Mi Lumi Device thing-type.miio.unsupported.label = Unsupported Xiaomi Mi Device thing-type.miio.vacuum.label = Xiaomi Robot Vacuum # thing types config -thing-type.config.miio.config.cloudServer.label = Xiaomi cloud Server (county code) +thing-type.config.miio.config.cloudServer.label = Cloud Server Country Code +thing-type.config.miio.config.cloudServer.description = Country code (2 characters) of the Xiaomi cloud server. See binding documentation for mapping of the country to cloud server thing-type.config.miio.config.communication.label = Communication Method thing-type.config.miio.config.communication.description = Determines how the binding communicates with this device thing-type.config.miio.config.communication.option.direct = Direct (Default) @@ -41,11 +44,23 @@ thing-type.config.miio.config.timeout.label = Timeout thing-type.config.miio.config.timeout.description = Timeout time in milliseconds thing-type.config.miio.config.token.label = Token thing-type.config.miio.config.token.description = Token for communication (in Hex) +thing-type.config.miio.configGatewayDevices.cloudServer.label = Cloud Server Country Code +thing-type.config.miio.configGatewayDevices.cloudServer.description = Country code (2 characters) of the Xiaomi cloud server. See binding documentation for mapping of the country to cloud server +thing-type.config.miio.configGatewayDevices.deviceId.label = Device ID +thing-type.config.miio.configGatewayDevices.deviceId.description = Device ID number for communication (in Hex) +thing-type.config.miio.configGatewayDevices.model.label = Device Model String +thing-type.config.miio.configGatewayDevices.model.description = Device model string, used to determine the subtype. +thing-type.config.miio.configGatewayDevices.refreshInterval.label = Refresh Interval +thing-type.config.miio.configGatewayDevices.refreshInterval.description = Refresh interval for refreshing the data in seconds. (0=disabled) +thing-type.config.miio.configGatewayDevices.timeout.label = Timeout +thing-type.config.miio.configGatewayDevices.timeout.description = Timeout time in milliseconds # channel group types channel-group-type.miio.actions.label = Action channel-group-type.miio.basicactions.label = Actions +channel-group-type.miio.basicactions.label = Actions +channel-group-type.miio.basicactions.label = Actions channel-group-type.miio.cleaning.label = Last Cleaning Details channel-group-type.miio.consumables.label = Consumables channel-group-type.miio.dnd.label = Do Not Disturb @@ -60,7 +75,12 @@ channel-group-type.miio.status.label = Status channel-type.miio.bssid.label = BSSID channel-type.miio.carpet_mode.label = Carpet Mode channel-type.miio.clean_area.label = Cleaning Area +channel-type.miio.clean_mop_start.label = Start Mop Wash +channel-type.miio.clean_mop_start.description = Once the vacuum cleaner is back in place, you can start cleaning the mop +channel-type.miio.clean_mop_stop.label = Stop Mop Wash channel-type.miio.clean_time.label = Cleaning Time +channel-type.miio.collect_dust.label = Start Collecting Dust +channel-type.miio.collect_dust.description = Once the vacuum cleaner is back in place, you can start collecting the dust channel-type.miio.color.label = Generic Color Channel channel-type.miio.commands.label = Execute Command channel-type.miio.consumable_reset.label = Reset Consumable @@ -82,6 +102,8 @@ channel-type.miio.dnd_enabled.label = Do Not Disturb channel-type.miio.dnd_end.label = End Time DND channel-type.miio.dnd_function.label = Do Not Disturb Functionality channel-type.miio.dnd_start.label = Start Time DND +channel-type.miio.dock_state.label = Dock State +channel-type.miio.dock_state_id.label = Dock State ID channel-type.miio.error_code.label = Error Code channel-type.miio.error_id.label = Error ID channel-type.miio.fan.label = Control Fan Level @@ -104,12 +126,15 @@ channel-type.miio.fw_features.label = Firmware Features channel-type.miio.image.label = Generic Image Channel channel-type.miio.in_cleaning.label = In Cleaning channel-type.miio.is_locating.label = Robot Locating +channel-type.miio.is_mop_drying.label = Mop Cleaning Active channel-type.miio.last_clean_area.label = Cleaning Area channel-type.miio.last_clean_duration.label = Cleaning Duration +channel-type.miio.last_clean_dustcollection_status.label = Dust Collection Status channel-type.miio.last_clean_end_time.label = Cleaning End channel-type.miio.last_clean_end_time.description = Last Cleaning End Time channel-type.miio.last_clean_error.label = Error channel-type.miio.last_clean_finish.label = Cleaning Finished +channel-type.miio.last_clean_finish_reason.label = Cleaning Finished Reason channel-type.miio.last_clean_record.label = Cleaning Record channel-type.miio.last_clean_start_time.label = Cleaning Start channel-type.miio.last_clean_start_time.description = Last Cleaning Start Time @@ -122,6 +147,7 @@ channel-type.miio.main_brush_time.label = Main Brush Time till Replacement channel-type.miio.map.label = Cleaning Map channel-type.miio.map_present.label = Map Present channel-type.miio.map_status.label = Map Status +channel-type.miio.mop_drying_time.label = Mop Drying Time channel-type.miio.mop_forbidden_enable.label = Mop Forbidden channel-type.miio.msg_seq.label = Msg Seq channel-type.miio.multi_maps_list.label = Multi Map List @@ -144,7 +170,7 @@ channel-type.miio.state_id.label = State ID channel-type.miio.string.label = Generic String Channel channel-type.miio.switch.label = Generic Switch Channel channel-type.miio.testcommands.label = (experimental) Create channels / test properties for unsupported devices (legacy protocol) -channel-type.miio.testcommands.description = Execute test for all known properties to find channels supported by your device. This is for older /legacy devices, newer devices mostly use MIOT. Check your log, share your results. +channel-type.miio.testcommands.description = Execute test for all known properties to find channels supported by your device. This is for older / legacy devices, newer devices mostly use MIOT. Check your log, share your results. channel-type.miio.testmiot.label = (experimental) Create channels for new/unsupported devices (MIOT protocol) channel-type.miio.testmiot.description = Create experimental support for MIOT protocol devices based on the online specification. Check your log, share your results. channel-type.miio.total_clean_area.label = Total Cleaning Area @@ -155,7 +181,8 @@ channel-type.miio.water_box_carriage_status.label = Water Box Carriage State channel-type.miio.water_box_mode.label = Water Box Mode channel-type.miio.water_box_status.label = Water Box State -# Thing status descriptions +# thing status descriptions + offline.config-error-ip = IP address required. Configure IP address offline.config-error-token = Token required. Configure token offline.config-error-cloud = Cloud communication requires defined deviceId in the config diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml index 9bdfb67ae1056..b5035df1a5d9a 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml @@ -215,6 +215,40 @@ + + String + + + + + Number + + + + + Number:Time + + + + + Switch + + + + + Switch + + Once the vacuum cleaner is back in place, you can start collecting the dust + + + Switch + + Once the vacuum cleaner is back in place, you can start cleaning the mop + + + Switch + + From 1707e1182cdae1cfdadfeff88c4afdd961f96b11 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 07:09:20 +0000 Subject: [PATCH 064/146] [hue] Support new home security products (#15601) Signed-off-by: Andrew Fiddian-Green --- .../org.openhab.binding.hue/doc/readme_v2.md | 63 +++++++------ .../hue/internal/HueBindingConstants.java | 5 ++ .../hue/internal/dto/clip2/ContactReport.java | 48 ++++++++++ .../hue/internal/dto/clip2/Resource.java | 56 ++++++++++++ .../hue/internal/dto/clip2/TamperReport.java | 48 ++++++++++ .../dto/clip2/enums/ContactStateType.java | 26 ++++++ .../dto/clip2/enums/ResourceType.java | 3 + .../dto/clip2/enums/TamperStateType.java | 26 ++++++ .../internal/handler/Clip2ThingHandler.java | 18 ++++ .../main/resources/OH-INF/i18n/hue.properties | 11 +++ .../resources/OH-INF/thing/Clip2Thing.xml | 19 ++++ .../main/resources/OH-INF/thing/channels.xml | 12 +++ .../hue/internal/clip2/Clip2DtoTest.java | 89 +++++++++++++++++++ .../src/test/resources/camera_motion.json | 28 ++++++ .../src/test/resources/contact.json | 19 ++++ .../src/test/resources/tamper.json | 31 +++++++ 16 files changed, 473 insertions(+), 29 deletions(-) create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java create mode 100644 bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json create mode 100644 bundles/org.openhab.binding.hue/src/test/resources/contact.json create mode 100644 bundles/org.openhab.binding.hue/src/test/resources/tamper.json diff --git a/bundles/org.openhab.binding.hue/doc/readme_v2.md b/bundles/org.openhab.binding.hue/doc/readme_v2.md index 206bc476a82d2..d9b84392f0a3a 100644 --- a/bundles/org.openhab.binding.hue/doc/readme_v2.md +++ b/bundles/org.openhab.binding.hue/doc/readme_v2.md @@ -59,35 +59,40 @@ The configuration of all things (as described above) is the same regardless of w Device things support some of the following channels: -| Channel ID | Item Type | Description | -|---------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------| -| color | Color | Supports full color control with hue, saturation and brightness values, or brightness only, or switching on or off. | -| brightness | Dimmer | Supports control of the brightness value, or switching on or off. | -| color-temperature | Dimmer | Supports control of the color temperature in percent from cold (0%) to warm (100%). | -| color-temperature-abs | Number:Temperature | Supports control of the color temperature via a QuantityType having a temperature unit e.g. Kelvin. (Advanced) | -| switch | Switch | Supports switching the device on and off. | -| dynamics | Number:Time | Sets the duration of dynamic transitions between light states. (Advanced) | -| alert | String | Allows setting an alert on a light e.g. flashing them. (Advanced) | -| effect | String | Allows setting an effect on a light e.g. 'candle' effect. (Advanced) | -| button-last-event | (String) | Informs which button was last pressed in the device. (Trigger Channel) | -| button-last-updated | DateTime | The date and time when a button was last pressed. (Read Only) (Advanced) | -| rotary-steps | (String) | Informs about the number of rotary steps of the last rotary dial movement. (Trigger Channel) | -| rotary-steps-last-updated | DateTime | The date and time when the rotary steps were last updated. (Read Only) (Advanced) | -| motion | Switch | Shows if motion has been detected by the sensor. (Read Only) | -| motion-enabled | Switch | Supports enabling / disabling the motion sensor. (Advanced) | -| motion-last-updated | DateTime | The date and time when the motion value was last updated. (Read Only) (Advanced) | -| light-level | Number:Illuminance | Shows the current light level measured by the sensor. (Read Only) | -| light-level-last-updated | DateTime | The date and time when the light level was last updated. (Read Only) (Advanced) | -| light-level-enabled | Switch | Supports enabling / disabling the light level sensor. (Advanced) | -| temperature | Number:Temperature | Shows the current temperature measured by the sensor. (Read Only) | -| temperature-last-updated | DateTime | The date and time when the temperature was last updated. (Read Only) (Advanced) | -| temperature-enabled | Switch | Supports enabling / disabling the temperature sensor. (Advanced) | -| battery-level | Number | Shows the battery level. (Read Only) | -| battery-low | Switch | Indicates whether the battery is low or not. (Read Only) | -| last-updated | DateTime | The date and time when the thing state was last updated. (Read Only) (Advanced) | -| color-xy-only | Color | Allows access to the `color-xy` parameter of the light(s) only. Has no impact on `dimming` or `on-off` parameters. | -| dimming-only | Dimmer | Allows access to the `dimming` parameter of the light(s) only. Has no impact on `color-xy` or `on-off` parameters. | -| on-off-only | Switch | Allows access to the `on-off` parameter of the light(s) only. Has no impact on `color-xy` or `dimming` parameters. | +| Channel ID | Item Type | Description | +|-------------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------| +| color | Color | Supports full color control with hue, saturation and brightness values, or brightness only, or switching on or off. | +| brightness | Dimmer | Supports control of the brightness value, or switching on or off. | +| color-temperature | Dimmer | Supports control of the color temperature in percent from cold (0%) to warm (100%). | +| color-temperature-abs | Number:Temperature | Supports control of the color temperature via a QuantityType having a temperature unit e.g. Kelvin. (Advanced) | +| switch | Switch | Supports switching the device on and off. | +| dynamics | Number:Time | Sets the duration of dynamic transitions between light states. (Advanced) | +| alert | String | Allows setting an alert on a light e.g. flashing them. (Advanced) | +| effect | String | Allows setting an effect on a light e.g. 'candle' effect. (Advanced) | +| button-last-event | (String) | Informs which button was last pressed in the device. (Trigger Channel) | +| button-last-updated | DateTime | The date and time when a button was last pressed. (Read Only) (Advanced) | +| rotary-steps | (String) | Informs about the number of rotary steps of the last rotary dial movement. (Trigger Channel) | +| rotary-steps-last-updated | DateTime | The date and time when the rotary steps were last updated. (Read Only) (Advanced) | +| motion | Switch | Shows if motion has been detected by the sensor. (Read Only) | +| motion-enabled | Switch | Supports enabling / disabling the motion sensor. (Advanced) | +| motion-last-updated | DateTime | The date and time when the motion value was last updated. (Read Only) (Advanced) | +| light-level | Number:Illuminance | Shows the current light level measured by the sensor. (Read Only) | +| light-level-last-updated | DateTime | The date and time when the light level was last updated. (Read Only) (Advanced) | +| light-level-enabled | Switch | Supports enabling / disabling the light level sensor. (Advanced) | +| temperature | Number:Temperature | Shows the current temperature measured by the sensor. (Read Only) | +| temperature-last-updated | DateTime | The date and time when the temperature was last updated. (Read Only) (Advanced) | +| temperature-enabled | Switch | Supports enabling / disabling the temperature sensor. (Advanced) | +| battery-level | Number | Shows the battery level. (Read Only) | +| battery-low | Switch | Indicates whether the battery is low or not. (Read Only) | +| last-updated | DateTime | The date and time when the thing state was last updated. (Read Only) (Advanced) | +| color-xy-only | Color | Allows access to the `color-xy` parameter of the light(s) only. Has no impact on `dimming` or `on-off` parameters. | +| dimming-only | Dimmer | Allows access to the `dimming` parameter of the light(s) only. Has no impact on `color-xy` or `on-off` parameters. | +| on-off-only | Switch | Allows access to the `on-off` parameter of the light(s) only. Has no impact on `color-xy` or `dimming` parameters. | +| security-contact | Contact | Indicates whether a security contact has been triggered. (Read Only) | +| security-contact-enabled | Switch | Supports enabling / disabling the security contact. (Advanced) | +| security-contact-last-updated | DateTime | The date and time when the security contact state was last updated. (Read Only) (Advanced) | +| security-tamper | Contact | Indicates whether a security tamper contact has been triggered. `Open` means tampering detected. (Read Only) | +| security-tamper-last-updated | DateTime | The date and time when the security tamper contact state was last updated. (Read Only) (Advanced) | The exact list of channels in a given device is determined at run time when the system is started. Each device reports its own live list of capabilities, and the respective list of channels is created accordingly. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java index 829b3464b9d59..67af11d4c995c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java @@ -169,6 +169,11 @@ public class HueBindingConstants { public static final String CHANNEL_2_COLOR_XY_ONLY = "color-xy-only"; public static final String CHANNEL_2_DIMMING_ONLY = "dimming-only"; public static final String CHANNEL_2_ON_OFF_ONLY = "on-off-only"; + public static final String CHANNEL_2_SECURITY_CONTACT = "security-contact"; + public static final String CHANNEL_2_SECURITY_CONTACT_ENABLED = "security-contact-enabled"; + public static final String CHANNEL_2_SECURITY_CONTACT_LAST_UPDATED = "security-contact-last-updated"; + public static final String CHANNEL_2_SECURITY_TAMPER = "security-tamper"; + public static final String CHANNEL_2_SECURITY_TAMPER_LAST_UPDATED = "security-tamper-last-updated"; // channel IDs that (optionally) support dynamics public static final Set DYNAMIC_CHANNELS = Set.of(CHANNEL_2_BRIGHTNESS, CHANNEL_2_COLOR, diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java new file mode 100644 index 0000000000000..99c8c2a835c0b --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hue.internal.dto.clip2; + +import java.time.Instant; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.hue.internal.dto.clip2.enums.ContactStateType; + +/** + * DTO for CLIP 2 home security alarm contact. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class ContactReport { + + private @NonNullByDefault({}) Instant changed; + private @NonNullByDefault({}) String state; + + public ContactStateType getContactState() throws IllegalArgumentException { + return ContactStateType.valueOf(state.toUpperCase()); + } + + public Instant getLastChanged() { + return changed; + } + + public ContactReport setLastChanged(Instant changed) { + this.changed = changed; + return this; + } + + public ContactReport setContactState(String state) { + this.state = state; + return this; + } +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java index 41fcae3a6cb6d..2791bbeab535c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java @@ -28,17 +28,20 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType; import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType; +import org.openhab.binding.hue.internal.dto.clip2.enums.ContactStateType; import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType; import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction; import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction; import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneState; +import org.openhab.binding.hue.internal.dto.clip2.enums.TamperStateType; import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus; import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; @@ -103,6 +106,8 @@ public class Resource { private @Nullable List children; private @Nullable JsonElement status; private @Nullable @SuppressWarnings("unused") Dynamics dynamics; + private @Nullable @SerializedName("contact_report") ContactReport contactReport; + private @Nullable @SerializedName("tamper_reports") List tamperReports; private @Nullable String state; /** @@ -325,6 +330,20 @@ public State getColorXyState() { return UnDefType.NULL; } + public State getContactLastUpdatedState(ZoneId zoneId) { + ContactReport contactReport = this.contactReport; + return Objects.nonNull(contactReport) + ? new DateTimeType(ZonedDateTime.ofInstant(contactReport.getLastChanged(), zoneId)) + : UnDefType.NULL; + } + + public State getContactState() { + ContactReport contactReport = this.contactReport; + return Objects.isNull(contactReport) ? UnDefType.NULL + : ContactStateType.CONTACT == contactReport.getContactState() ? OpenClosedType.CLOSED + : OpenClosedType.OPEN; + } + public int getControlId() { MetaData metadata = this.metadata; return Objects.nonNull(metadata) ? metadata.getControlId() : 0; @@ -649,6 +668,33 @@ public JsonObject getStatus() { return new JsonObject(); } + public State getTamperLastUpdatedState(ZoneId zoneId) { + TamperReport report = getTamperReportsLatest(); + return Objects.nonNull(report) ? new DateTimeType(ZonedDateTime.ofInstant(report.getLastChanged(), zoneId)) + : UnDefType.NULL; + } + + /** + * The the Hue bridge could return its raw list of tamper reports in any order, so sort the list (latest entry + * first) according to the respective 'changed' instant and return the first entry i.e. the latest changed entry. + * + * @return the latest changed tamper report + */ + private @Nullable TamperReport getTamperReportsLatest() { + List reports = this.tamperReports; + return Objects.nonNull(reports) + ? reports.stream().sorted((e1, e2) -> e2.getLastChanged().compareTo(e1.getLastChanged())).findFirst() + .orElse(null) + : null; + } + + public State getTamperState() { + TamperReport report = getTamperReportsLatest(); + return Objects.nonNull(report) + ? TamperStateType.TAMPERED == report.getTamperState() ? OpenClosedType.OPEN : OpenClosedType.CLOSED + : UnDefType.NULL; + } + public @Nullable Temperature getTemperature() { return temperature; } @@ -736,6 +782,11 @@ public Resource setColorXy(ColorXy color) { return this; } + public Resource setContactReport(ContactReport contactReport) { + this.contactReport = contactReport; + return this; + } + public Resource setDimming(Dimming dimming) { this.dimming = dimming; return this; @@ -815,6 +866,11 @@ public Resource setRecallDuration(Duration recallDuration) { return this; } + public Resource setTamperReports(List tamperReports) { + this.tamperReports = tamperReports; + return this; + } + public Resource setTimedEffects(TimedEffects timedEffects) { this.timedEffects = timedEffects; return this; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java new file mode 100644 index 0000000000000..50d250898adf4 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hue.internal.dto.clip2; + +import java.time.Instant; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.hue.internal.dto.clip2.enums.TamperStateType; + +/** + * DTO for CLIP 2 home security tamper switch. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class TamperReport { + + private @NonNullByDefault({}) Instant changed; + private @NonNullByDefault({}) String state; + + public Instant getLastChanged() { + return changed; + } + + public TamperStateType getTamperState() throws IllegalArgumentException { + return TamperStateType.valueOf(state.toUpperCase()); + } + + public TamperReport setLastChanged(Instant changed) { + this.changed = changed; + return this; + } + + public TamperReport setTamperState(String state) { + this.state = state; + return this; + } +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java new file mode 100644 index 0000000000000..2892e77ca18fb --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hue.internal.dto.clip2.enums; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Enum for security contact states. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public enum ContactStateType { + NO_CONTACT, + CONTACT +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java index f08566c77f9f2..56c2f0061be3e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java @@ -31,6 +31,8 @@ public enum ResourceType { BRIDGE, BRIDGE_HOME, BUTTON, + CAMERA_MOTION, + CONTACT, DEVICE, DEVICE_POWER, ENTERTAINMENT, @@ -47,6 +49,7 @@ public enum ResourceType { ROOM, RELATIVE_ROTARY, SCENE, + TAMPER, SMART_SCENE, TEMPERATURE, ZGP_CONNECTIVITY, diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java new file mode 100644 index 0000000000000..23199b1af50d3 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hue.internal.dto.clip2.enums; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Enum for tamper switch states. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public enum TamperStateType { + NOT_TAMPERED, + TAMPERED +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java index 1f2c3a7292e10..a436f7f889a4f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java @@ -433,6 +433,10 @@ public void handleCommand(ChannelUID channelUID, Command commandParam) { putResource = new Resource(ResourceType.LIGHT_LEVEL).setEnabled(command); break; + case CHANNEL_2_SECURITY_CONTACT_ENABLED: + putResource = new Resource(ResourceType.CONTACT).setEnabled(command); + break; + case CHANNEL_2_SCENE: if (command instanceof StringType) { Resource scene = sceneResourceEntries.get(((StringType) command).toString()); @@ -888,6 +892,7 @@ private boolean updateChannels(Resource resource) { break; case MOTION: + case CAMERA_MOTION: updateState(CHANNEL_2_MOTION, resource.getMotionState(), fullUpdate); updateState(CHANNEL_2_MOTION_LAST_UPDATED, resource.getMotionLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate); @@ -920,6 +925,19 @@ private boolean updateChannels(Resource resource) { updateState(CHANNEL_2_SCENE, resource.getSceneState(), fullUpdate); break; + case CONTACT: + updateState(CHANNEL_2_SECURITY_CONTACT, resource.getContactState(), fullUpdate); + updateState(CHANNEL_2_SECURITY_CONTACT_LAST_UPDATED, + resource.getContactLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate); + updateState(CHANNEL_2_SECURITY_CONTACT_ENABLED, resource.getEnabledState(), fullUpdate); + break; + + case TAMPER: + updateState(CHANNEL_2_SECURITY_TAMPER, resource.getTamperState(), fullUpdate); + updateState(CHANNEL_2_SECURITY_TAMPER_LAST_UPDATED, + resource.getTamperLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate); + break; + case SMART_SCENE: updateState(CHANNEL_2_SCENE, resource.getSmartSceneState(), fullUpdate); break; diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties index d8b6dd1b38b34..3fa66d65d9a03 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties @@ -55,6 +55,15 @@ thing-type.hue.device.channel.motion-last-updated.description = The date and tim thing-type.hue.device.channel.on-off-only.description = Set the on/off parameter of the light without changing other state parameters. thing-type.hue.device.channel.rotary-steps-last-updated.label = Rotary Steps Last Updated thing-type.hue.device.channel.rotary-steps-last-updated.description = The date and time when the rotary steps were last updated. +thing-type.hue.device.channel.security-contact.label = Security Contact +thing-type.hue.device.channel.security-contact.description = Open or closed state of the contact. +thing-type.hue.device.channel.security-contact-enabled.description = Security contact enabled. +thing-type.hue.device.channel.security-contact-last-updated.label = Security Contact Last Updated +thing-type.hue.device.channel.security-contact-last-updated.description = The date and time when the contact state was last updated. +thing-type.hue.device.channel.security-tamper.label = Security Tamper Contact +thing-type.hue.device.channel.security-tamper.description = Tamper or no tamper state of the sensor. +thing-type.hue.device.channel.security-tamper-last-updated.label = Tamper Contact Last Updated +thing-type.hue.device.channel.security-tamper-last-updated.description = The date and time when the tamper contact state was last updated. thing-type.hue.device.channel.temperature.label = Temperature thing-type.hue.device.channel.temperature.description = Temperature at the sensor location. thing-type.hue.device.channel.temperature-enabled.description = Temperature sensor enabled. @@ -183,6 +192,8 @@ channel-type.hue.rotary-steps.description = The last 'steps' value (e.g. +/-30) channel-type.hue.scene-v2.label = Scene channel-type.hue.scene.label = Scene channel-type.hue.scene.description = The scene channel allows recalling a scene to all lights that belong to the scene. +channel-type.hue.security-contact.label = Open/Closed +channel-type.hue.security-tamper.label = Normal/Tamper channel-type.hue.sensor-enabled.label = Sensor Enabled channel-type.hue.status.label = Status channel-type.hue.status.description = Status of CLIP sensor. diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml index 03419d9a88ab1..33f5d935c5608 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml @@ -62,6 +62,25 @@ Temperature sensor enabled. + + + Open or closed state of the contact. + + + Security contact enabled. + + + + The date and time when the contact state was last updated. + + + + Tamper or no tamper state of the sensor. + + + + The date and time when the tamper contact state was last updated. + diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml index 6e51b1761a522..c1f82b5516ac7 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml @@ -256,4 +256,16 @@ Switch + + Contact + + Lock + + + + Contact + + Siren + + diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java index 059a06ba0459a..79b32776d512d 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneId; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -31,6 +32,7 @@ import org.openhab.binding.hue.internal.dto.clip2.ActionEntry; import org.openhab.binding.hue.internal.dto.clip2.Alerts; import org.openhab.binding.hue.internal.dto.clip2.Button; +import org.openhab.binding.hue.internal.dto.clip2.ContactReport; import org.openhab.binding.hue.internal.dto.clip2.Dimming; import org.openhab.binding.hue.internal.dto.clip2.Effects; import org.openhab.binding.hue.internal.dto.clip2.Event; @@ -46,6 +48,7 @@ import org.openhab.binding.hue.internal.dto.clip2.Resources; import org.openhab.binding.hue.internal.dto.clip2.Rotation; import org.openhab.binding.hue.internal.dto.clip2.RotationEvent; +import org.openhab.binding.hue.internal.dto.clip2.TamperReport; import org.openhab.binding.hue.internal.dto.clip2.Temperature; import org.openhab.binding.hue.internal.dto.clip2.TimedEffects; import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType; @@ -63,6 +66,7 @@ import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; @@ -707,6 +711,91 @@ void testZoneGroup() { } @Test + void testSecurityContact() { + String json = load(ResourceType.CONTACT.name().toLowerCase()); + Resources resources = GSON.fromJson(json, Resources.class); + assertNotNull(resources); + List list = resources.getResources(); + assertNotNull(list); + assertEquals(1, list.size()); + Resource resource = list.get(0); + assertEquals(ResourceType.CONTACT, resource.getType()); + + assertEquals(OpenClosedType.CLOSED, resource.getContactState()); + assertEquals(new DateTimeType("2023-10-10T19:10:55.919Z"), + resource.getContactLastUpdatedState(ZoneId.of("UTC"))); + + resource.setContactReport(new ContactReport().setLastChanged(Instant.now()).setContactState("no_contact")); + assertEquals(OpenClosedType.OPEN, resource.getContactState()); + assertTrue(resource.getContactLastUpdatedState(ZoneId.of("UTC")) instanceof DateTimeType); + } + + @Test + void testSecurityTamper() { + String json = load(ResourceType.TAMPER.name().toLowerCase()); + Resources resources = GSON.fromJson(json, Resources.class); + assertNotNull(resources); + List list = resources.getResources(); + assertNotNull(list); + assertEquals(1, list.size()); + Resource resource = list.get(0); + assertEquals(ResourceType.TAMPER, resource.getType()); + + assertEquals(OpenClosedType.CLOSED, resource.getTamperState()); + assertEquals(new DateTimeType("2023-01-01T00:00:00.001Z"), + resource.getTamperLastUpdatedState(ZoneId.of("UTC"))); + + Instant start = Instant.now(); + List tamperReports; + State state; + + tamperReports = new ArrayList<>(); + tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start)); + resource.setTamperReports(tamperReports); + assertEquals(OpenClosedType.CLOSED, resource.getTamperState()); + state = resource.getTamperLastUpdatedState(ZoneId.of("UTC")); + assertTrue(state instanceof DateTimeType); + assertEquals(start, ((DateTimeType) state).getInstant()); + + tamperReports = new ArrayList<>(); + tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start)); + tamperReports.add(new TamperReport().setTamperState("tampered").setLastChanged(start.plusSeconds(1))); + resource.setTamperReports(tamperReports); + assertEquals(OpenClosedType.OPEN, resource.getTamperState()); + state = resource.getTamperLastUpdatedState(ZoneId.of("UTC")); + assertTrue(state instanceof DateTimeType); + assertEquals(start.plusSeconds(1), ((DateTimeType) state).getInstant()); + + tamperReports = new ArrayList<>(); + tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start)); + tamperReports.add(new TamperReport().setTamperState("tampered").setLastChanged(start.plusSeconds(1))); + tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start.plusSeconds(2))); + resource.setTamperReports(tamperReports); + assertEquals(OpenClosedType.CLOSED, resource.getTamperState()); + state = resource.getTamperLastUpdatedState(ZoneId.of("UTC")); + assertTrue(state instanceof DateTimeType); + assertEquals(start.plusSeconds(2), ((DateTimeType) state).getInstant()); + } + + @Test + void testCameraMotion() { + String json = load(ResourceType.CAMERA_MOTION.name().toLowerCase()); + Resources resources = GSON.fromJson(json, Resources.class); + assertNotNull(resources); + List list = resources.getResources(); + assertNotNull(list); + assertEquals(1, list.size()); + Resource resource = list.get(0); + assertEquals(ResourceType.CAMERA_MOTION, resource.getType()); + + Boolean enabled = resource.getEnabled(); + assertNotNull(enabled); + assertTrue(enabled); + assertEquals(OnOffType.ON, resource.getMotionState()); + assertEquals(new DateTimeType("2020-04-01T20:04:30.395Z"), + resource.getMotionLastUpdatedState(ZoneId.of("UTC"))); + } + void testFixedEffectSetter() { Resource source; Resource target; diff --git a/bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json b/bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json new file mode 100644 index 0000000000000..c678d620d27bb --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json @@ -0,0 +1,28 @@ +{ + "errors": [], + "data": [ + { + "id": "00000000-0000-0000-0000-000000000005", + "id_v1": "/sensors/5", + "owner": { + "rid": "00000000-0000-0000-0000-000000000000", + "rtype": "device" + }, + "enabled": true, + "motion": { + "motion": false, + "motion_valid": true, + "motion_report": { + "changed": "2020-04-01T20:04:30.395Z", + "motion": true + } + }, + "sensitivity": { + "status": "set", + "sensitivity": 2, + "sensitivity_max": 4 + }, + "type": "camera_motion" + } + ] +} diff --git a/bundles/org.openhab.binding.hue/src/test/resources/contact.json b/bundles/org.openhab.binding.hue/src/test/resources/contact.json new file mode 100644 index 0000000000000..6b7af5b44a0e1 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/test/resources/contact.json @@ -0,0 +1,19 @@ +{ + "errors": [ + ], + "data": [ + { + "id": "bcaee909-1b37-454b-814d-9928776ad350", + "owner": { + "rid": "faac7940-a303-4e8e-9f06-075fffb7229c", + "rtype": "device" + }, + "enabled": true, + "contact_report": { + "changed": "2023-10-10T19:10:55.919Z", + "state": "contact" + }, + "type": "contact" + } + ] +} diff --git a/bundles/org.openhab.binding.hue/src/test/resources/tamper.json b/bundles/org.openhab.binding.hue/src/test/resources/tamper.json new file mode 100644 index 0000000000000..10af6dc915f98 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/test/resources/tamper.json @@ -0,0 +1,31 @@ +{ + "errors": [ + ], + "data": [ + { + "id": "6c2ef541-fe03-4cae-ac60-3edcaa93b33e", + "owner": { + "rid": "faac7940-a303-4e8e-9f06-075fffb7229c", + "rtype": "device" + }, + "tamper_reports": [ + { + "changed": "1970-01-01T00:00:00.000Z", + "source": "battery_door", + "state": "not_tampered" + }, + { + "changed": "2023-01-01T00:00:00.001Z", + "source": "battery_door", + "state": "not_tampered" + }, + { + "changed": "2023-01-01T00:00:00.000Z", + "source": "battery_door", + "state": "tampered" + } + ], + "type": "tamper" + } + ] +} From b12963a8e0f76bdb6cdc96e360af2f08dfb30e46 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Thu, 9 Nov 2023 18:48:41 +0100 Subject: [PATCH 065/146] New translations hue.properties (Italian) (#15874) --- .../resources/OH-INF/i18n/hue_it.properties | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue_it.properties b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue_it.properties index 1a65da7f583d1..fb0e249278d36 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue_it.properties +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue_it.properties @@ -40,16 +40,35 @@ thing-type.hue.bridge.description = Il "ponte" Hue connette il sistema al Hue Br thing-type.hue.device.label = Dispositivo Hue thing-type.hue.device.description = Un dispositivo Hue API v2 con canali a seconda delle sue reali capacità. thing-type.hue.device.channel.alert.description = Attiva l'avviso per la luce. +thing-type.hue.device.channel.button-last-updated.label = Ultimo Aggiornamento Pulsante +thing-type.hue.device.channel.button-last-updated.description = La data e l'ora in cui è stata l'ultima pressione del pulsante. thing-type.hue.device.channel.color-xy-only.description = Imposta il parametro colore xy della luce senza cambiare altri parametri di stato. thing-type.hue.device.channel.dimming-only.description = Imposta il parametro dimming della luce senza modificare altri parametri di stato. thing-type.hue.device.channel.effect.description = Attival'effetto per la luce. thing-type.hue.device.channel.light-level.description = Intensità luce attuale. thing-type.hue.device.channel.light-level-enabled.description = Sensore intensità luce attivato. +thing-type.hue.device.channel.light-level-last-updated.label = Ultimo Aggiornamento Livello Luce +thing-type.hue.device.channel.light-level-last-updated.description = La data e l'ora in cui livello di luce è stato aggiornato. thing-type.hue.device.channel.motion-enabled.description = Sensore di movimento attivato. +thing-type.hue.device.channel.motion-last-updated.label = Ultimo Aggiornamento Movimento +thing-type.hue.device.channel.motion-last-updated.description = La data e l'ora dell'ultimo aggiornamento del valore del movimento. thing-type.hue.device.channel.on-off-only.description = Imposta il parametro on/off della luce senza modificare altri parametri di stato. +thing-type.hue.device.channel.rotary-steps-last-updated.label = Ultimo Aggiornamento Fasi Rotazione +thing-type.hue.device.channel.rotary-steps-last-updated.description = La data e l'ora dell'ultimo aggiornamento delle fasi di rotazione. +thing-type.hue.device.channel.security-contact.label = Contatto Di Sicurezza +thing-type.hue.device.channel.security-contact.description = Stato aperto o chiuso del contatto. +thing-type.hue.device.channel.security-contact-enabled.description = Contatto di sicurezza abilitato. +thing-type.hue.device.channel.security-contact-last-updated.label = Ultimo Aggiornamento Contatto Di Sicurezza +thing-type.hue.device.channel.security-contact-last-updated.description = Data e ora dell'ultimo aggiornamento dello stato del contatto. +thing-type.hue.device.channel.security-tamper.label = Contatto Sicurezza Manomissione +thing-type.hue.device.channel.security-tamper.description = Stato del sensore di manomissione. +thing-type.hue.device.channel.security-tamper-last-updated.label = Ultimo Aggiornamento Contatto Manomissione +thing-type.hue.device.channel.security-tamper-last-updated.description = La data e l'ora di ultimo aggiornamento del contatto della manomissione. thing-type.hue.device.channel.temperature.label = Temperatura thing-type.hue.device.channel.temperature.description = Temperatura nella posizione del sensore. thing-type.hue.device.channel.temperature-enabled.description = Sensore di temperatura attivato. +thing-type.hue.device.channel.temperature-last-updated.label = Ultimo Aggiornamento Temperatura +thing-type.hue.device.channel.temperature-last-updated.description = La data e l'ora dell'ultimo aggiornamento della temperatura. thing-type.hue.geofencesensor.label = Sensore perimetrale thing-type.hue.geofencesensor.description = Un sensore che fornisce rilevamento della presenza basata su di un perimetro definito. thing-type.hue.group.label = Gruppo Hue @@ -173,6 +192,8 @@ channel-type.hue.rotary-steps.description = Gli ultimi 'passi' (es. +/-30) del s channel-type.hue.scene-v2.label = Scena channel-type.hue.scene.label = Scena channel-type.hue.scene.description = La scena di un canale permette di richiamare una scena e tutte le luci associate. +channel-type.hue.security-contact.label = Aperto/Chiuso +channel-type.hue.security-tamper.label = Normale/Manomesso channel-type.hue.sensor-enabled.label = Sensore Abilitato channel-type.hue.status.label = Stato channel-type.hue.status.description = Lo stato di un sensore CLIP. From f01d24e2473fba0b3873b33bb1a82c516a3af912 Mon Sep 17 00:00:00 2001 From: Jamie Townsend Date: Thu, 9 Nov 2023 20:01:44 +0100 Subject: [PATCH 066/146] Fix control signal circulating pump Fixes #15836 Signed-off-by: Jamie Townsend --- bundles/org.openhab.binding.luxtronikheatpump/README.md | 2 +- .../src/main/resources/OH-INF/thing/channels.xml | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.luxtronikheatpump/README.md b/bundles/org.openhab.binding.luxtronikheatpump/README.md index 64e26bff6193f..e166b82c784fc 100644 --- a/bundles/org.openhab.binding.luxtronikheatpump/README.md +++ b/bundles/org.openhab.binding.luxtronikheatpump/README.md @@ -207,7 +207,7 @@ The following channels are holding read only values: | highPressure | Number:Pressure | x | High pressure | | lowPressure | Number:Pressure | x | Low pressure | | outputCompressorHeating | Switch | x | Output compressor heating | -| controlSignalCirculatingPump | Number:Energy | x | Control signal circulating pump | +| controlSignalCirculatingPump | Number:Dimensionless | x | Heating circulating pump power in % | | fanSpeed | Number | x | Fan speed | | temperatureSafetyLimitFloorHeating | Switch | x | Safety temperature limiter floor heating | | powerTargetValue | Number:Energy | x | Power target value | diff --git a/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/thing/channels.xml index 02b0d0dc04a11..41c0f6e728af0 100644 --- a/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.luxtronikheatpump/src/main/resources/OH-INF/thing/channels.xml @@ -1572,9 +1572,8 @@ - Number:Energy - - Energy + Number:Dimensionless + From fbc412c1dc67a4220979a0bec2ce259173bbc297 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Fri, 10 Nov 2023 13:29:25 +0100 Subject: [PATCH 067/146] [fineoffsetweatherstation] Fix wrong handling temperature reading for of WH34 (#15853) * [fineoffsetweatherstation] Improve tracing * [fineoffsetweatherstation] Fix wrong handling temperature reading for of WH34 Signed-off-by: Andreas Berger --- .../README.md | 2 +- .../internal/domain/Command.java | 4 + .../internal/domain/DebugDetails.java | 79 +++++++++++++++++++ .../internal/domain/Measurand.java | 35 +++++--- .../service/ELVGatewayQueryService.java | 7 +- .../service/FineOffsetDataParser.java | 18 +++-- .../FineOffsetGatewayQueryService.java | 14 +++- .../internal/service/GatewayQueryService.java | 2 +- .../service/FineOffsetDataParserTest.java | 48 +++++++++-- 9 files changed, 182 insertions(+), 27 deletions(-) create mode 100644 bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/DebugDetails.java diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/README.md b/bundles/org.openhab.binding.fineoffsetweatherstation/README.md index fcfed15c69c71..fa4bb744e67e3 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/README.md +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/README.md @@ -19,7 +19,7 @@ Here is a product picture of how this Weather Station looks like: ![WH2650](doc/WH2650.png) -This binding works offline by [implementing the wire protocol](https://osswww.ecowitt.net/uploads/20210716/WN1900%20GW1000,1100%20WH2680,2650%20telenet%20v1.6.0%20.pdf) of the WiFi gateway device. +This binding works offline by [implementing the wire protocol](https://osswww.ecowitt.net/uploads/20220407/WN1900%20GW1000,1100%20WH2680,2650%20telenet%20v1.6.4.pdf) of the WiFi gateway device. ## Discussion diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Command.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Command.java index eb2872865154d..dd81779c990ee 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Command.java +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Command.java @@ -275,4 +275,8 @@ public boolean isHeaderValid(byte[] data) { public boolean isResponseValid(byte[] data) { return isHeaderValid(data) && Utils.validateChecksum(data, sizeBytes); } + + public int getSizeBytes() { + return sizeBytes; + } } diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/DebugDetails.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/DebugDetails.java new file mode 100644 index 0000000000000..7d2eb17e709f6 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/DebugDetails.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.fineoffsetweatherstation.internal.domain; + +import java.util.Arrays; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.openhab.binding.fineoffsetweatherstation.internal.Utils; + +/** + * Class to collect debug details + * + * @author Andreas Berger - Initial contribution + */ +public class DebugDetails { + final byte[] data; + + private final Map segments = new TreeMap<>(); + + public DebugDetails(byte[] data, Command command, Protocol protocol) { + this.data = data; + addDebugDetails(0, 2, "header"); + addDebugDetails(2, 1, "command: " + command.name()); + addDebugDetails(3, command.getSizeBytes(), "size"); + if (protocol == Protocol.ELV) { + addDebugDetails(data.length - 2, 1, "ELV checksum"); + } + addDebugDetails(data.length - 1, 1, "checksum"); + } + + public void addDebugDetails(int start, int length, String description) { + segments.put(start, new DebugSegment(start, length, description)); + } + + @Override + public String toString() { + int padding = segments.values().stream().mapToInt(value -> value.length).max().orElse(0) * 2; + return "0x" + Utils.toHexString(data, data.length, "") + "\n" + segments.values().stream() + .map(debugSegment -> debugSegment.toDebugString(padding)).collect(Collectors.joining("\n")); + } + + private class DebugSegment { + final int start; + final int length; + final String description; + + DebugSegment(int start, int length, String description) { + this.start = start; + this.length = length; + this.description = description; + } + + @Override + public String toString() { + return toDebugString(0); + } + + private String toDebugString(int padding) { + String result = "0x"; + String hexString = Utils.toHexString(Arrays.copyOfRange(data, start, start + length), length, ""); + result += StringUtils.rightPad(hexString, padding, " "); + result += ": " + description; + return result; + } + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Measurand.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Measurand.java index 1693b73445963..1dbe4935ba83b 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Measurand.java +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Measurand.java @@ -138,8 +138,10 @@ public enum Measurand { // `LIGHTNING_POWER` is the name in the spec, so we keep it here as it LIGHTNING_POWER("lightning-counter", 0x62, "lightning counter for the day", MeasureType.LIGHTNING_COUNTER), - TF_USRX("temperature-external-channel", new int[] { 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A }, - "Soil or Water temperature", MeasureType.TEMPERATURE), + TF_USRX(new int[] { 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A }, + new MeasurandParser("temperature-external-channel", "Soil or Water temperature", MeasureType.TEMPERATURE), + // skip battery-level, since it is read via Command.CMD_READ_SENSOR_ID_NEW + new Skip(1)), ITEM_SENSOR_CO2(0x70, new MeasurandParser("sensor-co2-temperature", "Temperature (CO₂-Sensor)", MeasureType.TEMPERATURE), @@ -240,18 +242,20 @@ public enum Measurand { } private int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context, - @Nullable ParserCustomizationType customizationType, List result) { + @Nullable ParserCustomizationType customizationType, List result, + DebugDetails debugDetails) { int subOffset = 0; for (Parser parser : parsers) { subOffset += parser.extractMeasuredValues(data, offset + subOffset, channel, context, customizationType, - result); + result, debugDetails); } return subOffset; } private interface Parser { int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context, - @Nullable ParserCustomizationType customizationType, List result); + @Nullable ParserCustomizationType customizationType, List result, + DebugDetails debugDetails); } private static class Skip implements Parser { @@ -263,7 +267,9 @@ public Skip(int skip) { @Override public int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context, - @Nullable ParserCustomizationType customizationType, List result) { + @Nullable ParserCustomizationType customizationType, List result, + DebugDetails debugDetails) { + debugDetails.addDebugDetails(offset, skip, "skipped"); return skip; } } @@ -302,8 +308,14 @@ public SingleChannelMeasurand(Measurand measurand, @Nullable Integer channel) { } public int extractMeasuredValues(byte[] data, int offset, ConversionContext context, - @Nullable ParserCustomizationType customizationType, List result) { - return measurand.extractMeasuredValues(data, offset, channel, context, customizationType, result); + @Nullable ParserCustomizationType customizationType, List result, + DebugDetails debugDetails) { + return measurand.extractMeasuredValues(data, offset, channel, context, customizationType, result, + debugDetails); + } + + public String getDebugString() { + return measurand.name() + (channel == null ? "" : " channel " + channel); } } @@ -337,12 +349,17 @@ private static class MeasurandParser implements Parser { @Override public int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context, - @Nullable ParserCustomizationType customizationType, List result) { + @Nullable ParserCustomizationType customizationType, List result, + DebugDetails debugDetails) { MeasureType measureType = getMeasureType(customizationType); State state = measureType.toState(data, offset, context); if (state != null) { + debugDetails.addDebugDetails(offset, measureType.getByteSize(), + measureType.name() + ": " + state.toFullString()); ChannelTypeUID channelType = channelTypeUID == null ? measureType.getChannelTypeId() : channelTypeUID; result.add(new MeasuredValue(measureType, channelPrefix, channel, channelType, state, name)); + } else { + debugDetails.addDebugDetails(offset, measureType.getByteSize(), measureType.name() + ": null"); } return measureType.getByteSize(); } diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/ELVGatewayQueryService.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/ELVGatewayQueryService.java index 2e60c9f2ff27e..02e076e78706b 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/ELVGatewayQueryService.java +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/ELVGatewayQueryService.java @@ -21,6 +21,7 @@ import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command; import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.DebugDetails; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol; import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding; import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue; @@ -79,6 +80,10 @@ public List getMeasuredValues() { if (data == null) { return Collections.emptyList(); } - return fineOffsetDataParser.getMeasuredValues(data, conversionContext); + DebugDetails debugDetails = new DebugDetails(data, Command.CMD_WS980_LIVEDATA, Protocol.ELV); + List measuredValues = fineOffsetDataParser.getMeasuredValues(data, conversionContext, + debugDetails); + logger.trace("{}", debugDetails); + return measuredValues; } } diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParser.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParser.java index fc8922db53cda..c1ce0fd76ef9f 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParser.java +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParser.java @@ -27,6 +27,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.fineoffsetweatherstation.internal.Utils; import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.DebugDetails; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Measurand; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol; import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding; @@ -151,7 +152,7 @@ public Map getRegisteredSensors(byte[] data, return new SystemInfo(frequency, date, dst, useWh24); } - List getMeasuredValues(byte[] data, ConversionContext context) { + List getMeasuredValues(byte[] data, ConversionContext context, DebugDetails debugDetails) { /* * Pos| Length | Description * ------------------------------------------------- @@ -173,26 +174,31 @@ List getMeasuredValues(byte[] data, ConversionContext context) { var idx = 5; if (protocol == Protocol.ELV) { idx++; // at index 5 there is an additional Byte being set to 0x04 + debugDetails.addDebugDetails(5, 1, "ELV extra byte"); } - return readMeasuredValues(data, idx, context, protocol.getParserCustomizationType()); + return readMeasuredValues(data, idx, context, protocol.getParserCustomizationType(), debugDetails); } - List getRainData(byte[] data, ConversionContext context) { - return readMeasuredValues(data, 5, context, Measurand.ParserCustomizationType.RAIN_READING); + List getRainData(byte[] data, ConversionContext context, DebugDetails debugDetails) { + return readMeasuredValues(data, 5, context, Measurand.ParserCustomizationType.RAIN_READING, debugDetails); } private List readMeasuredValues(byte[] data, int idx, ConversionContext context, - Measurand.@Nullable ParserCustomizationType protocol) { + Measurand.@Nullable ParserCustomizationType protocol, DebugDetails debugDetails) { var size = toUInt16(data, 3); + List result = new ArrayList<>(); while (idx < size) { byte code = data[idx++]; Measurand.SingleChannelMeasurand measurand = Measurand.getByCode(code); if (measurand == null) { logger.warn("failed to get measurand 0x{}", Integer.toHexString(code)); + debugDetails.addDebugDetails(idx - 1, 1, "unknown measurand"); return result; + } else { + debugDetails.addDebugDetails(idx - 1, 1, "measurand " + measurand.getDebugString()); } - idx += measurand.extractMeasuredValues(data, idx, context, protocol, result); + idx += measurand.extractMeasuredValues(data, idx, context, protocol, result, debugDetails); } return result; } diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetGatewayQueryService.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetGatewayQueryService.java index a5921976a5eee..fd049dea1dbfe 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetGatewayQueryService.java +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetGatewayQueryService.java @@ -22,6 +22,7 @@ import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command; import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.DebugDetails; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol; import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding; import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue; @@ -38,6 +39,7 @@ */ @NonNullByDefault public class FineOffsetGatewayQueryService extends GatewayQueryService { + private static final Protocol PROTOCOL = Protocol.DEFAULT; private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayQueryService.class); private final FineOffsetDataParser fineOffsetDataParser; @@ -47,7 +49,7 @@ public class FineOffsetGatewayQueryService extends GatewayQueryService { public FineOffsetGatewayQueryService(FineOffsetGatewayConfiguration config, @Nullable ThingStatusListener thingStatusListener, ConversionContext conversionContext) { super(config, thingStatusListener); - this.fineOffsetDataParser = new FineOffsetDataParser(Protocol.DEFAULT); + this.fineOffsetDataParser = new FineOffsetDataParser(PROTOCOL); this.conversionContext = conversionContext; } @@ -92,18 +94,24 @@ public Collection getMeasuredValues() { byte[] data = executeCommand(Command.CMD_GW1000_LIVEDATA); if (data != null) { - List measuredValues = fineOffsetDataParser.getMeasuredValues(data, conversionContext); + DebugDetails debugDetails = new DebugDetails(data, Command.CMD_GW1000_LIVEDATA, PROTOCOL); + List measuredValues = fineOffsetDataParser.getMeasuredValues(data, conversionContext, + debugDetails); for (MeasuredValue measuredValue : measuredValues) { valuePerChannel.put(measuredValue.getChannelId(), measuredValue); } + logger.trace("{}", debugDetails); } data = executeCommand(Command.CMD_READ_RAIN); if (data != null) { - List measuredRainValues = fineOffsetDataParser.getRainData(data, conversionContext); + DebugDetails debugDetails = new DebugDetails(data, Command.CMD_READ_RAIN, PROTOCOL); + List measuredRainValues = fineOffsetDataParser.getRainData(data, conversionContext, + debugDetails); for (MeasuredValue measuredValue : measuredRainValues) { valuePerChannel.put(measuredValue.getChannelId(), measuredValue); } + logger.trace("{}", debugDetails); } return valuePerChannel.values(); diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/GatewayQueryService.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/GatewayQueryService.java index 4edd46dc1526e..e4edd15cbcf29 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/GatewayQueryService.java +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/GatewayQueryService.java @@ -44,7 +44,7 @@ */ @NonNullByDefault public abstract class GatewayQueryService implements AutoCloseable { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); private static final Lock REQUEST_LOCK = new ReentrantLock(); diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/test/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParserTest.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/test/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParserTest.java index 4a5e2082de9c3..1625d7fb44f81 100644 --- a/bundles/org.openhab.binding.fineoffsetweatherstation/src/test/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParserTest.java +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/test/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParserTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command; import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.DebugDetails; import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol; import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue; import org.openhab.core.util.HexUtils; @@ -32,9 +33,11 @@ class FineOffsetDataParserTest { @Test void testLiveDataWH45() { - List data = new FineOffsetDataParser(Protocol.DEFAULT).getMeasuredValues(HexUtils.hexToBytes( - "FFFF2700510100D306280827EF0927EF020045074F0A00150B00000C0000150000000016000117001900000E0000100000110021120000002113000005850D00007000D12E0060005A005B005502AE028F0633"), - new ConversionContext(ZoneOffset.UTC)); + byte[] bytes = HexUtils.hexToBytes( + "FFFF2700510100D306280827EF0927EF020045074F0A00150B00000C0000150000000016000117001900000E0000100000110021120000002113000005850D00007000D12E0060005A005B005502AE028F0633"); + DebugDetails debugDetails = new DebugDetails(bytes, Command.CMD_GW1000_LIVEDATA, Protocol.DEFAULT); + List data = new FineOffsetDataParser(Protocol.DEFAULT).getMeasuredValues(bytes, + new ConversionContext(ZoneOffset.UTC), debugDetails); Assertions.assertThat(data) .extracting(MeasuredValue::getChannelId, measuredValue -> measuredValue.getState().toString()) .containsExactly(new Tuple("temperature-indoor", "21.1 °C"), new Tuple("humidity-indoor", "40 %"), @@ -54,12 +57,43 @@ void testLiveDataWH45() { new Tuple("sensor-co2-co2", "686 ppm"), new Tuple("sensor-co2-co2-24-hour-average", "655 ppm")); } + @Test + void testLiveDataWH34AndWh45() { + byte[] bytes = HexUtils.hexToBytes( + "FFFF2700540100CA063E0826EC0926EC02007A074C0A002F0B001F0C0023150000032016000017001A0086225558005A00620000000661654A5AF1601B1900266300884B7000CE3F001D00240016001E041A037B0695"); + DebugDetails debugDetails = new DebugDetails(bytes, Command.CMD_GW1000_LIVEDATA, Protocol.DEFAULT); + List data = new FineOffsetDataParser(Protocol.DEFAULT).getMeasuredValues(bytes, + new ConversionContext(ZoneOffset.UTC), debugDetails); + Assertions.assertThat(data) + .extracting(MeasuredValue::getChannelId, measuredValue -> measuredValue.getState().toString()) + .containsExactly(new Tuple("temperature-indoor", "20.2 °C"), new Tuple("humidity-indoor", "62 %"), + new Tuple("pressure-absolute", "996.4 hPa"), new Tuple("pressure-relative", "996.4 hPa"), + new Tuple("temperature-outdoor", "12.2 °C"), new Tuple("humidity-outdoor", "76 %"), + new Tuple("direction-wind", "47 °"), new Tuple("speed-wind", "3.1 m/s"), + new Tuple("speed-gust", "3.5 m/s"), new Tuple("illumination", "80 lx"), + new Tuple("irradiation-uv", "0 mW/m²"), new Tuple("uv-index", "0"), + new Tuple("temperature-channel-1", "13.4 °C"), new Tuple("humidity-channel-1", "85 %"), + new Tuple("water-leak-channel-1", "OFF"), new Tuple("water-leak-channel-3", "OFF"), + new Tuple("lightning-counter", "6"), + new Tuple("lightning-time", "2023-11-07T15:42:41.000+0000"), + new Tuple("lightning-distance", "27 km"), new Tuple("wind-max-day", "3.8 m/s"), + new Tuple("temperature-external-channel-1", "13.6 °C"), + new Tuple("sensor-co2-temperature", "20.6 °C"), new Tuple("sensor-co2-humidity", "63 %"), + new Tuple("sensor-co2-pm10", "2.9 µg/m³"), + new Tuple("sensor-co2-pm10-24-hour-average", "3.6 µg/m³"), + new Tuple("sensor-co2-pm25", "2.2 µg/m³"), + new Tuple("sensor-co2-pm25-24-hour-average", "3 µg/m³"), + new Tuple("sensor-co2-co2", "1050 ppm"), + new Tuple("sensor-co2-co2-24-hour-average", "891 ppm")); + } + @Test void testLiveDataELV() { byte[] data = HexUtils.hexToBytes( "FFFF0B00500401010B0201120300620401120501120629072108254B09254B0A01480B00040C000A0E000000001000000021110000002E120000014F130000100714000012FD15000B4BB816086917056D35"); + DebugDetails debugDetails = new DebugDetails(data, Command.CMD_WS980_LIVEDATA, Protocol.ELV); List measuredValues = new FineOffsetDataParser(Protocol.ELV).getMeasuredValues(data, - new ConversionContext(ZoneOffset.UTC)); + new ConversionContext(ZoneOffset.UTC), debugDetails); Assertions.assertThat(measuredValues) .extracting(MeasuredValue::getChannelId, measuredValue -> measuredValue.getState().toString()) .containsExactly(new Tuple("temperature-indoor", "26.7 °C"), @@ -79,8 +113,9 @@ void testLiveDataELV() { void testRainData() { byte[] data = HexUtils .hexToBytes("FFFF5700290E000010000000001100000024120000003113000005030D00000F0064880000017A017B0030"); + DebugDetails debugDetails = new DebugDetails(data, Command.CMD_READ_RAIN, Protocol.DEFAULT); List measuredValues = new FineOffsetDataParser(Protocol.DEFAULT).getRainData(data, - new ConversionContext(ZoneOffset.UTC)); + new ConversionContext(ZoneOffset.UTC), debugDetails); Assertions.assertThat(measuredValues) .extracting(MeasuredValue::getChannelId, measuredValue -> measuredValue.getState().toString()) .containsExactly(new Tuple("rain-rate", "0 mm/h"), new Tuple("rain-day", "0 mm"), @@ -94,8 +129,9 @@ void testRainDataW90() { byte[] data = HexUtils.hexToBytes( "FFFF5700398000008300000009840000000985000000C786000000C7810000870064006400640064006400640064006400640064880900007A02BF"); Assertions.assertThat(Command.CMD_READ_RAIN.isResponseValid(data)).isTrue(); + DebugDetails debugDetails = new DebugDetails(data, Command.CMD_READ_RAIN, Protocol.DEFAULT); List measuredValues = new FineOffsetDataParser(Protocol.DEFAULT).getRainData(data, - new ConversionContext(ZoneOffset.UTC)); + new ConversionContext(ZoneOffset.UTC), debugDetails); Assertions.assertThat(measuredValues) .extracting(MeasuredValue::getChannelId, measuredValue -> measuredValue.getState().toString()) .containsExactly(new Tuple("piezo-rain-rate", "0 mm/h"), new Tuple("piezo-rain-day", "0.9 mm"), From 321a3f496886234b25247bc5cd06c1cccc5b0efd Mon Sep 17 00:00:00 2001 From: mlobstein Date: Fri, 10 Nov 2023 16:51:43 -0600 Subject: [PATCH 068/146] [tasmotaplug] Initial contribution (#15857) Signed-off-by: Michael Lobstein --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../org.openhab.binding.tasmotaplug/NOTICE | 13 ++ .../org.openhab.binding.tasmotaplug/README.md | 77 +++++++ .../org.openhab.binding.tasmotaplug/pom.xml | 17 ++ .../src/main/feature/feature.xml | 9 + .../internal/TasmotaPlugBindingConstants.java | 52 +++++ .../internal/TasmotaPlugConfiguration.java | 32 +++ .../internal/TasmotaPlugHandlerFactory.java | 64 ++++++ .../internal/handler/TasmotaPlugHandler.java | 202 ++++++++++++++++++ .../src/main/resources/OH-INF/addon/addon.xml | 11 + .../OH-INF/i18n/tasmotaplug.properties | 35 +++ .../resources/OH-INF/thing/thing-types.xml | 65 ++++++ bundles/pom.xml | 1 + 14 files changed, 584 insertions(+) create mode 100644 bundles/org.openhab.binding.tasmotaplug/NOTICE create mode 100644 bundles/org.openhab.binding.tasmotaplug/README.md create mode 100644 bundles/org.openhab.binding.tasmotaplug/pom.xml create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugBindingConstants.java create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugConfiguration.java create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugHandlerFactory.java create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/handler/TasmotaPlugHandler.java create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug.properties create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index f41c1b47ff210..35fae36c6246d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -344,6 +344,7 @@ /bundles/org.openhab.binding.tado/ @dfrommi @andrewfg /bundles/org.openhab.binding.tankerkoenig/ @dolic @JueBag /bundles/org.openhab.binding.tapocontrol/ @wildcs +/bundles/org.openhab.binding.tasmotaplug/ @mlobstein /bundles/org.openhab.binding.telegram/ @ZzetT /bundles/org.openhab.binding.teleinfo/ @Nokyyz @olivierkeke /bundles/org.openhab.binding.tellstick/ @openhab/add-ons-maintainers diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 5db1ce32a5276..62140dbb86166 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1706,6 +1706,11 @@ org.openhab.binding.tapocontrol ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.tasmotaplug + ${project.version} + org.openhab.addons.bundles org.openhab.binding.telegram diff --git a/bundles/org.openhab.binding.tasmotaplug/NOTICE b/bundles/org.openhab.binding.tasmotaplug/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.tasmotaplug/README.md b/bundles/org.openhab.binding.tasmotaplug/README.md new file mode 100644 index 0000000000000..9ef89d2d27fef --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/README.md @@ -0,0 +1,77 @@ +# TasmotaPlug Binding + +This binding connects Tasmota flashed smart plugs with 1, 2, 3 or 4 relay channels to openHAB. +The plug must report the status of the relay via the url `http://$PLUG_IP/cm?cmnd=Power` in order for the binding to work. +See the [Tasmota Supported Devices Repository](https://templates.blakadder.com/plug.html) for a list of supported plugs. + +## Supported Things + +There is exactly one supported thing type, which represents any supported Tasmota smart plug. +It has the `plug` id. +Multiple Things can be added if more than one plug is to be controlled. + +## Discovery + +Discovery is not supported. All things must be added manually. + +## Thing Configuration + +At minimum, the host name must be specified. +The refresh interval and number of channels can be overridden from the default. + +| Parameter | Description | +|-------------|-----------------------------------------------------------------------------------------| +| hostName | The host name or IP address of the plug. Mandatory. | +| refresh | Overrides the refresh interval of the plug status. Optional, the default is 30 seconds. | +| numChannels | Number of channels on the Tasmota Plug (1-4). Optional, the default is 1 | +| username | Username for authentication with the Tasmota Plug. Default 'admin' | +| password | Password for authentication with the Tasmota Plug, if not supplied auth is disabled. | + +## Channels + +The number of channels depends of on the `numChannels` configuration parameter. +Channels above the number specified are automatically removed. +Therefore `numChannels` cannot be changed upward after Thing creation. +If the number of channels must be increased, delete the Thing and re-create it with the correct number. + +| Channel ID | Item Type | Description | +|------------|-----------|-----------------------------------------| +| power | Switch | Turns the smart plug relay #1 ON or OFF | +| power2 | Switch | Turns the smart plug relay #2 ON or OFF | +| power3 | Switch | Turns the smart plug relay #3 ON or OFF | +| power4 | Switch | Turns the smart plug relay #4 ON or OFF | + +## Full Example + +tasmotaplug.things: + +```java +tasmotaplug:plug:plug1 "Plug 1" [ hostName="192.168.10.1", refresh=30 ] +tasmotaplug:plug:plug2 "Plug 2" [ hostName="myplug2", refresh=30 ] +``` + +tasmotaplug.items: + +```java +Switch Plug1 "Plug 1 Power" { channel="tasmotaplug:plug:plug1:power" } + +Switch Plug2a "4ch Power 1" { channel="tasmotaplug:plug:plug2:power" } +Switch Plug2b "4ch Power 2" { channel="tasmotaplug:plug:plug2:power2" } +Switch Plug2c "4ch Power 3" { channel="tasmotaplug:plug:plug2:power3" } +Switch Plug2d "4ch Power 4" { channel="tasmotaplug:plug:plug2:power4" } +``` + +tasmotaplug.sitemap: + +```perl +sitemap tasmotaplug label="My Tasmota Plugs" { + Frame label="Plugs" { + Switch item=Plug1 + + Switch item=Plug2a + Switch item=Plug2b + Switch item=Plug2c + Switch item=Plug2d + } +} +``` diff --git a/bundles/org.openhab.binding.tasmotaplug/pom.xml b/bundles/org.openhab.binding.tasmotaplug/pom.xml new file mode 100644 index 0000000000000..f6a4ab464c2d6 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.1.0-SNAPSHOT + + + org.openhab.binding.tasmotaplug + + openHAB Add-ons :: Bundles :: Tasmota Plug Binding + + diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/feature/feature.xml b/bundles/org.openhab.binding.tasmotaplug/src/main/feature/feature.xml new file mode 100644 index 0000000000000..21fbcfa496035 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.tasmotaplug/${project.version} + + diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugBindingConstants.java b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugBindingConstants.java new file mode 100644 index 0000000000000..98539aa39533e --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugBindingConstants.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.tasmotaplug.internal; + +import java.util.List; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link TasmotaPlugBinding} class defines common constants, which are + * used across the whole binding. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class TasmotaPlugBindingConstants { + public static final String BINDING_ID = "tasmotaplug"; + + public static final int DEFAULT_REFRESH_PERIOD_SEC = 30; + public static final int DEFAULT_NUM_CHANNELS = 1; + + public static final String CMD_URI = "/cm?cmnd=%s"; + public static final String CMD_URI_AUTH = "/cm?user=%s&password=%s&cmnd=%s"; + + public static final String ON = "ON"; + public static final String OFF = "OFF"; + public static final String BLANK = ""; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_PLUG = new ThingTypeUID(BINDING_ID, "plug"); + + // List of all Channel id's + public static final String POWER = "power"; + public static final String POWER2 = "power2"; + public static final String POWER3 = "power3"; + public static final String POWER4 = "power4"; + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PLUG); + public static final List SUPPORTED_CHANNEL_IDS = List.of(POWER, POWER2, POWER3, POWER4); +} diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugConfiguration.java b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugConfiguration.java new file mode 100644 index 0000000000000..865091e4b06e1 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugConfiguration.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.tasmotaplug.internal; + +import static org.openhab.binding.tasmotaplug.internal.TasmotaPlugBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link TasmotaPlugConfiguration} is the class used to match the + * thing configuration. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class TasmotaPlugConfiguration { + public String hostName = BLANK; + public int refresh = DEFAULT_REFRESH_PERIOD_SEC; + public int numChannels = DEFAULT_NUM_CHANNELS; + public String username = BLANK; + public String password = BLANK; +} diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugHandlerFactory.java b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugHandlerFactory.java new file mode 100644 index 0000000000000..846933315f506 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/TasmotaPlugHandlerFactory.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.tasmotaplug.internal; + +import static org.openhab.binding.tasmotaplug.internal.TasmotaPlugBindingConstants.THING_TYPE_PLUG; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.tasmotaplug.internal.handler.TasmotaPlugHandler; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link TasmotaPlugHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.tasmotaplug") +public class TasmotaPlugHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PLUG); + private final HttpClient httpClient; + + @Activate + public TasmotaPlugHandlerFactory(final @Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + if (THING_TYPE_PLUG.equals(thing.getThingTypeUID())) { + return new TasmotaPlugHandler(thing, httpClient); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/handler/TasmotaPlugHandler.java b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/handler/TasmotaPlugHandler.java new file mode 100644 index 0000000000000..b1223159c34e2 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/java/org/openhab/binding/tasmotaplug/internal/handler/TasmotaPlugHandler.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.tasmotaplug.internal.handler; + +import static org.eclipse.jetty.http.HttpStatus.OK_200; +import static org.openhab.binding.tasmotaplug.internal.TasmotaPlugBindingConstants.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.IntStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.openhab.binding.tasmotaplug.internal.TasmotaPlugConfiguration; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link TasmotaPlugHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class TasmotaPlugHandler extends BaseThingHandler { + private static final String PASSWORD_REGEX = "&password=(.*)&"; + private static final String PASSWORD_MASK = "&password=xxxx&"; + + private final Logger logger = LoggerFactory.getLogger(TasmotaPlugHandler.class); + private final HttpClient httpClient; + + private @Nullable ScheduledFuture refreshJob; + + private String plugHost = BLANK; + private int refreshPeriod = DEFAULT_REFRESH_PERIOD_SEC; + private int numChannels = DEFAULT_NUM_CHANNELS; + private boolean isAuth = false; + private String user = BLANK; + private String pass = BLANK; + + public TasmotaPlugHandler(Thing thing, HttpClient httpClient) { + super(thing); + this.httpClient = httpClient; + } + + @Override + public void initialize() { + logger.debug("Initializing TasmotaPlug handler."); + TasmotaPlugConfiguration config = getConfigAs(TasmotaPlugConfiguration.class); + + final String hostName = config.hostName; + final String username = config.username; + final String password = config.password; + refreshPeriod = config.refresh; + numChannels = config.numChannels; + + if (hostName.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.configuration-error-hostname"); + return; + } + + if (!username.isBlank() && !password.isBlank()) { + isAuth = true; + user = username; + pass = password; + } + + plugHost = "http://" + hostName; + + // remove the channels we are not using + if (this.numChannels < SUPPORTED_CHANNEL_IDS.size()) { + List channels = new ArrayList<>(this.getThing().getChannels()); + + List channelsToRemove = IntStream.range(this.numChannels + 1, SUPPORTED_CHANNEL_IDS.size() + 1) + .boxed().toList(); + + channelsToRemove.forEach(channel -> { + channels.removeIf(c -> (c.getUID().getId().equals(POWER + channel))); + }); + updateThing(editThing().withChannels(channels).build()); + } + + updateStatus(ThingStatus.UNKNOWN); + startAutomaticRefresh(); + } + + @Override + public void dispose() { + logger.debug("Disposing the TasmotaPlug handler."); + + ScheduledFuture refreshJob = this.refreshJob; + if (refreshJob != null) { + refreshJob.cancel(true); + this.refreshJob = null; + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (channelUID.getId().contains(POWER)) { + if (command instanceof OnOffType) { + getCommand(channelUID.getId(), command); + } else { + updateChannelState(channelUID.getId()); + } + } else { + logger.warn("Unsupported command: {}", command.toString()); + } + } + + /** + * Start the job to periodically update the state of the plug + */ + private void startAutomaticRefresh() { + ScheduledFuture refreshJob = this.refreshJob; + if (refreshJob == null || refreshJob.isCancelled()) { + refreshJob = null; + this.refreshJob = scheduler.scheduleWithFixedDelay(() -> { + SUPPORTED_CHANNEL_IDS.stream().limit(numChannels).forEach(channelId -> { + updateChannelState(channelId); + }); + }, 0, refreshPeriod, TimeUnit.SECONDS); + } + } + + private void updateChannelState(String channelId) { + final String plugState = getCommand(channelId, null); + if (plugState.contains(ON)) { + updateState(channelId, OnOffType.ON); + } else if (plugState.contains(OFF)) { + updateState(channelId, OnOffType.OFF); + } + } + + private String getCommand(String channelId, @Nullable Command command) { + final String plugChannel = channelId.substring(0, 1).toUpperCase() + channelId.substring(1); + String url; + + if (isAuth) { + url = String.format(CMD_URI_AUTH, user, pass, plugChannel); + } else { + url = String.format(CMD_URI, plugChannel); + } + + if (command != null) { + url += "%20" + command; + } + + try { + logger.trace("Sending GET request to {}{}", plugHost, maskPassword(url)); + ContentResponse contentResponse = httpClient.GET(plugHost + url); + logger.trace("Response: {}", contentResponse.getContentAsString()); + + if (contentResponse.getStatus() != OK_200) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/offline.communication-error.http-failure [\"" + contentResponse.getStatus() + "\"]"); + return BLANK; + } + + updateStatus(ThingStatus.ONLINE); + return contentResponse.getContentAsString(); + } catch (TimeoutException | ExecutionException e) { + logger.debug("Error executing Tasmota GET request: '{}{}', {}", plugHost, maskPassword(url), + e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + } catch (InterruptedException e) { + logger.debug("InterruptedException executing Tasmota GET request: '{}{}', {}", plugHost, maskPassword(url), + e.getMessage()); + Thread.currentThread().interrupt(); + } + return BLANK; + } + + private String maskPassword(String input) { + return isAuth ? input.replaceAll(PASSWORD_REGEX, PASSWORD_MASK) : input; + } +} diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..f45da95755dbf --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,11 @@ + + + + binding + Tasmota Plug Binding + Controls Wi-Fi Smart Plugs flashed with Tasmota + local + + diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug.properties b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug.properties new file mode 100644 index 0000000000000..c388401f09851 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug.properties @@ -0,0 +1,35 @@ +# add-on + +addon.tasmotaplug.name = Tasmota Plug Binding +addon.tasmotaplug.description = Controls Wi-Fi Smart Plugs flashed with Tasmota + +# thing types + +thing-type.tasmotaplug.plug.label = Plug +thing-type.tasmotaplug.plug.description = Tasmota Smart Plug +thing-type.tasmotaplug.plug.channel.power.label = Power +thing-type.tasmotaplug.plug.channel.power.description = Controls the smart plug relay for the 1st channel +thing-type.tasmotaplug.plug.channel.power2.label = Power 2 +thing-type.tasmotaplug.plug.channel.power2.description = Controls the smart plug relay for the 2nd channel +thing-type.tasmotaplug.plug.channel.power3.label = Power 3 +thing-type.tasmotaplug.plug.channel.power3.description = Controls the smart plug relay for the 3rd channel +thing-type.tasmotaplug.plug.channel.power4.label = Power 4 +thing-type.tasmotaplug.plug.channel.power4.description = Controls the smart plug relay for the 4th channel + +# thing types config + +thing-type.config.tasmotaplug.plug.hostName.label = Plug Host Name/IP Address +thing-type.config.tasmotaplug.plug.hostName.description = Host name or IP address of the plug +thing-type.config.tasmotaplug.plug.numChannels.label = Number of Channels +thing-type.config.tasmotaplug.plug.numChannels.description = Number of channels on the Tasmota plug (1-4) default 1 +thing-type.config.tasmotaplug.plug.password.label = Password +thing-type.config.tasmotaplug.plug.password.description = Tasmota password +thing-type.config.tasmotaplug.plug.refresh.label = Refresh Interval +thing-type.config.tasmotaplug.plug.refresh.description = Specifies the refresh interval in seconds +thing-type.config.tasmotaplug.plug.username.label = Username +thing-type.config.tasmotaplug.plug.username.description = Tasmota username + +# thing status descriptions + +offline.communication-error.http-failure = Tasmota http response code was: {0} +offline.configuration-error-hostname = Plug hostname must be specified diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..1382b7cd44a42 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,65 @@ + + + + + + + + Tasmota Smart Plug + + + + + + Controls the smart plug relay for the 1st channel + + + + Controls the smart plug relay for the 2nd channel + + + + Controls the smart plug relay for the 3rd channel + + + + Controls the smart plug relay for the 4th channel + + + + + + network-address + + Host name or IP address of the plug + + + + Specifies the refresh interval in seconds + 30 + + + + Number of channels on the Tasmota plug (1-4) default 1 + 1 + + + + Tasmota username + admin + true + + + password + + Tasmota password + true + + + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 92362c8ce8ca3..655b460a41a80 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -376,6 +376,7 @@ org.openhab.binding.tado org.openhab.binding.tankerkoenig org.openhab.binding.tapocontrol + org.openhab.binding.tasmotaplug org.openhab.binding.telegram org.openhab.binding.teleinfo org.openhab.binding.tellstick From bb60b263e713aafca0a69960f601c9d14cc55619 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 11 Nov 2023 11:51:03 +0100 Subject: [PATCH 069/146] [jdbc] Fix addon id for SQLite (#15877) Fixes #15847 Signed-off-by: Jacob Laursen --- .../src/main/resources/OH-INF/addon/addon-sqlite.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.persistence.jdbc/src/main/resources/OH-INF/addon/addon-sqlite.xml b/bundles/org.openhab.persistence.jdbc/src/main/resources/OH-INF/addon/addon-sqlite.xml index 54c1ba698459c..e098326ad51a8 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/resources/OH-INF/addon/addon-sqlite.xml +++ b/bundles/org.openhab.persistence.jdbc/src/main/resources/OH-INF/addon/addon-sqlite.xml @@ -1,5 +1,5 @@ - From 4193a49a5c83790f808ff4bcc7f9609f774eeaf2 Mon Sep 17 00:00:00 2001 From: hutcheonk <76188428+hutcheonk@users.noreply.github.com> Date: Sat, 11 Nov 2023 11:51:15 +0000 Subject: [PATCH 070/146] Update README.md (#15019) Add "MQTT Client Status Messages Mode setting" to ESP Config.. Signed-off-by: hutcheonk <76188428+hutcheonk@users.noreply.github.com> --- bundles/org.openhab.binding.mqtt.espmilighthub/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bundles/org.openhab.binding.mqtt.espmilighthub/README.md b/bundles/org.openhab.binding.mqtt.espmilighthub/README.md index 3c1af7827a8a1..1a4e5587b02ed 100644 --- a/bundles/org.openhab.binding.mqtt.espmilighthub/README.md +++ b/bundles/org.openhab.binding.mqtt.espmilighthub/README.md @@ -45,6 +45,9 @@ Leave this blank. **MQTT Client Status Topic:** `milight/status` +**MQTT Client Status Messages Mode:** +Simple. + **group_state_fields:** IMPORTANT: Make sure only the following are ticked: From 7f14a0a79ca831c3d48cbdd954a6e0e809f9043e Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 11 Nov 2023 13:30:52 +0100 Subject: [PATCH 071/146] Implement TimeSeriesProfile (#15873) Signed-off-by: Jacob Laursen --- .../internal/profile/VATTransformationProfile.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfile.java b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfile.java index d95813227aec5..60b8bed26fe45 100644 --- a/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfile.java +++ b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfile.java @@ -23,12 +23,13 @@ import org.openhab.core.thing.profiles.ProfileCallback; import org.openhab.core.thing.profiles.ProfileContext; import org.openhab.core.thing.profiles.ProfileTypeUID; -import org.openhab.core.thing.profiles.StateProfile; +import org.openhab.core.thing.profiles.TimeSeriesProfile; import org.openhab.core.transform.TransformationException; import org.openhab.core.transform.TransformationHelper; import org.openhab.core.transform.TransformationService; import org.openhab.core.types.Command; import org.openhab.core.types.State; +import org.openhab.core.types.TimeSeries; import org.openhab.core.types.Type; import org.openhab.core.types.UnDefType; import org.openhab.transform.vat.internal.config.VATConfig; @@ -41,7 +42,7 @@ * @author Jacob Laursen - Initial contribution */ @NonNullByDefault -public class VATTransformationProfile implements StateProfile { +public class VATTransformationProfile implements TimeSeriesProfile { private final Logger logger = LoggerFactory.getLogger(VATTransformationProfile.class); @@ -83,6 +84,14 @@ public void onStateUpdateFromHandler(State state) { callback.sendUpdate((State) transformState(state)); } + @Override + public void onTimeSeriesFromHandler(TimeSeries timeSeries) { + TimeSeries transformedTimeSeries = new TimeSeries(timeSeries.getPolicy()); + timeSeries.getStates() + .forEach(entry -> transformedTimeSeries.add(entry.timestamp(), (State) transformState(entry.state()))); + callback.sendTimeSeries(transformedTimeSeries); + } + private Type transformState(Type state) { String result = state.toFullString(); String percentage = getVATPercentage(); From 1a5e45665ff29c6241f6eff06d92b77921419d91 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 11 Nov 2023 13:39:05 +0100 Subject: [PATCH 072/146] Persist future prices (#15864) Resolves #15863 Signed-off-by: Jacob Laursen --- .../README.md | 7 +++ .../handler/EnergiDataServiceHandler.java | 48 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/bundles/org.openhab.binding.energidataservice/README.md b/bundles/org.openhab.binding.energidataservice/README.md index 7f43adf2cf5a2..8b717e19d9fc4 100644 --- a/bundles/org.openhab.binding.energidataservice/README.md +++ b/bundles/org.openhab.binding.energidataservice/README.md @@ -75,6 +75,13 @@ To include VAT for items linked to the `Number` channels, the [VAT profile](http This must be installed separately. Once installed, simply select "Value-Added Tax" as Profile when linking an item. +#### Persisting Time Series + +The binding offers support for persisting both historical and upcoming prices. +The recommended persistence strategy is `forecast`, as it ensures a clean history without redundancy. +Prices from the past 24 hours and all forthcoming prices will be stored. +Any changes that impact published prices (e.g. selecting or deselecting VAT Profile) will result in the replacement of persisted prices within this period. + #### Net Tariff Discounts are automatically taken into account for channel `net-tariff` so that it represents the actual price. diff --git a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java index 3241685f3d9c3..2a71212972c21 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java +++ b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java @@ -13,6 +13,7 @@ package org.openhab.binding.energidataservice.internal.handler; import static org.openhab.binding.energidataservice.internal.EnergiDataServiceBindingConstants.*; +import static org.openhab.core.types.TimeSeries.Policy.REPLACE; import java.math.BigDecimal; import java.time.Duration; @@ -24,6 +25,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Currency; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -65,6 +67,7 @@ import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; +import org.openhab.core.types.TimeSeries; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -180,6 +183,7 @@ private void refreshElectricityPrices() { updateStatus(ThingStatus.ONLINE); updatePrices(); + updateTimeSeries(); if (isLinked(CHANNEL_SPOT_PRICE) || isLinked(CHANNEL_HOURLY_PRICES)) { if (cacheManager.getNumberOfFutureSpotPrices() < 13) { @@ -348,6 +352,50 @@ private void updateHourlyPrices() { updateState(CHANNEL_HOURLY_PRICES, new StringType(gson.toJson(targetPrices))); } + private void updateTimeSeries() { + TimeSeries spotPriceTimeSeries = new TimeSeries(REPLACE); + Map datahubTimeSeriesMap = new HashMap<>(); + for (DatahubTariff datahubTariff : DatahubTariff.values()) { + datahubTimeSeriesMap.put(datahubTariff, new TimeSeries(REPLACE)); + } + + Map spotPriceMap = cacheManager.getSpotPrices(); + List> spotPrices = spotPriceMap.entrySet().stream() + .sorted(Map.Entry.comparingByKey()).toList(); + for (Entry spotPrice : spotPrices) { + Instant hourStart = spotPrice.getKey(); + if (isLinked(CHANNEL_SPOT_PRICE)) { + spotPriceTimeSeries.add(hourStart, new DecimalType(spotPrice.getValue())); + } + for (Map.Entry entry : datahubTimeSeriesMap.entrySet()) { + DatahubTariff datahubTariff = entry.getKey(); + String channelId = datahubTariff.getChannelId(); + if (!isLinked(channelId)) { + continue; + } + BigDecimal tariff = cacheManager.getTariff(datahubTariff, hourStart); + if (tariff != null) { + TimeSeries timeSeries = entry.getValue(); + timeSeries.add(hourStart, new DecimalType(tariff)); + } + } + } + if (spotPriceTimeSeries.size() > 0) { + sendTimeSeries(CHANNEL_SPOT_PRICE, spotPriceTimeSeries); + } + for (Map.Entry entry : datahubTimeSeriesMap.entrySet()) { + DatahubTariff datahubTariff = entry.getKey(); + String channelId = datahubTariff.getChannelId(); + if (!isLinked(channelId)) { + continue; + } + TimeSeries timeSeries = entry.getValue(); + if (timeSeries.size() > 0) { + sendTimeSeries(channelId, timeSeries); + } + } + } + /** * Get the configured {@link Currency} for spot prices. * From 901cf5f9bffe6ae5fff4c037cd35b0974d94a124 Mon Sep 17 00:00:00 2001 From: billfor Date: Sat, 11 Nov 2023 07:45:31 -0500 Subject: [PATCH 073/146] [mqtt] Add Topic Name for Incoming Payload Not Supported (#15872) Signed-off-by: bill Co-authored-by: Bill Forsyth --- .../java/org/openhab/binding/mqtt/generic/ChannelState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java index 71bdb24ee0180..607253f17bd82 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java @@ -192,7 +192,7 @@ public void processMessage(String topic, byte[] payload) { Command command = TypeParser.parseCommand(cachedValue.getSupportedCommandTypes(), strValue); if (command == null) { - logger.warn("Incoming payload '{}' not supported by type '{}'", strValue, + logger.warn("Incoming payload '{}' on '{}' not supported by type '{}'", strValue, topic, cachedValue.getClass().getSimpleName()); receivedOrTimeout(); return; From 51a71187b42153b4c151c3c711f00c1e569b026d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Sat, 11 Nov 2023 13:52:06 +0100 Subject: [PATCH 074/146] Adding unknown event type + bootstrap handling of topology changes (#15860) Signed-off-by: clinique --- .../netatmo/internal/api/data/EventType.java | 14 +++++++++++--- .../netatmo/internal/api/data/ModuleType.java | 2 +- .../internal/api/data/NetatmoConstants.java | 17 +++++++++++++++++ .../netatmo/internal/api/dto/WebhookEvent.java | 6 ++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java index d088853d2426d..1b4a6cd9eed51 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java @@ -20,14 +20,18 @@ import com.google.gson.annotations.SerializedName; /** - * This enum describes events generated by webhooks and the type of - * module they are related to according to API documentation + * This enum describes events generated by webhooks and the type of module they are related to according to + * API documentation * * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault public enum EventType { - UNKNOWN(), + UNKNOWN(ModuleType.UNKNOWN), + + @SerializedName("topology_changed") // Module configuration changed + TOPOLOGY_CHANGED(ModuleType.ACCOUNT), + @SerializedName("webhook_activation") // Ack of a 'webhook set' Api Call WEBHOOK_ACTIVATION(ModuleType.ACCOUNT), @@ -171,6 +175,10 @@ public enum EventType { private final Set appliesTo; EventType(ModuleType... appliesTo) { + if (appliesTo.length == 0) { + throw new IllegalArgumentException( + "Event type must be associated to at least a module type, please file a bug report."); + } this.appliesTo = Set.of(appliesTo); } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java index d9c5283faf08a..e5ba87e53912e 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java @@ -195,7 +195,7 @@ public int[] getSignalLevels() { : WIFI_SIGNAL_LEVELS; } throw new IllegalArgumentException( - "getSignalLevels should not be called for module type : '%s', please file a bug report." + "getSignalLevels should not be called for module type: '%s', please file a bug report." .formatted(name())); } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java index 471ff007ef71c..555eab75475e0 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java @@ -203,6 +203,23 @@ public enum Scope { UNKNOWN } + // Topology Changes + public enum TopologyChange { + @SerializedName("home_owner_added") + HOME_OWNER_ADDED, + @SerializedName("device_associated_to_user") + DEVICE_ASSOCIATED_TO_USER, + @SerializedName("device_associated_to_home") + DEVICE_ASSOCIATED_TO_HOME, + @SerializedName("device_updated") + DEVICE_UPDATED, + @SerializedName("device_associated_to_room") + DEVICE_ASSOCIATED_TO_ROOM, + @SerializedName("room_created") + ROOM_CREATED, + UNKNOWN + } + private static final Scope[] SMOKE_SCOPES = { Scope.READ_SMOKEDETECTOR }; private static final Scope[] CARBON_MONOXIDE_SCOPES = { Scope.READ_CARBONMONOXIDEDETECTOR }; private static final Scope[] AIR_CARE_SCOPES = { Scope.READ_HOMECOACH }; diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java index c8e84368112f6..602541944c5e2 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java @@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.TopologyChange; import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; import org.openhab.binding.netatmo.internal.deserialization.NAPushType; @@ -37,6 +38,7 @@ public class WebhookEvent extends Event { private String deviceId = ""; private @Nullable String snapshotUrl; private @Nullable String vignetteUrl; + private TopologyChange change = TopologyChange.UNKNOWN; private NAObjectMap persons = new NAObjectMap<>(); // Webhook does not provide the event generation time, so we'll use the event reception time private ZonedDateTime time = ZonedDateTime.now(); @@ -89,4 +91,8 @@ private void addNotBlank(Set collection, String value) { collection.add(value); } } + + public TopologyChange getChange() { + return change; + } } From 4560ec90b19365537bba5ef4a56b35e459b461f6 Mon Sep 17 00:00:00 2001 From: Robert D Date: Sat, 11 Nov 2023 14:01:37 +0100 Subject: [PATCH 075/146] [Senec] Fix for Senec firmware update (#15535) * Fix for Senec update Signed-off-by: querdenker2k --- .../org.openhab.binding.senechome/README.md | 18 ++--------- .../senechome/internal/SenecHomeApi.java | 28 ++++++++-------- .../internal/SenecHomeBindingConstants.java | 11 +------ .../internal/SenecHomeConfigurationDTO.java | 2 ++ .../senechome/internal/SenecHomeHandler.java | 16 +--------- .../internal/SenecHomeHandlerFactory.java | 32 +++++++++++++++++-- .../internal/json/SenecHomeResponse.java | 5 ++- .../OH-INF/i18n/senechome.properties | 2 ++ .../resources/OH-INF/thing/thing-types.xml | 18 +++++------ .../resources/OH-INF/update/instructions.xml | 20 ++++++++++++ 10 files changed, 84 insertions(+), 68 deletions(-) create mode 100644 bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/update/instructions.xml diff --git a/bundles/org.openhab.binding.senechome/README.md b/bundles/org.openhab.binding.senechome/README.md index 22fad8764cfac..c188babea896a 100644 --- a/bundles/org.openhab.binding.senechome/README.md +++ b/bundles/org.openhab.binding.senechome/README.md @@ -18,13 +18,14 @@ Examples: Lights, pool filters, wash machines, ... - only V3, V3duo have a power generator and thus MPPs (V3 has 2 MPP, V3duo has 3 MPP) - not equipped battery packs will return 0 for all ...Pack channels - currently channels for the first wallbox are implemented (senec could handle 4 wallboxes) +- Senec disables http access at ~30.08.2023 ## Thing Configuration demo.things ```java -Thing senechome:senechome:pvbattery [ hostname="192.168.0.128", refreshInterval=60, limitationTresholdValue=70, limitationDuration=60 ] +Thing senechome:senechome:pvbattery [ hostname="192.168.0.128", refreshInterval=60, limitationTresholdValue=70, limitationDuration=60, useHttp=false ] ``` If the thing goes online then the connection to the web interface is successful. @@ -69,13 +70,6 @@ The property `limitationTresholdValue` is used as threshold for channel `powerLi | gridVoltagePhase2 | volt | Grid voltage on Phase 2 | | gridVoltagePhase3 | volt | Grid voltage on Phase 3 | | gridFrequency | hertz | Grid frequency | -| liveBatCharge | kilo watt hour | Live Total Bat Charge | -| liveBatDischarge | kilo watt hour | Live Total Bat Discharge | -| liveGridImport | kilo watt hour | Live Total Grid Import | -| liveGridExport | kilo watt hour | Live Total Grid Export | -| liveHouseConsumption | kilo watt hour | Live Total House Consumption (without WB) | -| livePowerGenerator | kilo watt hour | Live Total PV generator generated energy | -| liveEnergyWallbox1 | kilo watt hour | Live Total Wallbox 1 charged energy | | chargedEnergyPack1 | kilo watt hour | total charged energy battery pack 1 | | chargedEnergyPack2 | kilo watt hour | total charged energy battery pack 2 | | chargedEnergyPack3 | kilo watt hour | total charged energy battery pack 3 | @@ -141,10 +135,6 @@ Number SenecGridVoltagePh2 "Voltage Level on Phase 2 [%d V]" { channel="senechome:senechome:pvbattery:gridVoltagePhase3" } Number SenecGridFrequency "Grid Frequency [%.2f Hz]" { channel="senechome:senechome:pvbattery:gridFrequency" } Number SenecBatteryVoltage "Battery Voltage [%.1f V]" { channel="senechome:senechome:pvbattery:batteryVoltage" } -Number SenecLiveBatCharge "Live Bat Charge [%d kWh]" { channel="senechome:senechome:pvbattery:liveBatCharge" } -Number SenecLiveBatDischarge "Live Bat Discharge [%d kWh]" { channel="senechome:senechome:pvbattery:liveBatDischarge" } -Number SenecLiveGridImport "Live Grid Import [%d kWh]" { channel="senechome:senechome:pvbattery:liveGridImport" } -Number SenecLiveGridExport "Live Grid Export [%d kWh]" { channel="senechome:senechome:pvbattery:liveGridExport" } ``` ## Sitemap @@ -176,10 +166,6 @@ Text label="Power Grid"{ Default item=SenecGridVoltagePh3 Default item=SenecGridFrequency Default item=SenecBatteryVoltage - Default item=SenecLiveBatCharge - Default item=SenecLiveBatDischarge - Default item=SenecLiveGridImport - Default item=SenecLiveGridExport } } ``` diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java index da5f773d5b418..22f57ad935bd2 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java @@ -13,7 +13,6 @@ package org.openhab.binding.senechome.internal; import java.io.IOException; -import java.net.MalformedURLException; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -33,19 +32,17 @@ import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; -import com.google.gson.stream.MalformedJsonException; /** * The {@link SenecHomeApi} class configures http client and * performs status requests * * @author Steven Schwarznau - Initial contribution + * @author Robert Delbrück - Update for Senec API changes * */ @NonNullByDefault public class SenecHomeApi { - private static final String HTTP_PROTO_PREFIX = "http://"; - private final Logger logger = LoggerFactory.getLogger(SenecHomeApi.class); private final HttpClient httpClient; private final Gson gson = new Gson(); @@ -66,28 +63,33 @@ public void setHostname(String hostname) { * To receive new values, just modify the Json objects and add them to the thing channels * * @return Instance of SenecHomeResponse - * @throws MalformedURLException Configuration/URL is wrong + * @throws TimeoutException Communication failed (Timeout) + * @throws ExecutionException Communication failed * @throws IOException Communication failed + * @throws InterruptedException Communication failed (Interrupted) + * @throws JsonSyntaxException Received response has an invalid json syntax */ public SenecHomeResponse getStatistics() - throws InterruptedException, TimeoutException, ExecutionException, IOException { - String location = HTTP_PROTO_PREFIX + hostname; + throws TimeoutException, ExecutionException, IOException, InterruptedException, JsonSyntaxException { + String location = hostname + "/lala.cgi"; + logger.trace("sending request to: {}", location); Request request = httpClient.newRequest(location); request.header(HttpHeader.ACCEPT, MimeTypes.Type.APPLICATION_JSON.asString()); - request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString()); + request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()); ContentResponse response = null; try { - response = request.method(HttpMethod.POST) - .content(new StringContentProvider(gson.toJson(new SenecHomeResponse()))).send(); + String dataToSend = gson.toJson(new SenecHomeResponse()); + logger.trace("data to send: {}", dataToSend); + response = request.method(HttpMethod.POST).content(new StringContentProvider(dataToSend)).send(); if (response.getStatus() == HttpStatus.OK_200) { - return Objects.requireNonNull(gson.fromJson(response.getContentAsString(), SenecHomeResponse.class)); + String responseString = response.getContentAsString(); + return Objects.requireNonNull(gson.fromJson(responseString, SenecHomeResponse.class)); } else { logger.trace("Got unexpected response code {}", response.getStatus()); throw new IOException("Got unexpected response code " + response.getStatus()); } - } catch (MalformedJsonException | JsonSyntaxException | InterruptedException | TimeoutException - | ExecutionException e) { + } catch (JsonSyntaxException | InterruptedException | TimeoutException | ExecutionException e) { String errorMessage = "\nlocation: " + location; errorMessage += "\nrequest: " + request.toString(); errorMessage += "\nrequest.getHeaders: " + request.getHeaders(); diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java index 2a73dda009ac1..ad48f43dd41b8 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java @@ -23,7 +23,7 @@ */ @NonNullByDefault public class SenecHomeBindingConstants { - private static final String BINDING_ID = "senechome"; + protected static final String BINDING_ID = "senechome"; private static final String THING_BASE_ID = "senechome"; public static final ThingTypeUID THING_TYPE_SENEC_HOME_BATTERY = new ThingTypeUID(BINDING_ID, THING_BASE_ID); @@ -65,15 +65,6 @@ public class SenecHomeBindingConstants { public static final String CHANNEL_SENEC_GRID_VOLTAGE_PH3 = "gridVoltagePhase3"; public static final String CHANNEL_SENEC_GRID_FREQUENCY = "gridFrequency"; - // SenecHomeStatistics - public static final String CHANNEL_SENEC_LIVE_BAT_CHARGE = "liveBatCharge"; - public static final String CHANNEL_SENEC_LIVE_BAT_DISCHARGE = "liveBatDischarge"; - public static final String CHANNEL_SENEC_LIVE_GRID_IMPORT = "liveGridImport"; - public static final String CHANNEL_SENEC_LIVE_GRID_EXPORT = "liveGridExport"; - public static final String CHANNEL_SENEC_LIVE_HOUSE_CONSUMPTION = "liveHouseConsumption"; - public static final String CHANNEL_SENEC_LIVE_POWER_GENERATOR = "livePowerGenerator"; - public static final String CHANNEL_SENEC_LIVE_ENERGY_WALLBOX1 = "liveEnergyWallbox1"; - // SenecHomeBattery public static final String CHANNEL_SENEC_CHARGED_ENERGY_PACK1 = "chargedEnergyPack1"; public static final String CHANNEL_SENEC_CHARGED_ENERGY_PACK2 = "chargedEnergyPack2"; diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeConfigurationDTO.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeConfigurationDTO.java index af7d25d7c30c0..58b1d48bc7330 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeConfigurationDTO.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeConfigurationDTO.java @@ -16,10 +16,12 @@ * The {@link SenecHomeConfigurationDTO} class contains fields mapping thing configuration parameters. * * @author Steven Schwarznau - Initial contribution + * @author Robert Delbrück - Add useHttp */ public class SenecHomeConfigurationDTO { public String hostname; public int refreshInterval = 15; public int limitationTresholdValue = 95; public int limitationDuration = 120; + public boolean useHttp = false; } diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java index a7b5e757efe3f..5ac74036047bc 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java @@ -124,7 +124,7 @@ protected void stopJobIfRunning() { @Override public void initialize() { config = getConfigAs(SenecHomeConfigurationDTO.class); - senecHomeApi.setHostname(config.hostname); + senecHomeApi.setHostname("%s://%s".formatted(config.useHttp ? "http" : "https", config.hostname)); refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval, TimeUnit.SECONDS); limitationStatus = null; } @@ -197,20 +197,6 @@ private void refresh() { updateQtyState(CHANNEL_SENEC_GRID_VOLTAGE_PH3, response.grid.currentGridVoltagePerPhase[2], 2, Units.VOLT); updateQtyState(CHANNEL_SENEC_GRID_FREQUENCY, response.grid.currentGridFrequency, 2, Units.HERTZ); - updateQtyState(CHANNEL_SENEC_LIVE_BAT_CHARGE, response.statistics.liveBatCharge, 2, Units.KILOWATT_HOUR); - updateQtyState(CHANNEL_SENEC_LIVE_BAT_DISCHARGE, response.statistics.liveBatDischarge, 2, - Units.KILOWATT_HOUR); - updateQtyState(CHANNEL_SENEC_LIVE_GRID_IMPORT, response.statistics.liveGridImport, 2, Units.KILOWATT_HOUR); - updateQtyState(CHANNEL_SENEC_LIVE_GRID_EXPORT, response.statistics.liveGridExport, 2, Units.KILOWATT_HOUR); - updateQtyState(CHANNEL_SENEC_LIVE_HOUSE_CONSUMPTION, response.statistics.liveHouseConsumption, 2, - Units.KILOWATT_HOUR); - updateQtyState(CHANNEL_SENEC_LIVE_POWER_GENERATOR, response.statistics.livePowerGenerator, 2, - Units.KILOWATT_HOUR); - if (response.statistics.liveWallboxEnergy != null) { - updateQtyState(CHANNEL_SENEC_LIVE_ENERGY_WALLBOX1, response.statistics.liveWallboxEnergy[0], 2, - Units.KILOWATT_HOUR, DIVISOR_ISO_TO_KILO); - } - if (response.battery.chargedEnergy != null) { updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK1, response.battery.chargedEnergy[0], 2, Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO); diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandlerFactory.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandlerFactory.java index 82757dbde233a..df08c8074d30f 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandlerFactory.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandlerFactory.java @@ -17,15 +17,19 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link SenecHomeHandlerFactory} is responsible for creating things and thing @@ -36,15 +40,17 @@ @NonNullByDefault @Component(configurationPid = "binding.senechome", service = ThingHandlerFactory.class) public class SenecHomeHandlerFactory extends BaseThingHandlerFactory { + private final Logger logger = LoggerFactory.getLogger(SenecHomeHandlerFactory.class); private static final Set SUPPORTED_THING_TYPES_UIDS = Set .of(SenecHomeBindingConstants.THING_TYPE_SENEC_HOME_BATTERY); - private HttpClient httpClient; + private final HttpClient httpClient; @Activate public SenecHomeHandlerFactory(@Reference HttpClientFactory httpClientFactory) { - this.httpClient = httpClientFactory.getCommonHttpClient(); + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(true); // Accept all certificates + this.httpClient = httpClientFactory.createHttpClient(SenecHomeBindingConstants.BINDING_ID, sslContextFactory); } @Override @@ -62,4 +68,26 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return null; } + + @Override + protected void activate(ComponentContext componentContext) { + super.activate(componentContext); + + try { + httpClient.start(); + } catch (Exception e) { + logger.warn("cannot start Jetty-Http-Client", e); + } + } + + @Override + protected void deactivate(ComponentContext componentContext) { + super.deactivate(componentContext); + + try { + httpClient.stop(); + } catch (Exception e) { + logger.warn("cannot stop Jetty-Http-Client", e); + } + } } diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/json/SenecHomeResponse.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/json/SenecHomeResponse.java index bad49a1b2a03f..4e95cbf85ed80 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/json/SenecHomeResponse.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/json/SenecHomeResponse.java @@ -28,14 +28,13 @@ public class SenecHomeResponse implements Serializable { public @SerializedName("PV1") SenecHomePower power = new SenecHomePower(); public @SerializedName("ENERGY") SenecHomeEnergy energy = new SenecHomeEnergy(); public @SerializedName("PM1OBJ1") SenecHomeGrid grid = new SenecHomeGrid(); - public @SerializedName("STATISTIC") SenecHomeStatistics statistics = new SenecHomeStatistics(); public @SerializedName("BMS") SenecHomeBattery battery = new SenecHomeBattery(); public @SerializedName("TEMPMEASURE") SenecHomeTemperature temperature = new SenecHomeTemperature(); public @SerializedName("WALLBOX") SenecHomeWallbox wallbox = new SenecHomeWallbox(); @Override public String toString() { - return "SenecHomeResponse [power=" + power + ", energy=" + energy + ", grid=" + grid + ", statistics=" - + statistics + "battery" + battery + "temperature" + temperature + "wallbox" + wallbox + "]"; + return "SenecHomeResponse [power=" + power + ", energy=" + energy + ", grid=" + grid + ", battery" + battery + + "temperature" + temperature + "wallbox" + wallbox + "]"; } } diff --git a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties index a27af54f3d365..bde416149b4be 100644 --- a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties +++ b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties @@ -18,6 +18,8 @@ thing-type.config.senechome.senechome.limitationTresholdValue.label = Limitation thing-type.config.senechome.senechome.limitationTresholdValue.description = Treshold in percent, defines when limitation state is enabled thing-type.config.senechome.senechome.refreshInterval.label = Refresh Interval thing-type.config.senechome.senechome.refreshInterval.description = Rate of refreshing details (in s) +thing-type.config.senechome.senechome.useHttp.label = Use HTTP +thing-type.config.senechome.senechome.useHttp.description = Use legacy http access instead of https # channel types diff --git a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml index 4e9e51b594d1f..db42197ddb32c 100644 --- a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml @@ -48,15 +48,6 @@ - - - - - - - - - @@ -101,6 +92,10 @@ + + 1 + + @@ -122,6 +117,11 @@ Duration of stable values until state is changed, defined in seconds 120 + + + Use legacy http access instead of https + false + diff --git a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/update/instructions.xml new file mode 100644 index 0000000000000..170de1342e6db --- /dev/null +++ b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/update/instructions.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + From c5945f0257c155478b1425b3f6751a85bfcf6a43 Mon Sep 17 00:00:00 2001 From: Patrick <54861416+pat-git023@users.noreply.github.com> Date: Sat, 11 Nov 2023 14:22:52 +0100 Subject: [PATCH 076/146] [boschshc] Add scenario channel (#15752) Signed-off-by: Patrick Gell --- .../org.openhab.binding.boschshc/README.md | 11 ++ .../devices/BoschSHCBindingConstants.java | 2 + .../devices/bridge/BridgeHandler.java | 24 ++- .../devices/bridge/ScenarioHandler.java | 110 +++++++++++ .../devices/bridge/dto/LongPollResult.java | 4 +- .../internal/devices/bridge/dto/Scenario.java | 64 +++++++ .../BoschServiceDataDeserializer.java | 66 +++++++ .../internal/serialization/GsonUtils.java | 2 + .../services/dto/BoschSHCServiceState.java | 2 +- .../resources/OH-INF/i18n/boschshc.properties | 4 + .../resources/OH-INF/thing/thing-types.xml | 21 +++ .../main/resources/OH-INF/update/binding.xml | 15 ++ .../devices/bridge/LongPollingTest.java | 46 ++++- .../devices/bridge/ScenarioHandlerTest.java | 172 ++++++++++++++++++ .../BoschServiceDataDeserializerTest.java | 71 ++++++++ 15 files changed, 609 insertions(+), 5 deletions(-) create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandler.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Scenario.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializer.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/update/binding.xml create mode 100644 bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandlerTest.java create mode 100644 bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializerTest.java diff --git a/bundles/org.openhab.binding.boschshc/README.md b/bundles/org.openhab.binding.boschshc/README.md index 6be38759eab06..3448c644c8f41 100644 --- a/bundles/org.openhab.binding.boschshc/README.md +++ b/bundles/org.openhab.binding.boschshc/README.md @@ -4,6 +4,7 @@ Binding for the Bosch Smart Home. - [Bosch Smart Home Binding](#bosch-smart-home-binding) - [Supported Things](#supported-things) + - [Smart Home Controller](#smart-home-controller) - [In-Wall Switch](#in-wall-switch) - [Compact Smart Plug](#compact-smart-plug) - [Twinguard Smoke Detector](#twinguard-smoke-detector) @@ -27,6 +28,16 @@ Binding for the Bosch Smart Home. ## Supported Things +### Smart Home Controller +The Smart Home Controller is the central hub that allows you to monitor and control your smart home devices from one place. + +**Bridge Type ID**: ``shc`` + +| Channel Type ID | Item Type | Writable | Description | +|--------------------|-----------|:--------:|-------------------------------------------------------------------------| +| scenario-triggered | String | ☐ | Name of the triggered scenario (e.g. by the Universal Switch Flex) | +| trigger-scenario | String | ☑ | Name of a scenario to be triggered on the Bosch Smart Home Controller. | + ### In-Wall Switch A simple light control. diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java index 8b70ce0a49987..d99006889148d 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java @@ -51,6 +51,8 @@ public class BoschSHCBindingConstants { // List of all Channel IDs // Auto-generated from thing-types.xml via script, don't modify + public static final String CHANNEL_SCENARIO_TRIGGERED = "scenario-triggered"; + public static final String CHANNEL_TRIGGER_SCENARIO = "trigger-scenario"; public static final String CHANNEL_POWER_SWITCH = "power-switch"; public static final String CHANNEL_TEMPERATURE = "temperature"; public static final String CHANNEL_TEMPERATURE_RATING = "temperature-rating"; diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java index 54e14d749d335..717ad89d375cb 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java @@ -34,11 +34,13 @@ import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants; import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device; import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData; import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult; import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario; import org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException; @@ -46,7 +48,9 @@ import org.openhab.binding.boschshc.internal.serialization.GsonUtils; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; import org.openhab.binding.boschshc.internal.services.dto.JsonRestExceptionResponse; +import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -55,6 +59,7 @@ import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; import org.slf4j.Logger; @@ -99,8 +104,11 @@ public class BridgeHandler extends BaseBridgeHandler { */ private @Nullable ThingDiscoveryService thingDiscoveryService; + private final ScenarioHandler scenarioHandler; + public BridgeHandler(Bridge bridge) { super(bridge); + scenarioHandler = new ScenarioHandler(); this.longPolling = new LongPolling(this.scheduler, this::handleLongPollResult, this::handleLongPollFailure); } @@ -195,6 +203,11 @@ public void dispose() { @Override public void handleCommand(ChannelUID channelUID, Command command) { // commands are handled by individual device handlers + BoschHttpClient localHttpClient = httpClient; + if (BoschSHCBindingConstants.CHANNEL_TRIGGER_SCENARIO.equals(channelUID.getId()) + && !RefreshType.REFRESH.equals(command) && localHttpClient != null) { + scenarioHandler.triggerScenario(localHttpClient, command.toString()); + } } /** @@ -410,8 +423,15 @@ public boolean unregisterDiscoveryListener() { * @param result Results from Long Polling */ private void handleLongPollResult(LongPollResult result) { - for (DeviceServiceData deviceServiceData : result.result) { - handleDeviceServiceData(deviceServiceData); + for (BoschSHCServiceState serviceState : result.result) { + if (serviceState instanceof DeviceServiceData deviceServiceData) { + handleDeviceServiceData(deviceServiceData); + } else if (serviceState instanceof Scenario scenario) { + final Channel channel = this.getThing().getChannel(BoschSHCBindingConstants.CHANNEL_SCENARIO_TRIGGERED); + if (channel != null && isLinked(channel.getUID())) { + updateState(channel.getUID(), new StringType(scenario.name)); + } + } } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandler.java new file mode 100644 index 0000000000000..54a080a8cf1ea --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandler.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.devices.bridge; + +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handler for executing a scenario. + * + * @author Patrick Gell - Initial contribution + * + */ +@NonNullByDefault +public class ScenarioHandler { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + protected ScenarioHandler() { + } + + public void triggerScenario(final BoschHttpClient httpClient, final String scenarioName) { + + final Scenario[] scenarios; + try { + scenarios = getAvailableScenarios(httpClient); + } catch (BoschSHCException e) { + logger.debug("unable to read the available scenarios from Bosch Smart Home Conteroller", e); + return; + } + final Optional scenario = Arrays.stream(scenarios).filter(s -> s.name.equals(scenarioName)) + .findFirst(); + if (scenario.isPresent()) { + sendPOSTRequest(httpClient.getBoschSmartHomeUrl(String.format("scenarios/%s/triggers", scenario.get().id)), + httpClient); + } else { + if (logger.isDebugEnabled()) { + logger.debug("Scenario '{}' was not found in the list of available scenarios {}", scenarioName, + prettyLogScenarios(scenarios)); + } + } + } + + private Scenario[] getAvailableScenarios(final BoschHttpClient httpClient) throws BoschSHCException { + final Request request = httpClient.createRequest(httpClient.getBoschSmartHomeUrl("scenarios"), HttpMethod.GET); + try { + return httpClient.sendRequest(request, Scenario[].class, Scenario::isValid, null); + } catch (InterruptedException e) { + logger.debug("Scenario call was interrupted", e); + Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + logger.debug("Scenario call timed out", e); + } catch (ExecutionException e) { + logger.debug("Exception occurred during scenario call", e); + } + + return new Scenario[] {}; + } + + private void sendPOSTRequest(final String url, final BoschHttpClient httpClient) { + try { + final Request request = httpClient.createRequest(url, HttpMethod.POST); + final ContentResponse response = request.send(); + if (HttpStatus.ACCEPTED_202 != response.getStatus()) { + logger.debug("{} - {} failed with {}: {}", HttpMethod.POST, url, response.getStatus(), + response.getContentAsString()); + } + } catch (InterruptedException e) { + logger.debug("Scenario call was interrupted", e); + Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + logger.debug("Scenario call timed out", e); + } catch (ExecutionException e) { + logger.debug("Exception occurred during scenario call", e); + } + } + + private String prettyLogScenarios(final Scenario[] scenarios) { + final StringBuilder builder = new StringBuilder(); + builder.append("["); + for (Scenario scenario : scenarios) { + builder.append("\n "); + builder.append(scenario); + } + builder.append("\n]"); + return builder.toString(); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResult.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResult.java index 1838c96b445f5..70b8cbade032a 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResult.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResult.java @@ -14,6 +14,8 @@ import java.util.ArrayList; +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; + /** * Response of the Controller for a Long Poll API call. * @@ -35,6 +37,6 @@ public class LongPollResult { * ],"jsonrpc":"2.0"} */ - public ArrayList result; + public ArrayList result; public String jsonrpc; } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Scenario.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Scenario.java new file mode 100644 index 0000000000000..4440d8ff8ce5e --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Scenario.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.devices.bridge.dto; + +import java.util.Arrays; + +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; + +/** + * A scenario as represented by the controller. + * + * Json example: + * { + * "@type": "scenarioTriggered", + * "name": "My scenario", + * "id": "509bd737-eed0-40b7-8caa-e8686a714399", + * "lastTimeTriggered": "1693758693032" + * } + * + * @author Patrick Gell - Initial contribution + */ +public class Scenario extends BoschSHCServiceState { + + public String name; + public String id; + public String lastTimeTriggered; + + public Scenario() { + super("scenarioTriggered"); + } + + public static Scenario createScenario(final String id, final String name, final String lastTimeTriggered) { + final Scenario scenario = new Scenario(); + + scenario.id = id; + scenario.name = name; + scenario.lastTimeTriggered = lastTimeTriggered; + return scenario; + } + + public static Boolean isValid(Scenario[] scenarios) { + return Arrays.stream(scenarios).allMatch(scenario -> (scenario.id != null)); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Scenario{"); + sb.append("name='").append(name).append("'"); + sb.append(", id='").append(id).append("'"); + sb.append(", lastTimeTriggered='").append(lastTimeTriggered).append("'"); + sb.append('}'); + return sb.toString(); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializer.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializer.java new file mode 100644 index 0000000000000..c04d0bd89a70f --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializer.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.serialization; + +import java.lang.reflect.Type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario; +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +/** + * Utility class for JSON deserialization of device data and triggered scenarios using Google Gson. + * + * @author Patrick Gell - Initial contribution + * + */ +@NonNullByDefault +public class BoschServiceDataDeserializer implements JsonDeserializer { + + @Nullable + @Override + public BoschSHCServiceState deserialize(JsonElement jsonElement, Type type, + JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + + JsonObject jsonObject = jsonElement.getAsJsonObject(); + JsonElement dataType = jsonObject.get("@type"); + switch (dataType.getAsString()) { + case "DeviceServiceData" -> { + var deviceServiceData = new DeviceServiceData(); + deviceServiceData.deviceId = jsonObject.get("deviceId").getAsString(); + deviceServiceData.state = jsonObject.get("state"); + deviceServiceData.id = jsonObject.get("id").getAsString(); + deviceServiceData.path = jsonObject.get("path").getAsString(); + return deviceServiceData; + } + case "scenarioTriggered" -> { + var scenario = new Scenario(); + scenario.id = jsonObject.get("id").getAsString(); + scenario.name = jsonObject.get("name").getAsString(); + scenario.lastTimeTriggered = jsonObject.get("lastTimeTriggered").getAsString(); + return scenario; + } + default -> { + return new BoschSHCServiceState(dataType.getAsString()); + } + } + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java index efa652a50656c..0360370a0c4ba 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java @@ -13,6 +13,7 @@ package org.openhab.binding.boschshc.internal.serialization; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -35,6 +36,7 @@ private GsonUtils() { * This instance does not serialize or deserialize fields named logger. */ public static final Gson DEFAULT_GSON_INSTANCE = new GsonBuilder() + .registerTypeAdapter(BoschSHCServiceState.class, new BoschServiceDataDeserializer()) .addSerializationExclusionStrategy(new LoggerExclusionStrategy()) .addDeserializationExclusionStrategy(new LoggerExclusionStrategy()).create(); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java index 488356b30534f..7d906c6717449 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java @@ -37,7 +37,7 @@ public class BoschSHCServiceState { @SerializedName("@type") public final String type; - protected BoschSHCServiceState(String type) { + public BoschSHCServiceState(String type) { this.type = type; if (stateType == null) { diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties index 2371ad36a11b5..f2eb06fccffc6 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties @@ -105,6 +105,8 @@ channel-type.boschshc.purity-rating.label = Purity Rating channel-type.boschshc.purity-rating.description = Rating of the air purity. channel-type.boschshc.purity.label = Purity channel-type.boschshc.purity.description = Purity of the air. A higher value indicates a higher pollution. +channel-type.boschshc.scenario-triggered.label = Scenario Triggered +channel-type.boschshc.scenario-triggered.description = Name of the triggered scenario channel-type.boschshc.setpoint-temperature.label = Setpoint Temperature channel-type.boschshc.setpoint-temperature.description = Desired temperature. channel-type.boschshc.silent-mode.label = Silent Mode @@ -126,6 +128,8 @@ channel-type.boschshc.temperature-rating.state.option.MEDIUM = Medium Temperatur channel-type.boschshc.temperature-rating.state.option.BAD = Bad Temperature channel-type.boschshc.temperature.label = Temperature channel-type.boschshc.temperature.description = Current measured temperature. +channel-type.boschshc.trigger-scenario.label = Trigger Scenario +channel-type.boschshc.trigger-scenario.description = Name of the scenario to trigger channel-type.boschshc.valve-tappet-position.label = Valve Tappet Position channel-type.boschshc.valve-tappet-position.description = Current open ratio (0 to 100). diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml index 03354abe14322..1ff71e37d1bd8 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml @@ -9,6 +9,15 @@ The Bosch Smart Home Bridge representing the Bosch Smart Home Controller. + + + + + + + 1 + + @@ -520,4 +529,16 @@
+ + String + + Name of the triggered scenario + + + + String + + Name of the scenario to trigger + + diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/update/binding.xml b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/update/binding.xml new file mode 100644 index 0000000000000..814f6b8da8b3f --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/update/binding.xml @@ -0,0 +1,15 @@ + + + + + + boschshc:scenario-triggered + + + boschshc:trigger-scenario + + + + diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java index 33035a95b08af..2b28b8d068b48 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPollingTest.java @@ -48,6 +48,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData; import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario; import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException; @@ -237,7 +238,8 @@ void start() throws InterruptedException, TimeoutException, ExecutionException, verify(longPollHandler).accept(longPollResultCaptor.capture()); LongPollResult longPollResult = longPollResultCaptor.getValue(); assertEquals(1, longPollResult.result.size()); - DeviceServiceData longPollResultItem = longPollResult.result.get(0); + assertEquals(longPollResult.result.get(0).getClass(), DeviceServiceData.class); + DeviceServiceData longPollResultItem = (DeviceServiceData) longPollResult.result.get(0); assertEquals("hdm:HomeMaticIP:3014F711A0001916D859A8A9", longPollResultItem.deviceId); assertEquals("/devices/hdm:HomeMaticIP:3014F711A0001916D859A8A9/services/PowerSwitch", longPollResultItem.path); assertEquals("PowerSwitch", longPollResultItem.id); @@ -246,6 +248,48 @@ void start() throws InterruptedException, TimeoutException, ExecutionException, assertEquals("ON", stateObject.get("switchState").getAsString()); } + @Test + void startLongPolling_receiveScenario() + throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException { + // when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod(); + when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod(); + + Request subscribeRequest = mock(Request.class); + when(httpClient.createRequest(anyString(), same(HttpMethod.POST), + argThat((JsonRpcRequest r) -> "RE/subscribe".equals(r.method)))).thenReturn(subscribeRequest); + SubscribeResult subscribeResult = new SubscribeResult(); + when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult); + + Request longPollRequest = mock(Request.class); + when(httpClient.createRequest(anyString(), same(HttpMethod.POST), + argThat((JsonRpcRequest r) -> "RE/longPoll".equals(r.method)))).thenReturn(longPollRequest); + + fixture.start(httpClient); + + ArgumentCaptor completeListener = ArgumentCaptor.forClass(CompleteListener.class); + verify(longPollRequest).send(completeListener.capture()); + + BufferingResponseListener bufferingResponseListener = (BufferingResponseListener) completeListener.getValue(); + + String longPollResultJSON = "{\"result\":[{\"@type\": \"scenarioTriggered\",\"name\": \"My scenario\",\"id\": \"509bd737-eed0-40b7-8caa-e8686a714399\",\"lastTimeTriggered\": \"1693758693032\"}],\"jsonrpc\":\"2.0\"}\n"; + Response response = mock(Response.class); + bufferingResponseListener.onContent(response, + ByteBuffer.wrap(longPollResultJSON.getBytes(StandardCharsets.UTF_8))); + + Result result = mock(Result.class); + bufferingResponseListener.onComplete(result); + + ArgumentCaptor longPollResultCaptor = ArgumentCaptor.forClass(LongPollResult.class); + verify(longPollHandler).accept(longPollResultCaptor.capture()); + LongPollResult longPollResult = longPollResultCaptor.getValue(); + assertEquals(1, longPollResult.result.size()); + assertEquals(longPollResult.result.get(0).getClass(), Scenario.class); + Scenario longPollResultItem = (Scenario) longPollResult.result.get(0); + assertEquals("509bd737-eed0-40b7-8caa-e8686a714399", longPollResultItem.id); + assertEquals("My scenario", longPollResultItem.name); + assertEquals("1693758693032", longPollResultItem.lastTimeTriggered); + } + @Test void startSubscriptionFailure() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException { diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandlerTest.java new file mode 100644 index 0000000000000..3035a6f102349 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/ScenarioHandlerTest.java @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.devices.bridge; + +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; + +/** + * Unit tests for {@link ScenarioHandler}. + * + * @author Patrick Gell - Initial contribution + * + */ +@NonNullByDefault +@ExtendWith(MockitoExtension.class) +class ScenarioHandlerTest { + + private final Scenario[] existingScenarios = List.of( + Scenario.createScenario(UUID.randomUUID().toString(), "Scenario 1", + String.valueOf(System.currentTimeMillis())), + Scenario.createScenario(UUID.randomUUID().toString(), "Scenario 2", + String.valueOf(System.currentTimeMillis())) + + ).toArray(Scenario[]::new); + + protected static Exception[] exceptionData() { + return List.of(new BoschSHCException(), new InterruptedException(), new TimeoutException(), + new ExecutionException(new BoschSHCException())).toArray(Exception[]::new); + } + + protected static Exception[] httpExceptionData() { + return List + .of(new InterruptedException(), new TimeoutException(), new ExecutionException(new BoschSHCException())) + .toArray(Exception[]::new); + } + + @Test + void triggerScenario_ShouldSendPOST_ToBoschAPI() throws Exception { + // GIVEN + final var httpClient = mock(BoschHttpClient.class); + final var request = mock(Request.class); + final var contentResponse = mock(ContentResponse.class); + when(httpClient.getBoschSmartHomeUrl(anyString())).thenReturn("http://localhost/smartHome/scenarios") + .thenReturn("http://localhost/smartHome/scenarios/1234/triggers"); + when(httpClient.createRequest(anyString(), any(HttpMethod.class))).thenReturn(request).thenReturn(request); + when(httpClient.sendRequest(any(Request.class), any(), any(), any())).thenReturn(existingScenarios); + when(request.send()).thenReturn(contentResponse); + when(contentResponse.getStatus()).thenReturn(HttpStatus.OK_200); + + final var handler = new ScenarioHandler(); + + // WHEN + handler.triggerScenario(httpClient, "Scenario 1"); + + // THEN + verify(httpClient).getBoschSmartHomeUrl("scenarios"); + verify(request).send(); + } + + @Test + void triggerScenario_ShouldNoSendPOST_ToScenarioNameDoesNotExist() throws Exception { + // GIVEN + final var httpClient = mock(BoschHttpClient.class); + final var request = mock(Request.class); + when(httpClient.getBoschSmartHomeUrl(anyString())).thenReturn("http://localhost/smartHome/scenarios") + .thenReturn("http://localhost/smartHome/scenarios/1234/triggers"); + when(httpClient.createRequest(anyString(), any(HttpMethod.class))).thenReturn(request).thenReturn(request); + when(httpClient.sendRequest(any(Request.class), any(), any(), any())).thenReturn(existingScenarios); + + final var handler = new ScenarioHandler(); + + // WHEN + handler.triggerScenario(httpClient, "not existing Scenario"); + + // THEN + verify(httpClient).getBoschSmartHomeUrl("scenarios"); + verify(request, times(0)).send(); + } + + @ParameterizedTest + @MethodSource("exceptionData") + void triggerScenario_ShouldNotPanic_IfBoschAPIThrowsException(final Exception exception) throws Exception { + // GIVEN + final var httpClient = mock(BoschHttpClient.class); + final var request = mock(Request.class); + when(httpClient.getBoschSmartHomeUrl(anyString())).thenReturn("http://localhost/smartHome/scenarios") + .thenReturn("http://localhost/smartHome/scenarios/1234/triggers"); + when(httpClient.createRequest(anyString(), any(HttpMethod.class))).thenReturn(request); + when(httpClient.sendRequest(any(Request.class), any(), any(), any())).thenThrow(exception); + + final var handler = new ScenarioHandler(); + + // WHEN + handler.triggerScenario(httpClient, "Scenario 1"); + + // THEN + verify(httpClient).getBoschSmartHomeUrl("scenarios"); + verify(request, times(0)).send(); + } + + @Test + void triggerScenario_ShouldNotPanic_IfPOSTIsNotSuccessful() throws Exception { + // GIVEN + final var httpClient = mock(BoschHttpClient.class); + final var request = mock(Request.class); + final var contentResponse = mock(ContentResponse.class); + when(httpClient.getBoschSmartHomeUrl(anyString())).thenReturn("http://localhost/smartHome/scenarios") + .thenReturn("http://localhost/smartHome/scenarios/1234/triggers"); + when(httpClient.createRequest(anyString(), any(HttpMethod.class))).thenReturn(request).thenReturn(request); + when(httpClient.sendRequest(any(Request.class), any(), any(), any())).thenReturn(existingScenarios); + when(request.send()).thenReturn(contentResponse); + when(contentResponse.getStatus()).thenReturn(HttpStatus.METHOD_NOT_ALLOWED_405); + + final var handler = new ScenarioHandler(); + + // WHEN + handler.triggerScenario(httpClient, "Scenario 1"); + + // THEN + verify(httpClient).getBoschSmartHomeUrl("scenarios"); + verify(request).send(); + } + + @ParameterizedTest + @MethodSource("httpExceptionData") + void triggerScenario_ShouldNotPanic_IfPOSTThrowsException(final Exception exception) throws Exception { + // GIVEN + final var httpClient = mock(BoschHttpClient.class); + final var request = mock(Request.class); + when(httpClient.getBoschSmartHomeUrl(anyString())).thenReturn("http://localhost/smartHome/scenarios") + .thenReturn("http://localhost/smartHome/scenarios/1234/triggers"); + when(httpClient.createRequest(anyString(), any(HttpMethod.class))).thenReturn(request).thenReturn(request); + when(httpClient.sendRequest(any(Request.class), any(), any(), any())).thenReturn(existingScenarios); + when(request.send()).thenThrow(exception); + + final var handler = new ScenarioHandler(); + + // WHEN + handler.triggerScenario(httpClient, "Scenario 1"); + + // THEN + verify(httpClient).getBoschSmartHomeUrl("scenarios"); + verify(request).send(); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializerTest.java new file mode 100644 index 0000000000000..c06753d797cc9 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializerTest.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.serialization; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashSet; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario; + +/** + * Unit tests for {@link BoschServiceDataDeserializer}. + * + * @author Patrick Gell - Initial contribution + * + */ +@NonNullByDefault +class BoschServiceDataDeserializerTest { + + @Test + void deserializationOfLongPollingResult() { + var resultJson = """ + { + "result": [ + { + "@type": "scenarioTriggered", + "name": "MyTriggeredScenario", + "id": "509bd737-eed0-40b7-8caa-e8686a714399", + "lastTimeTriggered": "1689417526720" + }, + { + "path":"/devices/hdm:HomeMaticIP:3014F711A0001916D859A8A9/services/PowerSwitch", + "@type":"DeviceServiceData", + "id":"PowerSwitch", + "state":{ + "@type":"powerSwitchState", + "switchState":"ON" + }, + "deviceId":"hdm:HomeMaticIP:3014F711A0001916D859A8A9" + } + ], + "jsonrpc": "2.0" + } + """; + + var longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(resultJson, LongPollResult.class); + assertNotNull(longPollResult); + assertEquals(2, longPollResult.result.size()); + + var resultClasses = new HashSet<>(longPollResult.result.stream().map(e -> e.getClass().getName()).toList()); + assertEquals(2, resultClasses.size()); + assertTrue(resultClasses.contains(DeviceServiceData.class.getName())); + assertTrue(resultClasses.contains(Scenario.class.getName())); + } +} From 6a19cc78f814c8eef36de622138f529df4538728 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 11 Nov 2023 23:15:21 +0100 Subject: [PATCH 077/146] [influxdb] Improve connection handling (#15879) * [influxdb] Improve connection handling Especially for InfluxDB2 the connection check was not properly implemented. It only checked if a connections was ever successfully established. Since we removed the full crash when a write error occured, this lead to a situation where a broken connection was not detected. A ping is now implemented and also a failed write results in a disconnect. --------- Signed-off-by: Jan N. Klug --- .../persistence/influxdb/InfluxDBPersistenceService.java | 1 + .../influxdb/internal/influx2/InfluxDB2RepositoryImpl.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java b/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java index 358bd778a2eb2..ee2ba5190facb 100644 --- a/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java +++ b/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/InfluxDBPersistenceService.java @@ -286,6 +286,7 @@ private void commit() { if (!influxDBRepository.write(points)) { logger.warn("Re-queuing {} elements, failed to write batch.", points.size()); pointsQueue.addAll(points); + influxDBRepository.disconnect(); } else { logger.trace("Wrote {} elements to database", points.size()); } diff --git a/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx2/InfluxDB2RepositoryImpl.java b/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx2/InfluxDB2RepositoryImpl.java index 862ecf4c2ba31..77ef198009a2b 100644 --- a/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx2/InfluxDB2RepositoryImpl.java +++ b/bundles/org.openhab.persistence.influxdb/src/main/java/org/openhab/persistence/influxdb/internal/influx2/InfluxDB2RepositoryImpl.java @@ -44,6 +44,7 @@ import com.influxdb.client.InfluxDBClientOptions; import com.influxdb.client.QueryApi; import com.influxdb.client.WriteApi; +import com.influxdb.client.domain.HealthCheck; import com.influxdb.client.domain.Ready; import com.influxdb.client.domain.WritePrecision; import com.influxdb.client.write.Point; @@ -76,7 +77,8 @@ public InfluxDB2RepositoryImpl(InfluxDBConfiguration configuration, @Override public boolean isConnected() { - return client != null; + InfluxDBClient client = this.client; + return client != null && client.health().getStatus() == HealthCheck.StatusEnum.PASS; } @Override From 30004d8d15df63c04f90ddb32bd19c491ba513d0 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Sat, 11 Nov 2023 23:56:49 +0100 Subject: [PATCH 078/146] New Crowdin updates (#15881) * New translations boschshc.properties (Italian) * New translations tasmotaplug.properties (Italian) --- .../OH-INF/i18n/boschshc_it.properties | 4 +++ .../OH-INF/i18n/tasmotaplug_it.properties | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug_it.properties diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_it.properties b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_it.properties index 8e420b8864687..f9dca7a23b34e 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_it.properties +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_it.properties @@ -105,6 +105,8 @@ channel-type.boschshc.purity-rating.label = Valutazione Della Purezza channel-type.boschshc.purity-rating.description = Valutazione della purezza dell'aria. channel-type.boschshc.purity.label = Purezza channel-type.boschshc.purity.description = Purezza dell’aria. Un valore più elevato indica un inquinamento più elevato. +channel-type.boschshc.scenario-triggered.label = Scenario Attivato +channel-type.boschshc.scenario-triggered.description = Nome dello scenario attivato channel-type.boschshc.setpoint-temperature.label = Temperatura Impostata channel-type.boschshc.setpoint-temperature.description = Temperatura desiderata. channel-type.boschshc.silent-mode.label = Modalità silenziosa @@ -126,6 +128,8 @@ channel-type.boschshc.temperature-rating.state.option.MEDIUM = Temperatura Media channel-type.boschshc.temperature-rating.state.option.BAD = Temperatura Pessima channel-type.boschshc.temperature.label = Temperatura channel-type.boschshc.temperature.description = Temperatura misurata corrente. +channel-type.boschshc.trigger-scenario.label = Scena da Attivare +channel-type.boschshc.trigger-scenario.description = Nome dello scenario da attivare channel-type.boschshc.valve-tappet-position.label = Posizione utilizzata dalla Valvola channel-type.boschshc.valve-tappet-position.description = Rapporto aperto corrente (da 0 a 100). diff --git a/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug_it.properties b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug_it.properties new file mode 100644 index 0000000000000..f9754ce43c473 --- /dev/null +++ b/bundles/org.openhab.binding.tasmotaplug/src/main/resources/OH-INF/i18n/tasmotaplug_it.properties @@ -0,0 +1,35 @@ +# add-on + +addon.tasmotaplug.name = Binding Spina Tasmota +addon.tasmotaplug.description = Controlla le Spine Intelligenti Wi-Fi flashate con Tasmota + +# thing types + +thing-type.tasmotaplug.plug.label = Spina +thing-type.tasmotaplug.plug.description = Tasmota Spina Smart +thing-type.tasmotaplug.plug.channel.power.label = Potenza +thing-type.tasmotaplug.plug.channel.power.description = Controlla il relè smart plug per il primo canale +thing-type.tasmotaplug.plug.channel.power2.label = Potenza 2 +thing-type.tasmotaplug.plug.channel.power2.description = Controlla il relè smart plug per il secondo canale +thing-type.tasmotaplug.plug.channel.power3.label = Potenza 3 +thing-type.tasmotaplug.plug.channel.power3.description = Controlla il relè smart plug per il terzo canale +thing-type.tasmotaplug.plug.channel.power4.label = Potenza 4 +thing-type.tasmotaplug.plug.channel.power4.description = Controlla il relè smart plug per il 4° canale + +# thing types config + +thing-type.config.tasmotaplug.plug.hostName.label = Nome Host/Indirizzo IP Plug +thing-type.config.tasmotaplug.plug.hostName.description = Nome host o indirizzo IP della spina +thing-type.config.tasmotaplug.plug.numChannels.label = Numero dei canali +thing-type.config.tasmotaplug.plug.numChannels.description = Numero di canali sulla spina Tasmota (1-4) predefinito 1 +thing-type.config.tasmotaplug.plug.password.label = Password +thing-type.config.tasmotaplug.plug.password.description = Password di Tasmota +thing-type.config.tasmotaplug.plug.refresh.label = Intervallo di aggiornamento +thing-type.config.tasmotaplug.plug.refresh.description = Specifica l'intervallo di aggiornamento in secondi +thing-type.config.tasmotaplug.plug.username.label = Utente +thing-type.config.tasmotaplug.plug.username.description = Utente Tasmota + +# thing status descriptions + +offline.communication-error.http-failure = Il codice di risposta di Tasmota http era\: {0} +offline.configuration-error-hostname = Il nome host della spina deve essere specificato From c93a894b1125e26012c49a8f439b13a5092d7aa8 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 12 Nov 2023 10:03:53 +0100 Subject: [PATCH 079/146] Remove Bountysource badge (#15884) It seems that Bountysource has been shut down as the website isn't reachable anymore and they don't respond to support mails. Signed-off-by: mueller-ma --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f2e089311a8d3..4a0c1a58b70bd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![Jenkins Build Status](https://ci.openhab.org/job/openHAB-Addons/badge/icon)](https://ci.openhab.org/job/openHAB-Addons/) [![EPL-2.0](https://img.shields.io/badge/license-EPL%202-green.svg)](https://opensource.org/licenses/EPL-2.0) [![Crowdin](https://badges.crowdin.net/openhab-addons/localized.svg)](https://crowdin.com/project/openhab-addons) -[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=2164344)](https://www.bountysource.com/teams/openhab/issues?tracker_ids=2164344) This repository contains the official set of add-ons that are implemented on top of openHAB Core APIs. Add-ons that got accepted in here will be maintained (e.g. adapted to new core APIs) From a29fe5cea06aa82e698c98d6f6c4988764b8a0c1 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Sun, 12 Nov 2023 20:16:43 +0100 Subject: [PATCH 080/146] New Crowdin updates (#15886) * New translations sonos.properties (French) * New translations influxdb.properties (French) * New translations netatmo.properties (French) * New translations jpa.properties (French) * New translations chatgpt.properties (French) --- .../resources/OH-INF/i18n/chatgpt_fr.properties | 10 +++++++--- .../resources/OH-INF/i18n/netatmo_fr.properties | 7 +++++++ .../resources/OH-INF/i18n/sonos_fr.properties | 7 ++++++- .../OH-INF/i18n/influxdb_fr.properties | 11 +++++++---- .../resources/OH-INF/i18n/jpa_fr.properties | 17 +++++++++++++++++ 5 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/i18n/jpa_fr.properties diff --git a/bundles/org.openhab.binding.chatgpt/src/main/resources/OH-INF/i18n/chatgpt_fr.properties b/bundles/org.openhab.binding.chatgpt/src/main/resources/OH-INF/i18n/chatgpt_fr.properties index 531320fd597c0..7391c52fcb622 100644 --- a/bundles/org.openhab.binding.chatgpt/src/main/resources/OH-INF/i18n/chatgpt_fr.properties +++ b/bundles/org.openhab.binding.chatgpt/src/main/resources/OH-INF/i18n/chatgpt_fr.properties @@ -12,6 +12,10 @@ thing-type.chatgpt.account.description = Compte chez OpenAI utilisé pour accéd thing-type.config.chatgpt.account.apiKey.label = Clé API thing-type.config.chatgpt.account.apiKey.description = Clé API pour accéder au compte +thing-type.config.chatgpt.account.apiUrl.label = URL de l'API +thing-type.config.chatgpt.account.apiUrl.description = L'API du serveur où atteindre le service IA. +thing-type.config.chatgpt.account.modelUrl.label = URL du modèle +thing-type.config.chatgpt.account.modelUrl.description = L'url du modèle d'où récupérer les modèles disponibles. # channel types @@ -29,7 +33,7 @@ channel-type.config.chatgpt.chat.systemMessage.description = Le message système channel-type.config.chatgpt.chat.temperature.label = Température channel-type.config.chatgpt.chat.temperature.description = Des valeurs plus élevées comme 0.8 rendront la sortie plus aléatoire, tandis que des valeurs plus basses comme 0.2 le rendront plus concentré et déterministe. -# Status messages +# status messages -offline.configuration-error=Aucune clé API configurée -offline.communication-error=Impossible de se connecter à l'API OpenAI +offline.configuration-error = Aucune clé API configurée +offline.communication-error = Impossible de se connecter à l'API OpenAI diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties index 39b6f2b8718aa..1feaf9cbbc0e9 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties @@ -462,6 +462,13 @@ status-bridge-offline = La passerelle n'est pas connectée à l'API Netatmo device-not-connected = L'objet n'est pas accessible data-over-limit = Les données semblent assez anciennes request-time-out = La requête a expiré - va essayer de se reconnecter plus tard +deserialization-unknown = La désérialisation renvoie un code inconnu + +homestatus-unknown-error = Erreur inconnue +homestatus-internal-error = Erreur interne +homestatus-parser-error = Erreur d'analyse +homestatus-command-unknown = Erreur de commande avec module inconnu +homestatus-invalid-params = Paramètres de commande non valides # actions diff --git a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_fr.properties b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_fr.properties index d5a2a5cb9e3b6..733787c51deaf 100644 --- a/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_fr.properties +++ b/bundles/org.openhab.binding.sonos/src/main/resources/OH-INF/i18n/sonos_fr.properties @@ -32,6 +32,8 @@ thing-type.sonos.Five.label = Five thing-type.sonos.Five.description = Représente une enceinte Sonos Five thing-type.sonos.Move.label = Move thing-type.sonos.Move.description = Représente une enceinte Sonos Move +thing-type.sonos.Move2.label = Move 2 +thing-type.sonos.Move2.description = Représente une enceinte Sonos Move 2 thing-type.sonos.One.label = One thing-type.sonos.One.description = Représente une enceinte Sonos One thing-type.sonos.OneSL.label = One SL @@ -89,12 +91,15 @@ channel-type.sonos.codec.description = Nom du codec en cours de décodage channel-type.sonos.codec.state.option.noSignal = Aucun signal channel-type.sonos.codec.state.option.silence = Silence channel-type.sonos.codec.state.option.DTS = DTS -channel-type.sonos.codec.state.option.dolbyAtmos = Dolby Atmos +channel-type.sonos.codec.state.option.Atmos = Dolby Atmos channel-type.sonos.codec.state.option.DD20 = Dolby Digital 2.0 channel-type.sonos.codec.state.option.PCM20 = PCM 2.0 channel-type.sonos.codec.state.option.DD51 = Dolby Digital 5.1 +channel-type.sonos.codec.state.option.DDPlus20 = Dolby Digital Plus 2.0 channel-type.sonos.codec.state.option.DDPlus51 = Dolby Digital Plus 5.1 +channel-type.sonos.codec.state.option.TrueHD51 = Dolby TrueHD 5.1 channel-type.sonos.codec.state.option.PCM51 = PCM 5.1 +channel-type.sonos.codec.state.option.DTS51 = DTS Surround 5.1 channel-type.sonos.coordinator.label = Coordinateur Groupe channel-type.sonos.coordinator.description = L'identifiant du Sonos qui coordonne le groupe actuel channel-type.sonos.currentalbum.label = Album diff --git a/bundles/org.openhab.persistence.influxdb/src/main/resources/OH-INF/i18n/influxdb_fr.properties b/bundles/org.openhab.persistence.influxdb/src/main/resources/OH-INF/i18n/influxdb_fr.properties index 747f9aa6c35b8..7cc337b02b6ed 100644 --- a/bundles/org.openhab.persistence.influxdb/src/main/resources/OH-INF/i18n/influxdb_fr.properties +++ b/bundles/org.openhab.persistence.influxdb/src/main/resources/OH-INF/i18n/influxdb_fr.properties @@ -1,3 +1,10 @@ +# add-on + +addon.influxdb.name = Persistance InfluxDB +addon.influxdb.description = Il s'agit du service de persistance pour InfluxDB. + +# add-on + persistence.config.influxdb.addCategoryTag.label = Ajouter balise catégory persistence.config.influxdb.addCategoryTag.description = La catégorie de l'élément doit-elle être incluse en tant que balise "category" ? Si aucune catégorie n'est définie, "n/a" sera utilisé. persistence.config.influxdb.addLabelTag.label = Ajouter balise label @@ -28,7 +35,3 @@ persistence.config.influxdb.version.label = Version de la base de données persistence.config.influxdb.version.description = Version de InfluxDB persistence.config.influxdb.version.option.V1 = InfluxDB 1 persistence.config.influxdb.version.option.V2 = InfluxDB 2 - -# service - -service.persistence.influxdb.label = Service d'historisation InfluxDB diff --git a/bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/i18n/jpa_fr.properties b/bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/i18n/jpa_fr.properties new file mode 100644 index 0000000000000..531c3c7a61746 --- /dev/null +++ b/bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/i18n/jpa_fr.properties @@ -0,0 +1,17 @@ +# add-on + +addon.jpa.name = Persistance JPA +addon.jpa.description = Il s'agit du service de persistance pour JPA. + +# add-on config + +persistence.config.jpa.driver.label = Pilote de base de données +persistence.config.jpa.driver.description = Le nom de la classe du pilote JDBC pour la connexion.
Exemples\:
com.mysql.jdbc.Driver
org.apache.derby.jdbc.ClientDriver
org.mariadb.jdbc.Driver
org.postgresql.Driver +persistence.config.jpa.password.label = Mot de passe de la base de données +persistence.config.jpa.password.description = Le mot de passe de l'utilisateur de la base de données pour la connexion. +persistence.config.jpa.syncmappings.label = Synchroniser les mappings +persistence.config.jpa.syncmappings.description = La configuration des mappings de synchronisation OpenJPA. +persistence.config.jpa.url.label = URL de la base de données +persistence.config.jpa.url.description = URL de connexion JDBC.
Exemples\:
jdbc\:derby\://hab.local\:1527/openhab;create\=true
jdbc\:mariadb\://localhost\:3306/openhab
jdbc\:mysql\://localhost\:3306/openhab
jdbc\:postgresql\://hab.local\:5432/openhab +persistence.config.jpa.user.label = Utilisateur de la base de données +persistence.config.jpa.user.description = Le nom d'utilisateur de la base de données pour la connexion. From c0cc76f95abfb775242e0209390033206655ec68 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Mon, 13 Nov 2023 19:37:04 +0100 Subject: [PATCH 081/146] [shelly] Fix resource leak, BLU script installation, TRV init, NPE on IPv6 mDNS discovery (#15798) * Fix resource leak when discovery handling failed and NPE when a IPv6 address is reported. * remove callApi(request/innerRequest from Shelly1HttpApi, which changes to callApi/httpRequest in ShellyHttpCLient. Signed-off-by: Markus Michels --- .../internal/ShellyBindingConstants.java | 2 +- .../internal/api/ShellyDeviceProfile.java | 11 ++- .../shelly/internal/api/ShellyHttpClient.java | 7 +- .../internal/api1/Shelly1CoIoTVersion2.java | 3 + .../internal/api1/Shelly1CoapHandler.java | 2 +- .../shelly/internal/api1/Shelly1HttpApi.java | 97 +------------------ .../shelly/internal/api2/Shelly2ApiRpc.java | 14 +-- .../discovery/ShellyDiscoveryParticipant.java | 18 ++-- .../internal/handler/ShellyBaseHandler.java | 26 +++-- .../internal/handler/ShellyComponents.java | 5 +- .../provider/ShellyChannelDefinitions.java | 4 +- .../main/resources/OH-INF/config/config2.xml | 4 - .../resources/OH-INF/config/configblu.xml | 2 +- .../resources/OH-INF/i18n/shelly.properties | 6 +- .../main/resources/sniplets/ov_device.html | 2 +- 15 files changed, 71 insertions(+), 132 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index e3300b7766b55..3f3f23524c8df 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -333,7 +333,7 @@ public class ShellyBindingConstants { public static final int UPDATE_MIN_DELAY = 15;// update every x triggers or when a key was pressed public static final int UPDATE_SETTINGS_INTERVAL_SECONDS = 60; // check for updates every x sec public static final int HEALTH_CHECK_INTERVAL_SEC = 300; // Health check interval, 5min - public static final int VIBRATION_FILTER_SEC = 5; // Absore duplicate vibration events for xx sec + public static final int VIBRATION_FILTER_SEC = 5; // Absorb duplicate vibration events for xx sec public static final String BUNDLE_RESOURCE_SNIPLETS = "sniplets"; // where to find code sniplets in the bundle public static final String BUNDLE_RESOURCE_SCRIPTS = "scripts"; // where to find scrips in the bundle diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 7e8f335b2f4b0..814917e26bad7 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -196,7 +196,8 @@ public void initFromThingType(String name) { return; } - isBlu = thingType.startsWith("shellyblu"); // e.g. SBBT for BU Button + isGen2 = isGeneration2(thingType); + isBlu = isBluSeries(thingType); // e.g. SBBT for BLU Button isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2) || deviceType.equalsIgnoreCase(SHELLYDT_PLUSDIMMERUS) @@ -397,6 +398,14 @@ public static String extractFwVersion(@Nullable String version) { return ""; } + public static boolean isGeneration2(String thingType) { + return thingType.startsWith("shellyplus") || thingType.startsWith("shellypro") || isBluSeries(thingType); + } + + public static boolean isBluSeries(String thingType) { + return thingType.startsWith("shellyblu"); + } + public boolean coiotEnabled() { if ((settings.coiot != null) && (settings.coiot.enabled != null)) { return settings.coiot.enabled; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java index e82dc8bee2358..5b04bc58a604b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java @@ -113,6 +113,8 @@ protected String httpRequest(String uri) throws ShellyApiException { while (retries > 0) { try { apiResult = innerRequest(HttpMethod.GET, uri, null, ""); + + // If call doesn't throw an exception the device is reachable == no timeout if (timeout) { logger.debug("{}: API timeout #{}/{} recovered ({})", thingName, timeoutErrors, timeoutsRecovered, apiResult.getUrl()); @@ -128,9 +130,10 @@ protected String httpRequest(String uri) throws ShellyApiException { } timeout = true; - retries--; timeoutErrors++; // count the retries logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); + + retries--; } } throw new ShellyApiException("API Timeout or inconsistent result"); // successful @@ -174,7 +177,7 @@ private ShellyApiResult innerRequest(HttpMethod method, String uri, @Nullable Sh } } fillPostData(request, data); - logger.trace("{}: HTTP {} for {} {}\n{}", thingName, method, url, data, request.getHeaders()); + logger.trace("{}: HTTP {} {}\n{}\n{}", thingName, method, url, request.getHeaders(), data); // Do request and get response ContentResponse contentResponse = request.send(); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion2.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion2.java index 24f2b7147b2c0..5a599811f6416 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion2.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion2.java @@ -126,6 +126,9 @@ public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen thingHandler.requestUpdates(1, false); } break; + case "3122": // boost mode, Type=S, Range=0/1 + updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_BCONTROL, getOnOff(value > 0)); + break; default: processed = false; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java index b3b88d71f1bff..23c9cdede0164 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java @@ -182,7 +182,7 @@ public void processResponse(@Nullable Response response) { for (Option opt : options) { if (opt.getNumber() == COIOT_OPTION_GLOBAL_DEVID) { String devid = opt.getStringValue(); - if (devid.contains("#")) { + if (devid.contains("#") && profile.mac != null) { // Format: ## String macid = substringBetween(devid, "#", "#"); if (profile.mac.toUpperCase().contains(macid.toUpperCase())) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java index 97d83ad7eea73..69a4d7bd50110 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java @@ -20,20 +20,11 @@ import java.util.Base64; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpStatus; import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyApiInterface; -import org.openhab.binding.shelly.internal.api.ShellyApiResult; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api.ShellyHttpClient; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult; @@ -258,7 +249,7 @@ public void setSleepTime(int value) throws ShellyApiException { @Override public void setValveTemperature(int valveId, int value) throws ShellyApiException { - request("/thermostat/" + valveId + "?target_t_enabled=1&target_t=" + value); + httpRequest("/thermostat/" + valveId + "?target_t_enabled=1&target_t=" + value); } @Override @@ -273,17 +264,17 @@ public void setValveMode(int valveId, boolean auto) throws ShellyApiException { @Override public void setValveProfile(int valveId, int value) throws ShellyApiException { String uri = "/settings/thermostat/" + valveId + "?"; - request(uri + (value == 0 ? "schedule=0" : "schedule=1&schedule_profile=" + value)); + httpRequest(uri + (value == 0 ? "schedule=0" : "schedule=1&schedule_profile=" + value)); } @Override public void setValvePosition(int valveId, double value) throws ShellyApiException { - request("/thermostat/" + valveId + "?pos=" + value); // percentage to open the valve + httpRequest("/thermostat/" + valveId + "?pos=" + value); // percentage to open the valve } @Override public void setValveBoostTime(int valveId, int value) throws ShellyApiException { - request("/settings/thermostat/" + valveId + "?boost_minutes=" + value); + httpRequest("/settings/thermostat/" + valveId + "?boost_minutes=" + value); } @Override @@ -641,86 +632,6 @@ private static String mkEventUrl(String eventType) { return eventType + SHELLY_EVENTURL_SUFFIX; } - /** - * Submit GET request and return response, check for invalid responses - * - * @param uri: URI (e.g. "/settings") - */ - @Override - public T callApi(String uri, Class classOfT) throws ShellyApiException { - String json = request(uri); - return fromJson(gson, json, classOfT); - } - - private String request(String uri) throws ShellyApiException { - ShellyApiResult apiResult = new ShellyApiResult(); - int retries = 3; - boolean timeout = false; - while (retries > 0) { - try { - apiResult = innerRequest(HttpMethod.GET, uri); - if (timeout) { - logger.debug("{}: API timeout #{}/{} recovered ({})", thingName, timeoutErrors, timeoutsRecovered, - apiResult.getUrl()); - timeoutsRecovered++; - } - return apiResult.response; // successful - } catch (ShellyApiException e) { - if ((!e.isTimeout() && !apiResult.isHttpServerError()) || profile.hasBattery || (retries == 0)) { - // Sensor in sleep mode or API exception for non-battery device or retry counter expired - throw e; // non-timeout exception - } - - timeout = true; - retries--; - timeoutErrors++; // count the retries - logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); - } - } - throw new ShellyApiException("API Timeout or inconsistent result"); // successful - } - - private ShellyApiResult innerRequest(HttpMethod method, String uri) throws ShellyApiException { - Request request = null; - String url = "http://" + config.deviceIp + uri; - ShellyApiResult apiResult = new ShellyApiResult(method.toString(), url); - - try { - request = httpClient.newRequest(url).method(method.toString()).timeout(SHELLY_API_TIMEOUT_MS, - TimeUnit.MILLISECONDS); - - if (!config.userId.isEmpty()) { - String value = config.userId + ":" + config.password; - request.header(HTTP_HEADER_AUTH, - HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(value.getBytes())); - } - request.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON); - logger.trace("{}: HTTP {} for {}", thingName, method, url); - - // Do request and get response - ContentResponse contentResponse = request.send(); - apiResult = new ShellyApiResult(contentResponse); - String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim(); - logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response); - - // validate response, API errors are reported as Json - if (contentResponse.getStatus() != HttpStatus.OK_200) { - throw new ShellyApiException(apiResult); - } - if (response.isEmpty() || !response.startsWith("{") && !response.startsWith("[") && !url.contains("/debug/") - && !url.contains("/sta_cache_reset")) { - throw new ShellyApiException("Unexpected response: " + response); - } - } catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) { - ShellyApiException ex = new ShellyApiException(apiResult, e); - if (!ex.isTimeout()) { // will be handled by the caller - logger.trace("{}: API call returned exception", thingName, ex); - } - throw ex; - } - return apiResult; - } - @Override public String getControlUriPrefix(Integer id) { String uri = ""; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java index c1a9b67f84878..d35273bcccccb 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java @@ -319,14 +319,16 @@ public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiEx asyncApiRequest(SHELLYRPC_METHOD_GETSTATUS); // request periodic status updates from device try { - if (config.enableBluGateway != null) { + if (profile.alwaysOn && config.enableBluGateway != null) { logger.debug("{}: BLU Gateway support is {} for this device", thingName, config.enableBluGateway ? "enabled" : "disabled"); - boolean bluetooth = getBool(profile.settings.bluetooth); - if (config.enableBluGateway && !bluetooth) { - logger.info("{}: Bluetooth needs to be enabled to activate BLU Gateway mode", thingName); + if (config.enableBluGateway) { + boolean bluetooth = getBool(profile.settings.bluetooth); + if (config.enableBluGateway && !bluetooth) { + logger.info("{}: Bluetooth needs to be enabled to activate BLU Gateway mode", thingName); + } + installScript(SHELLY2_BLU_GWSCRIPT, config.enableBluGateway && bluetooth); } - installScript(SHELLY2_BLU_GWSCRIPT, config.enableBluGateway && bluetooth); } } catch (ShellyApiException e) { logger.debug("{}: Device config failed", thingName, e); @@ -1023,7 +1025,7 @@ public ShellySettingsUpdate firmwareUpdate(String fwurl) throws ShellyApiExcepti Shelly2RpcRequestParams params = new Shelly2RpcRequestParams(); if (prod || beta) { - params.stage = prod || beta ? "stable" : "beta"; + params.stage = prod ? "stable" : "beta"; } else { params.url = fwurl; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java index 863c2b99a8e40..967a658641949 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java @@ -13,10 +13,11 @@ package org.openhab.binding.shelly.internal.discovery; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; -import static org.openhab.binding.shelly.internal.util.ShellyUtils.substringBeforeLast; +import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; import java.io.IOException; +import java.net.Inet4Address; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -117,9 +118,9 @@ public DiscoveryResult createResult(final ServiceInfo service) { Map properties = new TreeMap<>(); name = service.getName().toLowerCase(); - String[] hostAddresses = service.getHostAddresses(); + Inet4Address[] hostAddresses = service.getInet4Addresses(); if ((hostAddresses != null) && (hostAddresses.length > 0)) { - address = hostAddresses[0]; + address = substringAfter(hostAddresses[0].toString(), "/"); } if (address.isEmpty()) { logger.trace("{}: Shelly device discovered with empty IP address (service-name={})", name, service); @@ -142,12 +143,11 @@ public DiscoveryResult createResult(final ServiceInfo service) { config.password = bindingConfig.defaultPassword; boolean gen2 = "2".equals(service.getPropertyString("gen")); + ShellyApiInterface api = null; try { - ShellyApiInterface api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) - : new Shelly1HttpApi(name, config, httpClient); + api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) : new Shelly1HttpApi(name, config, httpClient); api.initialize(); profile = api.getDeviceProfile(thingType); - api.close(); logger.debug("{}: Shelly settings : {}", name, profile.settingsJson); deviceName = profile.name; model = profile.deviceType; @@ -170,6 +170,10 @@ public DiscoveryResult createResult(final ServiceInfo service) { } } catch (IllegalArgumentException e) { // maybe some format description was buggy logger.debug("{}: Discovery failed!", name, e); + } finally { + if (api != null) { + api.close(); + } } if (thingUID != null) { @@ -185,7 +189,7 @@ public DiscoveryResult createResult(final ServiceInfo service) { String thingLabel = deviceName.isEmpty() ? name + " - " + address : deviceName + " (" + name + "@" + address + ")"; return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(thingLabel) - .withRepresentationProperty(PROPERTY_DEV_NAME).build(); + .withRepresentationProperty(PROPERTY_SERVICE_NAME).build(); } } catch (IOException | NullPointerException e) { // maybe some format description was buggy diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index 54cd538c1a64c..c76a181e45876 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -150,11 +150,8 @@ public ShellyBaseHandler(final Thing thing, final ShellyTranslationProvider tran Map properties = thing.getProperties(); String gen = getString(properties.get(PROPERTY_DEV_GEN)); String thingType = getThingType(); - if (gen.isEmpty() && thingType.startsWith("shellyplus") || thingType.startsWith("shellypro")) { - gen = "2"; - } - gen2 = "2".equals(gen); - blu = thingType.startsWith("shellyblu"); + gen2 = "2".equals(gen) || ShellyDeviceProfile.isGeneration2(thingType); + blu = ShellyDeviceProfile.isBluSeries(thingType); this.api = !blu ? !gen2 ? new Shelly1HttpApi(thingName, this) : new Shelly2ApiRpc(thingName, thingTable, this) : new ShellyBluApi(thingName, thingTable, this); if (gen2) { @@ -569,7 +566,9 @@ protected void refreshStatus() { status = "offline.conf-error-access-denied"; } else if (isWatchdogStarted()) { if (!isWatchdogExpired()) { - logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); + if (profile.alwaysOn) { // suppress for battery powered sensors + logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); + } } else { if (isThingOnline()) { status = "offline.status-error-watchdog"; @@ -799,7 +798,7 @@ public void incProtErrors() { private boolean checkRestarted(ShellySettingsStatus status) { if (profile.isInitialized() && profile.alwaysOn /* exclude battery powered devices */ && (status.uptime != null && status.uptime < stats.lastUptime - || (!profile.status.update.oldVersion.isEmpty() + || (profile.status.update != null && !getString(profile.status.update.oldVersion).isEmpty() && !status.update.oldVersion.equals(profile.status.update.oldVersion)))) { logger.debug("{}: Device has been restarted, uptime={}/{}, firmware={}/{}", thingName, stats.lastUptime, getLong(status.uptime), profile.status.update.oldVersion, status.update.oldVersion); @@ -1022,10 +1021,14 @@ protected void initializeThingConfig() { config.serviceName = getString(properties.get(PROPERTY_SERVICE_NAME)); config.localIp = bindingConfig.localIP; config.localPort = String.valueOf(bindingConfig.httpPort); - if (config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) { + if (!profile.isGen2 && config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) { + // Gen2 has hard coded user "admin" config.userId = bindingConfig.defaultUserId; + logger.debug("{}: Using default userId {} from binding config", thingName, config.userId); + } + if (config.password.isEmpty() && !bindingConfig.defaultPassword.isEmpty()) { config.password = bindingConfig.defaultPassword; - logger.debug("{}: Using userId {} from bindingConfig", thingName, config.userId); + logger.debug("{}: Using default password from bindingConfig (userId={})", thingName, config.userId); } if (config.updateInterval == 0) { config.updateInterval = UPDATE_STATUS_INTERVAL_SECONDS * UPDATE_SKIP_COUNT; @@ -1052,6 +1055,11 @@ protected void initializeThingConfig() { private void checkVersion(ShellyDeviceProfile prf, ShellySettingsStatus status) { try { + if (prf.fwVersion.isEmpty()) { + // no fw version available (e.g. BLU device) + return; + } + ShellyVersionDTO version = new ShellyVersionDTO(); if (version.checkBeta(getString(prf.fwVersion))) { logger.info("{}: {}", prf.hostname, messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java index 28569c9c073c6..cbac83ffc2aa1 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java @@ -435,7 +435,10 @@ public static boolean updateSensors(ShellyThingInterface thingHandler, ShellySet if (t.tmp != null) { updated |= updateTempChannel(thingHandler, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, t.tmp.value, t.tmp.units); - updated |= updateTempChannel(thingHandler, CHANNEL_GROUP_SENSOR, CHANNEL_CONTROL_SETTEMP, + if (t.targetTemp.unit == null) { + t.targetTemp.unit = t.tmp.units; + } + updated |= updateTempChannel(thingHandler, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SETTEMP, t.targetTemp.value, t.targetTemp.unit); } if (t.pos != null) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java index a83c3b5b7b839..4bb96355f834c 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java @@ -519,7 +519,7 @@ public static Map createSensorChannels(final Thing thing, final CHANNEL_SENSOR_VIBRATION); } // Create tilt for DW/DW2, for BLU DW create channel even tilt is currently not reported - if (sdata.accel != null || (profile.isBlu && sdata.lux != null)) { + if (sdata.accel != null || (profile.isBlu && profile.isDW && sdata.lux != null)) { addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT); } @@ -670,7 +670,7 @@ public ShellyChannel(ShellyTranslationProvider messages, String group, String ch } description = getText(PREFIX_CHANNEL + typeId + ".description"); if (description.contains(PREFIX_CHANNEL)) { - description = ""; + description = ""; // no resource found } } diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config2.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config2.xml index bc212f828b2bb..ceccff2a0acae 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config2.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config2.xml @@ -96,10 +96,6 @@ @text/thing-type.config.shelly.deviceIp.description network-address - - - @text/thing-type.config.shelly.userId.description - @text/thing-type.config.shelly.password.description diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/configblu.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/configblu.xml index fa888ad7b9f9f..7886954bd6c49 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/configblu.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/configblu.xml @@ -7,7 +7,7 @@ - @text/thing-type.config.shelly.@text/thing-type.config.shelly.deviceAddress.label.description + @text/thing-type.config.shelly.deviceAddress.description diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties index e42353f1c548d..7af710eef9136 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties @@ -42,7 +42,7 @@ message.event.triggered = Event triggered: {0} message.event.filtered = Event filtered: {0} message.coap.init.failed = Unable to start CoIoT: {0} message.discovery.disabled = Device is marked as non-discoverable, will be skipped -message.discovery.protected = Device {0} reported 'Access defined' (missing userid/password or incorrect). +message.discovery.protected = Device {0} is protected and reports 'Access denied', check userId/password message.discovery.failed = Device discovery of device with address {0} failed: {1} message.roller.calibrating = Device is not calibrated, use Shelly App to perform initial roller calibration. message.roller.favmissing = Roller position favorites are not supported by installed firmware or not configured in the Shelly App @@ -101,7 +101,7 @@ thing-type.shelly.shellyplussmoke.description = Shelly Plus Smoke - Smoke Detect thing-type.shelly.shellypluswdus.description = Shelly Wall Dimmer US Device # Wall displays -thing-type.shelly.shellywalldisplay.description = Shelly Plus Wall Display with sensors and input/output +thing-type.shelly.shellywalldisplay.description = Shelly Wall Display with sensors and input/output # Plus Mini Devices thing-type.shelly.shellyplusmini1.description = Shelly Plus Mini 1 - Single Relay Switch @@ -120,7 +120,7 @@ thing-type.shelly.shellyproem50.description = Shelly Pro EM-50 - 2xPower Meter + thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch with Power Meter # BLU devices -thing-type.shelly.shellypblubutton.description = Shelly BLU Button +thing-type.shelly.shellyblubutton.description = Shelly BLU Button thing-type.shelly.shellybludw.description = Shelly BLU Door/Window Sensor # Wall Displays diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/sniplets/ov_device.html b/bundles/org.openhab.binding.shelly/src/main/resources/sniplets/ov_device.html index 3465237ea59c2..c395960220ce8 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/sniplets/ov_device.html +++ b/bundles/org.openhab.binding.shelly/src/main/resources/sniplets/ov_device.html @@ -32,7 +32,7 @@ Device Mode${deviceMode} Firmware Version${firmwareVersion} Network Name${serviceName} - MAC Address${macAddress} + Device Address${macAddress} Discoverable${discoverable} WiFi Auto Recovery${wifiAutoRecovery} WiFi AP Roaming${apRoamingMode} From a3bf1a5168df2d31344c0c7510d1bcd3658a3d64 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Mon, 13 Nov 2023 19:39:39 +0100 Subject: [PATCH 082/146] [Bluetooth] re-fix ArrayStoreException (#15891) Signed-off-by: Leo Siepel --- .../java/org/openhab/binding/bluetooth/BluetoothUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java index 33c9e8b32783e..456c35e7369b1 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothUtils.java @@ -47,7 +47,10 @@ public class BluetoothUtils { */ public static int[] toIntArray(byte[] value) { int[] ret = new int[value.length]; - System.arraycopy(value, 0, ret, 0, value.length); + // System.arraycopy cannot be used as it throws ArrayStoreException + for (int i = 0; i < value.length; i++) { + ret[i] = value[i]; + } return ret; } From 7aeb25fecf7f3972652f25c4ae127e4cb527e95d Mon Sep 17 00:00:00 2001 From: mlobstein Date: Tue, 14 Nov 2023 08:44:47 -0600 Subject: [PATCH 083/146] Suppress warning messages (#15129) Signed-off-by: Michael Lobstein --- .../binding/bondhome/internal/api/BPUPListener.java | 8 ++++---- .../binding/bondhome/internal/api/BondHttpApi.java | 2 +- .../bondhome/internal/discovery/BondDiscoveryService.java | 2 +- .../bondhome/internal/handler/BondBridgeHandler.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BPUPListener.java b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BPUPListener.java index 6420772ee7043..fc7b126d1297b 100644 --- a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BPUPListener.java +++ b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BPUPListener.java @@ -132,7 +132,7 @@ private void sendBPUPKeepAlive() { @Nullable String bondId = response.bondId; if (bondId == null || !bondId.equalsIgnoreCase(bridgeHandler.getBridgeId())) { - logger.warn("Response isn't from expected Bridge! Expected: {} Got: {}", + logger.trace("Response isn't from expected Bridge! Expected: {} Got: {}", bridgeHandler.getBridgeId(), bondId); } else { bridgeHandler.setBridgeOnline(inPacket.getAddress().getHostAddress()); @@ -200,7 +200,7 @@ private void processPacket(DatagramPacket packet) { BPUPUpdate update = transformUpdatePacket(packet); if (update != null) { if (!update.bondId.equalsIgnoreCase(bridgeHandler.getBridgeId())) { - logger.warn("Response isn't from expected Bridge! Expected: {} Got: {}", bridgeHandler.getBridgeId(), + logger.trace("Response isn't from expected Bridge! Expected: {} Got: {}", bridgeHandler.getBridgeId(), update.bondId); } @@ -235,7 +235,7 @@ private void processPacket(DatagramPacket packet) { try { response = this.gsonBuilder.fromJson(responseJson, BPUPUpdate.class); } catch (JsonParseException e) { - logger.warn("Error parsing json! {}", e.getMessage()); + logger.debug("Error parsing json! {}", e.getMessage()); } return response; } @@ -276,7 +276,7 @@ private void datagramSocketHealthRoutine() { this.socket = s; logger.trace("Datagram Socket reconnected using port {}.", s.getPort()); } catch (SocketException exception) { - logger.warn("Problem creating new socket : {}", exception.getLocalizedMessage()); + logger.trace("Problem creating new socket : {}", exception.getLocalizedMessage()); } } } diff --git a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BondHttpApi.java b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BondHttpApi.java index 8dedf69df09e9..46423df31802e 100644 --- a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BondHttpApi.java +++ b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/api/BondHttpApi.java @@ -194,7 +194,7 @@ public synchronized void executeDeviceAction(String deviceId, BondDeviceAction a try { response = request.send(); } catch (Exception e) { - logger.warn("Unable to execute device action {} against device {}: {}", deviceId, action, e.getMessage()); + logger.debug("Unable to execute device action {} against device {}: {}", deviceId, action, e.getMessage()); return; } diff --git a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/discovery/BondDiscoveryService.java b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/discovery/BondDiscoveryService.java index c32697ef3b9a7..64551f0cbf854 100644 --- a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/discovery/BondDiscoveryService.java +++ b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/discovery/BondDiscoveryService.java @@ -106,7 +106,7 @@ protected synchronized void startScan() { } } } catch (BondException ignored) { - logger.warn("Error getting devices for discovery: {}", ignored.getMessage()); + logger.debug("Error getting devices for discovery: {}", ignored.getMessage()); } finally { removeOlderResults(getTimestampOfLastScan()); } diff --git a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/handler/BondBridgeHandler.java b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/handler/BondBridgeHandler.java index 35cd1806c2c0f..104fa9540a237 100644 --- a/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/handler/BondBridgeHandler.java +++ b/bundles/org.openhab.binding.bondhome/src/main/java/org/openhab/binding/bondhome/internal/handler/BondBridgeHandler.java @@ -230,7 +230,7 @@ public void forwardUpdateToThing(BPUPUpdate pushUpdate) { logger.trace("could not read topic type from push update or type was not state."); } } else { - logger.warn("Can not read device Id from push update."); + logger.trace("Cannot read device Id from push update."); } } @@ -283,7 +283,7 @@ public void setBridgeOffline(ThingStatusDetail detail, String description) { */ public void setBridgeOnline(String bridgeAddress) { if (!config.isValid()) { - logger.warn("Configuration error, cannot set the bridghe online without configuration"); + logger.warn("Configuration error, cannot set the bridge online without configuration"); return; } else if (!config.ipAddress.equals(bridgeAddress)) { logger.debug("IP address of Bond {} has changed to {}", config.serialNumber, bridgeAddress); From 45b751a6d810575f9773ab3f2bd0d5f71cc90214 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Wed, 15 Nov 2023 09:12:24 +0100 Subject: [PATCH 084/146] [twitter] rename binding to X (#15809) Signed-off-by: Leo Siepel --- CODEOWNERS | 2 +- bom/openhab-addons/pom.xml | 10 +- bundles/org.openhab.binding.twitter/README.md | 63 -------- .../resources/OH-INF/i18n/twitter.properties | 32 ---- .../OH-INF/i18n/twitter_fr.properties | 32 ---- .../OH-INF/i18n/twitter_ru.properties | 9 -- .../NOTICE | 0 bundles/org.openhab.binding.x/README.md | 60 ++++++++ .../pom.xml | 8 +- .../src/main/feature/feature.xml | 6 +- .../x/internal/XBindingConstants.java} | 10 +- .../openhab/binding/x/internal/XHandler.java} | 139 +++++++++--------- .../binding/x/internal/XHandlerFactory.java} | 12 +- .../binding/x/internal/action/XActions.java} | 58 ++++---- .../binding/x/internal/config/XConfig.java} | 6 +- .../src/main/resources/OH-INF/addon/addon.xml | 6 +- .../main/resources/OH-INF/i18n/x.properties | 32 ++++ .../resources/OH-INF/thing/thing-types.xml | 15 +- bundles/pom.xml | 2 +- 19 files changed, 228 insertions(+), 274 deletions(-) delete mode 100644 bundles/org.openhab.binding.twitter/README.md delete mode 100644 bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter.properties delete mode 100644 bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_fr.properties delete mode 100644 bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_ru.properties rename bundles/{org.openhab.binding.twitter => org.openhab.binding.x}/NOTICE (100%) create mode 100644 bundles/org.openhab.binding.x/README.md rename bundles/{org.openhab.binding.twitter => org.openhab.binding.x}/pom.xml (67%) rename bundles/{org.openhab.binding.twitter => org.openhab.binding.x}/src/main/feature/feature.xml (53%) rename bundles/{org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterBindingConstants.java => org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XBindingConstants.java} (72%) rename bundles/{org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandler.java => org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandler.java} (64%) rename bundles/{org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandlerFactory.java => org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandlerFactory.java} (76%) rename bundles/{org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/action/TwitterActions.java => org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/action/XActions.java} (60%) rename bundles/{org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/config/TwitterConfig.java => org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/config/XConfig.java} (80%) rename bundles/{org.openhab.binding.twitter => org.openhab.binding.x}/src/main/resources/OH-INF/addon/addon.xml (54%) create mode 100644 bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x.properties rename bundles/{org.openhab.binding.twitter => org.openhab.binding.x}/src/main/resources/OH-INF/thing/thing-types.xml (78%) diff --git a/CODEOWNERS b/CODEOWNERS index 35fae36c6246d..04e86052e8046 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -356,7 +356,6 @@ /bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand /bundles/org.openhab.binding.tr064/ @J-N-K /bundles/org.openhab.binding.tradfri/ @cweitkamp @kaikreuzer -/bundles/org.openhab.binding.twitter/ @computergeek1507 /bundles/org.openhab.binding.unifi/ @mgbowman @Hilbrand /bundles/org.openhab.binding.unifiedremote/ @GiviMAD /bundles/org.openhab.binding.upb/ @marcusb @@ -387,6 +386,7 @@ /bundles/org.openhab.binding.wled/ @Skinah /bundles/org.openhab.binding.wolfsmartset/ @BoBiene /bundles/org.openhab.binding.wundergroundupdatereceiver/ @danieldemus +/bundles/org.openhab.binding.x/ @computergeek1507 /bundles/org.openhab.binding.xmltv/ @clinique /bundles/org.openhab.binding.xmppclient/ @pavel-gololobov /bundles/org.openhab.binding.yamahamusiccast/ @coop-git diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 62140dbb86166..879b8c19fe96c 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1766,11 +1766,6 @@ org.openhab.binding.tradfri ${project.version} - - org.openhab.addons.bundles - org.openhab.binding.twitter - ${project.version} - org.openhab.addons.bundles org.openhab.binding.unifi @@ -1926,6 +1921,11 @@ org.openhab.binding.wundergroundupdatereceiver ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.x + ${project.version} + org.openhab.addons.bundles org.openhab.binding.xmltv diff --git a/bundles/org.openhab.binding.twitter/README.md b/bundles/org.openhab.binding.twitter/README.md deleted file mode 100644 index b0ed2e6abae62..0000000000000 --- a/bundles/org.openhab.binding.twitter/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Twitter Binding - -The Twitter binding allows your home to Tweet 280 characters at a time. It also supports direct messages and tweeting with media. - -## Supported Things - -```text -account - Twitter Account. -``` - -## Thing Configuration - -The Twitter Account Thing requires you to create a Twitter App in the Twitter Developer Page. - -| Property | Default | Required | Description | -|-------------------|---------|:--------:|-----------------------------------| -| consumerKey | | Yes | Consumer API Key | -| consumerSecret | | Yes | Consumer API Secret | -| accessToken | | Yes | Access Token | -| accessTokenSecret | | Yes | Access Token Secret | -| refresh | 30 | No | Tweet refresh interval in minutes | - -## Channels - -| channel | type | description | -|------------|--------|------------------------------------------------| -| lasttweet | String | This channel provides the Latest Tweet message | - -## Full Example - -twitter.things: - -```java -Thing twitter:account:sampleaccount [ consumerKey="11111", consumerSecret="22222", accessToken="33333", accessTokenSecret="444444" ] - -``` - -twitter.items: - -```java -String sample_tweet "Latest Tweet: [%s]" { channel="twitter:account:sampleaccount:lasttweet" } - -``` - -## Rule Action - -This binding includes rule actions for sending tweets and direct messages. - -- `boolean success = sendTweet(String text)` -- `boolean success = sendTweetWithAttachment(String text, String URL)` -- `boolean success = sendDirectMessage(String recipientID, String text)` - -Examples: - -```java -val tweetActions = getActions("twitter","twitter:account:sampleaccount") -val success = tweetActions.sendTweet("This is A Tweet") -val success2 = tweetActions.sendTweetWithAttachment("This is A Tweet with a Pic", file:///tmp/201601011031.jpg) -val success3 = tweetActions.sendTweetWithAttachment("Windows Picture", "D:\\Test.png" ) -val success4 = tweetActions.sendTweetWithAttachment("HTTP Picture", "http://www.mywebsite.com/Test.png" ) -val success5 = tweetActions.sendDirectMessage("1234567", "Wake Up" ) - -``` diff --git a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter.properties b/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter.properties deleted file mode 100644 index 161cccf2a81be..0000000000000 --- a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter.properties +++ /dev/null @@ -1,32 +0,0 @@ -# add-on - -addon.twitter.name = Twitter Binding -addon.twitter.description = Supports adding Thing for getting the Last Tweet. Send Tweets and Pictures with Actions. - -# thing types - -thing-type.twitter.account.label = Twitter Account -thing-type.twitter.account.description = Account uses for sending Tweets - -# thing types config - -thing-type.config.twitter.account.accessToken.label = Access Token -thing-type.config.twitter.account.accessTokenSecret.label = Access Token Secret -thing-type.config.twitter.account.consumerKey.label = Consumer API Key -thing-type.config.twitter.account.consumerSecret.label = Consumer API Secret -thing-type.config.twitter.account.refresh.label = Refresh Time -thing-type.config.twitter.account.refresh.description = Refresh Time for This Account in Mins - -# channel types - -channel-type.twitter.lasttweet.label = Last Tweet -channel-type.twitter.lasttweet.description = Users Last Tweet - -# actions - -sendAttachmentTweetActionLabel = send a Tweet with attachment -sendAttachmentTweetActionDescription = Sends a Tweet with an attachment. -sendDirectMessageActionLabel = send a DirectMessage -sendDirectMessageActionDescription = Sends a DirectMessage. -sendTweetActionLabel = send a Tweet -sendTweetActionDescription = Sends a Tweet. diff --git a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_fr.properties b/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_fr.properties deleted file mode 100644 index 06d82693efbf8..0000000000000 --- a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_fr.properties +++ /dev/null @@ -1,32 +0,0 @@ -# add-on - -addon.twitter.name = Extension Twitter -addon.twitter.description = Cette extension permet d'obtenir le dernier Tweet ou d'envoyer des Tweets et des images depuis vos règles d'automatisation. - -# thing types - -thing-type.twitter.account.label = Compte Twitter -thing-type.twitter.account.description = Compte pour envoyer des Tweets - -# thing types config - -thing-type.config.twitter.account.accessToken.label = Jeton d'authentification -thing-type.config.twitter.account.accessTokenSecret.label = Code secret du jeton d'authentification -thing-type.config.twitter.account.consumerKey.label = Clé d'accès à l'API -thing-type.config.twitter.account.consumerSecret.label = Code secret pour l'accès à l'API -thing-type.config.twitter.account.refresh.label = Fréquence de rafraîchissement -thing-type.config.twitter.account.refresh.description = Fréquence de rafraîchissement de ce compte en minutes - -# channel types - -channel-type.twitter.lasttweet.label = Dernier Tweet -channel-type.twitter.lasttweet.description = Dernier Tweet des utilisateurs - -# actions - -sendAttachmentTweetActionLabel = envoyer un Tweet avec pièce jointe -sendAttachmentTweetActionDescription = Envoie un Tweet avec pièce jointe. -sendDirectMessageActionLabel = envoyer un message privé -sendDirectMessageActionDescription = Envoie un message privé. -sendTweetActionLabel = envoyer un Tweet -sendTweetActionDescription = Envoie un Tweet. diff --git a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_ru.properties b/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_ru.properties deleted file mode 100644 index 6991945830740..0000000000000 --- a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/i18n/twitter_ru.properties +++ /dev/null @@ -1,9 +0,0 @@ -# actions -sendTweetActionLabel = отправить твит -sendTweetActionDescription = Отправляет твит. - -sendAttachmentTweetActionLabel = отправить твит с вложением -sendAttachmentTweetActionDescription = Отправляет твит с вложением. - -sendDirectMessageActionLabel = отправить личное сообщение -sendDirectMessageActionDescription = Отправляет личное сообщение. diff --git a/bundles/org.openhab.binding.twitter/NOTICE b/bundles/org.openhab.binding.x/NOTICE similarity index 100% rename from bundles/org.openhab.binding.twitter/NOTICE rename to bundles/org.openhab.binding.x/NOTICE diff --git a/bundles/org.openhab.binding.x/README.md b/bundles/org.openhab.binding.x/README.md new file mode 100644 index 0000000000000..846ee864b1164 --- /dev/null +++ b/bundles/org.openhab.binding.x/README.md @@ -0,0 +1,60 @@ +# X Binding + +The X (formerly known as Twitter) binding allows your home to post 280 characters at a time. It also supports direct messages and posting with media. + +## Supported Things + +```text +account - X Account. +``` + +## Thing Configuration + +The X Account Thing requires you to create a X App in the X Developer Page. + +| Property | Default | Required | Description | +|-------------------|---------|:--------:|-----------------------------------| +| consumerKey | | Yes | Consumer API Key | +| consumerSecret | | Yes | Consumer API Secret | +| accessToken | | Yes | Access Token | +| accessTokenSecret | | Yes | Access Token Secret | +| refresh | 30 | No | Post refresh interval in minutes | + +## Channels + +| channel | type | description | +|----------|--------|-----------------------------------------------| +| lastpost | String | This channel provides the Latest post message | + +## Full Example + +x.things: + +```java +Thing x:account:sampleaccount [ consumerKey="11111", consumerSecret="22222", accessToken="33333", accessTokenSecret="444444" ] +``` + +x.items: + +```java +String sample_post "Latest post: [%s]" { channel="x:account:sampleaccount:lastpost" } +``` + +## Rule Action + +This binding includes rule actions for sending posts and direct messages. + +- `boolean success = sendPost(String text)` +- `boolean success = sendPostWithAttachment(String text, String URL)` +- `boolean success = sendDirectMessage(String recipientID, String text)` + +Examples: + +```java +val postActions = getActions("x","x:account:sampleaccount") +val success = postActions.sendPost("This is A Post") +val success2 = postActions.sendPostWithAttachment("This is A Post with a Pic", file:///tmp/201601011031.jpg) +val success3 = postActions.sendPostWithAttachment("Windows Picture", "D:\\Test.png" ) +val success4 = postActions.sendPostWithAttachment("HTTP Picture", "http://www.mywebsite.com/Test.png" ) +val success5 = postActions.sendDirectMessage("1234567", "Wake Up" ) +``` diff --git a/bundles/org.openhab.binding.twitter/pom.xml b/bundles/org.openhab.binding.x/pom.xml similarity index 67% rename from bundles/org.openhab.binding.twitter/pom.xml rename to bundles/org.openhab.binding.x/pom.xml index 00e42c8b7ff4f..f7020d2f91e9e 100644 --- a/bundles/org.openhab.binding.twitter/pom.xml +++ b/bundles/org.openhab.binding.x/pom.xml @@ -10,19 +10,19 @@ 4.1.0-SNAPSHOT - org.openhab.binding.twitter + org.openhab.binding.x - openHAB Add-ons :: Bundles :: Twitter Binding + openHAB Add-ons :: Bundles :: X Binding - !android.*,!com.android.org.*,!dalvik.*,!javax.annotation.meta.*,!org.apache.harmony.*,!org.conscrypt.*,!sun.*,!com.google.appengine.api.* + !org.slf4j.impl.*,!android.*,!com.android.org.*,!dalvik.*,!javax.annotation.meta.*,!org.apache.harmony.*,!org.conscrypt.*,!sun.*,!com.google.appengine.api.* org.twitter4j twitter4j-core - 4.0.7 + 4.1.2 compile diff --git a/bundles/org.openhab.binding.twitter/src/main/feature/feature.xml b/bundles/org.openhab.binding.x/src/main/feature/feature.xml similarity index 53% rename from bundles/org.openhab.binding.twitter/src/main/feature/feature.xml rename to bundles/org.openhab.binding.x/src/main/feature/feature.xml index 86048f2829fcf..ffb954879f7c5 100644 --- a/bundles/org.openhab.binding.twitter/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.x/src/main/feature/feature.xml @@ -1,9 +1,9 @@ - + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - + openhab-runtime-base - mvn:org.openhab.addons.bundles/org.openhab.binding.twitter/${project.version} + mvn:org.openhab.addons.bundles/org.openhab.binding.x/${project.version} diff --git a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterBindingConstants.java b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XBindingConstants.java similarity index 72% rename from bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterBindingConstants.java rename to bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XBindingConstants.java index c42baaeb2ba68..d35f5246b6285 100644 --- a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterBindingConstants.java +++ b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XBindingConstants.java @@ -10,25 +10,25 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.twitter.internal; +package org.openhab.binding.x.internal; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.ThingTypeUID; /** - * The {@link TwitterBindingConstants} class defines common constants, which are + * The {@link XBindingConstants} class defines common constants, which are * used across the whole binding. * * @author Scott Hanson - Initial contribution */ @NonNullByDefault -public class TwitterBindingConstants { +public class XBindingConstants { - private static final String BINDING_ID = "twitter"; + private static final String BINDING_ID = "x"; // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account"); // List of all Channel ids - public static final String CHANNEL_LASTTWEET = "lasttweet"; + public static final String CHANNEL_LASTPOST = "lastpost"; } diff --git a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandler.java b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandler.java similarity index 64% rename from bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandler.java rename to bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandler.java index 8b962d7902e9f..b262840e2e6f9 100644 --- a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandler.java +++ b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandler.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.twitter.internal; +package org.openhab.binding.x.internal; -import static org.openhab.binding.twitter.internal.TwitterBindingConstants.CHANNEL_LASTTWEET; +import static org.openhab.binding.x.internal.XBindingConstants.CHANNEL_LASTPOST; import java.io.File; import java.io.FileNotFoundException; @@ -27,8 +27,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.twitter.internal.action.TwitterActions; -import org.openhab.binding.twitter.internal.config.TwitterConfig; +import org.openhab.binding.x.internal.action.XActions; +import org.openhab.binding.x.internal.config.XConfig; import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.library.types.RawType; import org.openhab.core.library.types.StringType; @@ -41,28 +41,26 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import twitter4j.DirectMessage; -import twitter4j.ResponseList; -import twitter4j.Status; -import twitter4j.StatusUpdate; import twitter4j.Twitter; import twitter4j.TwitterException; -import twitter4j.TwitterFactory; -import twitter4j.auth.AccessToken; +import twitter4j.v1.DirectMessage; +import twitter4j.v1.ResponseList; +import twitter4j.v1.Status; +import twitter4j.v1.StatusUpdate; /** - * The {@link TwitterHandler} is responsible for handling commands, which are + * The {@link XHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Scott Hanson - Initial contribution */ @NonNullByDefault -public class TwitterHandler extends BaseThingHandler { +public class XHandler extends BaseThingHandler { - private final Logger logger = LoggerFactory.getLogger(TwitterHandler.class); + private final Logger logger = LoggerFactory.getLogger(XHandler.class); - private TwitterConfig config = new TwitterConfig(); + private XConfig config = new XConfig(); private @Nullable ScheduledFuture refreshTask; @@ -71,7 +69,7 @@ public class TwitterHandler extends BaseThingHandler { private static @Nullable Twitter client = null; boolean isProperlyConfigured = false; - public TwitterHandler(Thing thing) { + public XHandler(Thing thing) { super(thing); } @@ -82,14 +80,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { // creates list of available Actions @Override public Collection> getServices() { - return List.of(TwitterActions.class); + return List.of(XActions.class); } @Override public void initialize() { - config = getConfigAs(TwitterConfig.class); + config = getConfigAs(XConfig.class); - // create a New Twitter Client + // create a New X/Twitter Client Twitter localClient = createClient(); client = localClient; refresh();// Get latest status @@ -107,7 +105,7 @@ public void dispose() { } /** - * Internal method for Getting Twitter Status + * Internal method for Getting X Status * */ private void refresh() { @@ -117,83 +115,83 @@ private void refresh() { } Twitter localClient = client; if (localClient != null) { - ResponseList statuses = localClient.getUserTimeline(); + ResponseList statuses = localClient.v1().timelines().getUserTimeline(); if (!statuses.isEmpty()) { - updateState(CHANNEL_LASTTWEET, StringType.valueOf(statuses.get(0).getText())); + updateState(CHANNEL_LASTPOST, StringType.valueOf(statuses.get(0).getText())); } else { logger.debug("No Statuses Found"); } } } catch (TwitterException e) { - logger.debug("Error when trying to refresh Twitter Account: {}", e.getMessage()); + logger.debug("Error when trying to refresh X Account: {}", e.getMessage()); } } /** - * Internal method for sending a tweet, with or without image + * Internal method for sending a post, with or without image * - * @param tweetTxt - * text string to be sent as a Tweet + * @param postTxt + * text string to be sent as a Post * @param fileToAttach * the file to attach. May be null if no attached file. * - * @return true, if sending the tweet has been successful and + * @return true, if sending the post has been successful and * false in all other cases. */ - private boolean sendTweet(final String tweetTxt, final @Nullable File fileToAttach) { + private boolean sendPost(final String postTxt, final @Nullable File fileToAttach) { if (!checkPrerequisites()) { return false; } - // abbreviate the Tweet to meet the 280 character limit ... - String abbreviatedTweetTxt = abbreviateString(tweetTxt, CHARACTER_LIMIT); + // abbreviate the Post to meet the 280 character limit ... + String abbreviatedPostTxt = abbreviateString(postTxt, CHARACTER_LIMIT); try { Twitter localClient = client; if (localClient != null) { - // send the Tweet - StatusUpdate status = new StatusUpdate(abbreviatedTweetTxt); + // send the Post + StatusUpdate status = StatusUpdate.of(abbreviatedPostTxt); if (fileToAttach != null && fileToAttach.isFile()) { - status.setMedia(fileToAttach); + status = status.media(fileToAttach); } - Status updatedStatus = localClient.updateStatus(status); - logger.debug("Successfully sent Tweet '{}'", updatedStatus.getText()); - updateState(CHANNEL_LASTTWEET, StringType.valueOf(updatedStatus.getText())); + Status updatedStatus = localClient.v1().tweets().updateStatus(status); + logger.debug("Successfully sent Post '{}'", updatedStatus.getText()); + updateState(CHANNEL_LASTPOST, StringType.valueOf(updatedStatus.getText())); return true; } } catch (TwitterException e) { - logger.warn("Failed to send Tweet '{}' because of : {}", abbreviatedTweetTxt, e.getLocalizedMessage()); + logger.warn("Failed to send Post '{}' because of : {}", abbreviatedPostTxt, e.getLocalizedMessage()); } return false; } /** - * Sends a standard Tweet. + * Sends a standard Post. * - * @param tweetTxt - * text string to be sent as a Tweet + * @param postTxt + * text string to be sent as a Post * - * @return true, if sending the tweet has been successful and + * @return true, if sending the post has been successful and * false in all other cases. */ - public boolean sendTweet(String tweetTxt) { + public boolean sendPost(String postTxt) { if (!checkPrerequisites()) { return false; } - return sendTweet(tweetTxt, (File) null); + return sendPost(postTxt, (File) null); } /** - * Sends a Tweet with an image + * Sends a Post with an image * - * @param tweetTxt - * text string to be sent as a Tweet - * @param tweetPicture + * @param postTxt + * text string to be sent as a Post + * @param postPicture * the path of the picture that needs to be attached (either an url, * either a path pointing to a local file) * - * @return true, if sending the tweet has been successful and + * @return true, if sending the post has been successful and * false in all other cases. */ - public boolean sendTweet(String tweetTxt, String tweetPicture) { + public boolean sendPost(String postTxt, String postPicture) { if (!checkPrerequisites()) { return false; } @@ -201,18 +199,18 @@ public boolean sendTweet(String tweetTxt, String tweetPicture) { // prepare the image attachment File fileToAttach = null; boolean deleteTemporaryFile = false; - if (tweetPicture.startsWith("http://") || tweetPicture.startsWith("https://")) { + if (postPicture.startsWith("http://") || postPicture.startsWith("https://")) { try { // we have a remote url and need to download the remote file to a temporary location Path tDir = Files.createTempDirectory("TempDirectory"); - String path = tDir + File.separator + "openhab-twitter-remote_attached_file" + "." - + getExtension(tweetPicture); + String path = tDir + File.separator + "openhab-x-remote_attached_file" + "." + + getExtension(postPicture); - // URL url = new URL(tweetPicture); + // URL url = new URL(postPicture); fileToAttach = new File(path); deleteTemporaryFile = true; - RawType rawPicture = HttpUtil.downloadImage(tweetPicture); + RawType rawPicture = HttpUtil.downloadImage(postPicture); if (rawPicture != null) { try (FileOutputStream fos = new FileOutputStream(path)) { fos.write(rawPicture.getBytes(), 0, rawPicture.getBytes().length); @@ -222,24 +220,24 @@ public boolean sendTweet(String tweetTxt, String tweetPicture) { logger.debug("Could not write {} to temp dir. {}", path, ex.getMessage()); } } else { - logger.debug("Could not download tweet file from {}", tweetPicture); + logger.debug("Could not download post file from {}", postPicture); } } catch (IOException ex) { - logger.debug("Could not write {} to temp dir. {}", tweetPicture, ex.getMessage()); + logger.debug("Could not write {} to temp dir. {}", postPicture, ex.getMessage()); } } else { // we have a local file and can just use it directly - fileToAttach = new File(tweetPicture); + fileToAttach = new File(postPicture); } if (fileToAttach != null && fileToAttach.isFile()) { - logger.debug("Image '{}' correctly found, will be included in tweet", tweetPicture); + logger.debug("Image '{}' correctly found, will be included in post", postPicture); } else { - logger.warn("Image '{}' not found, will only tweet text", tweetPicture); + logger.warn("Image '{}' not found, will only post text", postPicture); } - // send the Tweet - boolean result = sendTweet(tweetTxt, fileToAttach); + // send the Post + boolean result = sendPost(postTxt, fileToAttach); // delete temp file (if needed) if (deleteTemporaryFile) { if (fileToAttach != null) { @@ -272,10 +270,11 @@ public boolean sendDirectMessage(String recipientId, String messageTxt) { try { Twitter localClient = client; if (localClient != null) { - // abbreviate the Tweet to meet the allowed character limit ... + // abbreviate the Post to meet the allowed character limit ... String abbreviatedMessageTxt = abbreviateString(messageTxt, CHARACTER_LIMIT); // send the direct message - DirectMessage message = localClient.sendDirectMessage(recipientId, abbreviatedMessageTxt); + DirectMessage message = localClient.v1().directMessages().sendDirectMessage(recipientId, + abbreviatedMessageTxt); logger.debug("Successfully sent direct message '{}' to @'{}'", message.getText(), message.getRecipientId()); return true; @@ -287,18 +286,18 @@ public boolean sendDirectMessage(String recipientId, String messageTxt) { } /** - * check if twitter account was created with prerequisites - * - * @return true, if twitter account was initialized + * check if X account was created with prerequisites + * + * @return true, if X account was initialized * false in all other cases. */ private boolean checkPrerequisites() { if (client == null) { - logger.debug("Twitter client is not yet configured > execution aborted!"); + logger.debug("X client is not yet configured > execution aborted!"); return false; } if (!isProperlyConfigured) { - logger.debug("Twitter client is not yet configured > execution aborted!"); + logger.debug("X client is not yet configured > execution aborted!"); return false; } return true; @@ -310,9 +309,9 @@ private boolean checkPrerequisites() { * @return a new instance of a Twitter4J Twitter client. */ private twitter4j.Twitter createClient() { - twitter4j.Twitter client = TwitterFactory.getSingleton(); - client.setOAuthConsumer(config.consumerKey, config.consumerSecret); - client.setOAuthAccessToken(new AccessToken(config.accessToken, config.accessTokenSecret)); + Twitter client = Twitter.newBuilder().oAuthConsumer(config.consumerKey, config.consumerSecret) + .oAuthAccessToken(config.accessToken, config.accessTokenSecret).build(); + return client; } diff --git a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandlerFactory.java b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandlerFactory.java similarity index 76% rename from bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandlerFactory.java rename to bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandlerFactory.java index d27f76f7bd945..50a9341d2525a 100644 --- a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/TwitterHandlerFactory.java +++ b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/XHandlerFactory.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.twitter.internal; +package org.openhab.binding.x.internal; -import static org.openhab.binding.twitter.internal.TwitterBindingConstants.THING_TYPE_ACCOUNT; +import static org.openhab.binding.x.internal.XBindingConstants.THING_TYPE_ACCOUNT; import java.util.Set; @@ -26,14 +26,14 @@ import org.osgi.service.component.annotations.Component; /** - * The {@link TwitterHandlerFactory} is responsible for creating things and thing + * The {@link XHandlerFactory} is responsible for creating things and thing * handlers. * * @author Scott Hanson - Initial contribution */ @NonNullByDefault -@Component(configurationPid = "binding.twitter", service = ThingHandlerFactory.class) -public class TwitterHandlerFactory extends BaseThingHandlerFactory { +@Component(configurationPid = "binding.x", service = ThingHandlerFactory.class) +public class XHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT); @@ -47,7 +47,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) { - return new TwitterHandler(thing); + return new XHandler(thing); } return null; diff --git a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/action/TwitterActions.java b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/action/XActions.java similarity index 60% rename from bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/action/TwitterActions.java rename to bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/action/XActions.java index 12a56c3574161..8133645ddf4f5 100644 --- a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/action/TwitterActions.java +++ b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/action/XActions.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.twitter.internal.action; +package org.openhab.binding.x.internal.action; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.twitter.internal.TwitterHandler; +import org.openhab.binding.x.internal.XHandler; import org.openhab.core.automation.annotation.ActionInput; import org.openhab.core.automation.annotation.ActionOutput; import org.openhab.core.automation.annotation.RuleAction; @@ -25,53 +25,53 @@ import org.slf4j.LoggerFactory; /** - * The {@link TwitterActions} class defines rule actions for sending tweet + * The {@link XActions} class defines rule actions for sending post * * @author Scott Hanson - Initial contribution */ -@ThingActionsScope(name = "twitter") +@ThingActionsScope(name = "x") @NonNullByDefault -public class TwitterActions implements ThingActions { +public class XActions implements ThingActions { - private final Logger logger = LoggerFactory.getLogger(TwitterActions.class); + private final Logger logger = LoggerFactory.getLogger(XActions.class); - private @Nullable TwitterHandler handler; + private @Nullable XHandler handler; - @RuleAction(label = "@text/sendTweetActionLabel", description = "@text/sendTweetActionDescription") - public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendTweet( + @RuleAction(label = "@text/sendPostActionLabel", description = "@text/sendPostActionDescription") + public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPost( @ActionInput(name = "text") @Nullable String text) { if (text == null) { - logger.warn("Cannot send Tweet as text is missing."); + logger.warn("Cannot send Post as text is missing."); return false; } - final TwitterHandler handler = this.handler; + final XHandler handler = this.handler; if (handler == null) { - logger.debug("Handler is null, cannot tweet."); + logger.debug("Handler is null, cannot post."); return false; } else { - return handler.sendTweet(text); + return handler.sendPost(text); } } - @RuleAction(label = "@text/sendAttachmentTweetActionLabel", description = "@text/sendAttachmentTweetActionDescription") - public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendTweetWithAttachment( + @RuleAction(label = "@text/sendAttachmentPostActionLabel", description = "@text/sendAttachmentPostActionDescription") + public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPostWithAttachment( @ActionInput(name = "text") @Nullable String text, @ActionInput(name = "url") @Nullable String urlString) { if (text == null) { - logger.warn("Cannot send Tweet as text is missing."); + logger.warn("Cannot send Post as text is missing."); return false; } if (urlString == null) { - logger.warn("Cannot send Tweet as urlString is missing."); + logger.warn("Cannot send Post as urlString is missing."); return false; } - final TwitterHandler handler = this.handler; + final XHandler handler = this.handler; if (handler == null) { - logger.debug("Handler is null, cannot tweet."); + logger.debug("Handler is null, cannot post."); return false; } else { - return handler.sendTweet(text, urlString); + return handler.sendPost(text, urlString); } } @@ -88,32 +88,32 @@ public class TwitterActions implements ThingActions { return false; } - final TwitterHandler handler = this.handler; + final XHandler handler = this.handler; if (handler == null) { - logger.debug("Handler is null, cannot tweet."); + logger.debug("Handler is null, cannot post."); return false; } else { return handler.sendDirectMessage(recipient, text); } } - public static boolean sendTweet(ThingActions actions, @Nullable String text) { - return ((TwitterActions) actions).sendTweet(text); + public static boolean sendPost(ThingActions actions, @Nullable String text) { + return ((XActions) actions).sendPost(text); } - public static boolean sendTweetWithAttachment(ThingActions actions, @Nullable String text, + public static boolean sendPostWithAttachment(ThingActions actions, @Nullable String text, @Nullable String urlString) { - return ((TwitterActions) actions).sendTweetWithAttachment(text, urlString); + return ((XActions) actions).sendPostWithAttachment(text, urlString); } public static boolean sendDirectMessage(ThingActions actions, @Nullable String recipient, @Nullable String text) { - return ((TwitterActions) actions).sendDirectMessage(recipient, text); + return ((XActions) actions).sendDirectMessage(recipient, text); } @Override public void setThingHandler(@Nullable ThingHandler handler) { - if (handler instanceof TwitterHandler twitterHandler) { - this.handler = twitterHandler; + if (handler instanceof XHandler xHandler) { + this.handler = xHandler; } } diff --git a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/config/TwitterConfig.java b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/config/XConfig.java similarity index 80% rename from bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/config/TwitterConfig.java rename to bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/config/XConfig.java index 529bee31a858a..73e11d9dd7547 100644 --- a/bundles/org.openhab.binding.twitter/src/main/java/org/openhab/binding/twitter/internal/config/TwitterConfig.java +++ b/bundles/org.openhab.binding.x/src/main/java/org/openhab/binding/x/internal/config/XConfig.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.twitter.internal.config; +package org.openhab.binding.x.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link TwitterConfig} class contains fields mapping thing configuration parameters. + * The {@link XConfig} class contains fields mapping thing configuration parameters. * * @author Scott Hanson - Initial contribution */ @NonNullByDefault -public class TwitterConfig { +public class XConfig { public String consumerKey = ""; public String consumerSecret = ""; public String accessToken = ""; diff --git a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/addon/addon.xml similarity index 54% rename from bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/addon/addon.xml rename to bundles/org.openhab.binding.x/src/main/resources/OH-INF/addon/addon.xml index 8c90697c78f48..acb27648087c2 100644 --- a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/addon/addon.xml @@ -1,11 +1,11 @@ - binding - Twitter Binding - Supports adding Thing for getting the Last Tweet. Send Tweets and Pictures with Actions. + X Binding + Supports adding Thing for getting the Last Post. Send Posts and Pictures with Actions. cloud diff --git a/bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x.properties b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x.properties new file mode 100644 index 0000000000000..66bc6924ed3df --- /dev/null +++ b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x.properties @@ -0,0 +1,32 @@ +# add-on + +addon.x.name = X Binding +addon.x.description = Supports adding Thing for getting the Last Post. Send Posts and Pictures with Actions. + +# thing types + +thing-type.x.account.label = X Account +thing-type.x.account.description = Account uses for sending posts + +# thing types config + +thing-type.config.x.account.accessToken.label = Access Token +thing-type.config.x.account.accessTokenSecret.label = Access Token Secret +thing-type.config.x.account.consumerKey.label = Consumer API Key +thing-type.config.x.account.consumerSecret.label = Consumer API Secret +thing-type.config.x.account.refresh.label = Refresh Time +thing-type.config.x.account.refresh.description = Refresh Time for This Account in Mins + +# channel types + +channel-type.x.lastpost.label = Last Post +channel-type.x.lastpost.description = Users Last Post + +# actions + +sendAttachmentPostActionLabel = send a Post with attachment +sendAttachmentPostActionDescription = Sends a Post with an attachment. +sendDirectMessageActionLabel = send a DirectMessage +sendDirectMessageActionDescription = Sends a DirectMessage. +sendPostActionLabel = send a Post +sendPostActionDescription = Sends a Post. diff --git a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/thing/thing-types.xml similarity index 78% rename from bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/thing/thing-types.xml rename to bundles/org.openhab.binding.x/src/main/resources/OH-INF/thing/thing-types.xml index 1ec1355e99059..d18df8f5e1c0c 100644 --- a/bundles/org.openhab.binding.twitter/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/thing/thing-types.xml @@ -1,15 +1,14 @@ - - - Account uses for sending Tweets + + Account uses for sending posts - + @@ -38,10 +37,10 @@ - + String - - Users Last Tweet + + Users Last Post diff --git a/bundles/pom.xml b/bundles/pom.xml index 655b460a41a80..4ab10b59b2f7a 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -388,7 +388,6 @@ org.openhab.binding.tplinksmarthome org.openhab.binding.tr064 org.openhab.binding.tradfri - org.openhab.binding.twitter org.openhab.binding.unifi org.openhab.binding.unifiedremote org.openhab.binding.upnpcontrol @@ -420,6 +419,7 @@ org.openhab.binding.wled org.openhab.binding.wolfsmartset org.openhab.binding.wundergroundupdatereceiver + org.openhab.binding.x org.openhab.binding.xmltv org.openhab.binding.xmppclient org.openhab.binding.yamahamusiccast From a09a8a10bbaa337c835b8e251b0e644f2281a8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Fri, 17 Nov 2023 18:41:59 +0100 Subject: [PATCH 085/146] [netatmo] Enhance errored modules handling (#15866) * Resolves issue #15858 * Enhance null control in DTO --------- Signed-off-by: clinique --- .../internal/api/dto/NAHomeStatus.java | 14 +++++++------ .../internal/deserialization/NAObjectMap.java | 4 ++-- .../internal/handler/CommonInterface.java | 20 +++++++++++++++++++ .../handler/capability/EnergyCapability.java | 12 +++++++++-- .../handler/capability/HomeCapability.java | 13 ++++++++---- 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java index 59861c4dd5b4b..5583bc4aef831 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java @@ -35,24 +35,26 @@ public class HomeStatus extends NAThing { private @Nullable NAObjectMap modules; public NAObjectMap getModules() { - NAObjectMap localModules = modules; - return localModules != null ? localModules : new NAObjectMap<>(); + NAObjectMap local = modules; + return local != null ? local : new NAObjectMap<>(); } } public class Energy extends HomeStatus { - private NAObjectMap rooms = new NAObjectMap<>(); + private @Nullable NAObjectMap rooms; public NAObjectMap getRooms() { - return rooms; + NAObjectMap local = rooms; + return local != null ? local : new NAObjectMap<>(); } } public class Security extends HomeStatus { - private NAObjectMap persons = new NAObjectMap<>(); + private @Nullable NAObjectMap persons; public NAObjectMap getPersons() { - return persons; + NAObjectMap local = persons; + return local != null ? local : new NAObjectMap<>(); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java index c4357e6d40a1e..4251c4f439e9b 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java @@ -33,7 +33,7 @@ public T put(T thing) { return super.put(thing.getId(), thing); } - public Optional getOpt(String key) { - return Optional.ofNullable(super.get(key)); + public Optional getOpt(@Nullable String key) { + return Optional.ofNullable(key != null ? super.get(key) : null); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java index 1a6c973692b21..85ba84c9078fd 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java @@ -128,6 +128,26 @@ default Optional recurseUpToHomeHandler(@Nullable CommonInterfa : recurseUpToHomeHandler(handler.getBridgeHandler()); } + /** + * Recurses down in the home/module/device tree + * + * @param bridge + * @return the list of childs of the bridge + */ + default List getAllActiveChildren(Bridge bridge) { + List result = new ArrayList<>(); + bridge.getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler).forEach(childHandler -> { + if (childHandler != null) { + Thing childThing = childHandler.getThing(); + if (childThing instanceof Bridge bridgeChild) { + result.addAll(getAllActiveChildren(bridgeChild)); + } + result.add((CommonInterface) childHandler); + } + }); + return result; + } + default List getActiveChildren() { Thing thing = getThing(); if (thing instanceof Bridge bridge) { diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java index 832d2252dcd0a..39752014ec589 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java @@ -91,11 +91,19 @@ protected void updateHomeStatus(HomeStatus homeStatus) { NAObjectMap modules = energyStatus.getModules(); handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> { String childId = childHandler.getId(); - rooms.getOpt(childId).ifPresentOrElse(roomData -> childHandler.setNewData(roomData), () -> { + logger.trace("childId: {}", childId); + rooms.getOpt(childId).ifPresentOrElse(roomData -> { + logger.trace("roomData: {}", roomData); + childHandler.setNewData(roomData); + }, () -> { modules.getOpt(childId).ifPresent(moduleData -> { + logger.trace("moduleData: {}", moduleData); childHandler.setNewData(moduleData); modules.values().stream().filter(module -> childId.equals(module.getBridge())) - .forEach(bridgedModule -> childHandler.setNewData(bridgedModule)); + .forEach(bridgedModule -> { + logger.trace("bridgedModule: {}", bridgedModule); + childHandler.setNewData(bridgedModule); + }); }); }); }); diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java index f9e281ab5ddaa..4b7a62ef04220 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java @@ -32,6 +32,7 @@ import org.openhab.binding.netatmo.internal.config.HomeConfiguration; import org.openhab.binding.netatmo.internal.handler.CommonInterface; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; +import org.openhab.core.thing.Bridge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -105,10 +106,14 @@ private boolean hasArea(FeatureArea searched) { return featureAreas.contains(searched); } + /** + * Errored equipments are reported at home level - so we need to explore all the tree to identify modules + * depending from a child device. + */ @Override protected void updateErrors(NAError error) { - handler.getActiveChildren().stream().filter(handler -> handler.getId().equals(error.getId())).findFirst() - .ifPresent(handler -> handler.setNewData(error)); + handler.getAllActiveChildren((Bridge) thing).stream().filter(handler -> handler.getId().equals(error.getId())) + .findFirst().ifPresent(handler -> handler.setNewData(error)); } @Override @@ -123,11 +128,11 @@ protected List updateReadings(HomeApi api) { } api.getHomeStatus(id).ifPresent(body -> { - body.getHomeStatus().ifPresent(homeStatus -> result.add(homeStatus)); + body.getHomeStatus().ifPresent(result::add); result.addAll(body.getErrors()); }); } catch (NetatmoException e) { - logger.warn("Error getting Home informations : {}", e.getMessage()); + logger.warn("Error getting Home informations: {}", e.getMessage()); } }); return result; From 77adc4c2ce71869032a48551c06a860519268d65 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Fri, 17 Nov 2023 18:44:06 +0100 Subject: [PATCH 086/146] New translations x.properties (Italian) (#15901) --- .../resources/OH-INF/i18n/x_it.properties | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x_it.properties diff --git a/bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x_it.properties b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x_it.properties new file mode 100644 index 0000000000000..016fe4d68bba6 --- /dev/null +++ b/bundles/org.openhab.binding.x/src/main/resources/OH-INF/i18n/x_it.properties @@ -0,0 +1,32 @@ +# add-on + +addon.x.name = Binding X +addon.x.description = Supporta l'aggiunta di Thing per ottenere l'ultimo Post. Invia messaggi e immagini con azioni. + +# thing types + +thing-type.x.account.label = Account X +thing-type.x.account.description = Account utilizzato per inviare messaggi + +# thing types config + +thing-type.config.x.account.accessToken.label = Token Accesso +thing-type.config.x.account.accessTokenSecret.label = Segreto per Token di Accesso +thing-type.config.x.account.consumerKey.label = Chiave API utente +thing-type.config.x.account.consumerSecret.label = Segreti API utente +thing-type.config.x.account.refresh.label = Tempo di aggiornamento +thing-type.config.x.account.refresh.description = Aggiorna il tempo per questo account in Minuti + +# channel types + +channel-type.x.lastpost.label = Ultimo Post +channel-type.x.lastpost.description = Ultimo Post Utenti + +# actions + +sendAttachmentPostActionLabel = invia un Post con allegato +sendAttachmentPostActionDescription = Invia un Post con un allegato. +sendDirectMessageActionLabel = invia un DirectMessage +sendDirectMessageActionDescription = Invia un DirectMessage. +sendPostActionLabel = invia un Post +sendPostActionDescription = Invia un Messaggio. From 1c01400a5bc75b6c47b6bd862ad5b012137d79b1 Mon Sep 17 00:00:00 2001 From: mlobstein Date: Fri, 17 Nov 2023 11:51:26 -0600 Subject: [PATCH 087/146] Ignore invalid elapsed/total time values (#15900) Signed-off-by: Michael Lobstein --- .../org/openhab/binding/roku/internal/handler/RokuHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bundles/org.openhab.binding.roku/src/main/java/org/openhab/binding/roku/internal/handler/RokuHandler.java b/bundles/org.openhab.binding.roku/src/main/java/org/openhab/binding/roku/internal/handler/RokuHandler.java index bb886851704cc..ea9067a272a1a 100644 --- a/bundles/org.openhab.binding.roku/src/main/java/org/openhab/binding/roku/internal/handler/RokuHandler.java +++ b/bundles/org.openhab.binding.roku/src/main/java/org/openhab/binding/roku/internal/handler/RokuHandler.java @@ -205,6 +205,8 @@ private void refreshPlayerState() { } else { updateState(TIME_TOTAL, UnDefType.UNDEF); } + } catch (NumberFormatException e) { + logger.debug("Unable to parse playerInfo integer value. Exception: {}", e.getMessage()); } catch (RokuHttpException e) { logger.debug("Unable to retrieve Roku media-player info. Exception: {}", e.getMessage(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); From adc171affdd83c12929dbf5c37af42c4968e7f33 Mon Sep 17 00:00:00 2001 From: Hakan Tandogan Date: Fri, 17 Nov 2023 19:52:53 +0100 Subject: [PATCH 088/146] [tesla] extend docu on 'softwareupdateversion', this can hold map updates too (#15835) * [tesla] extend docu on 'softwareupdateversion', this can hold map updates too --------- Signed-off-by: Hakan Tandogan --- bundles/org.openhab.binding.tesla/README.md | 6 +++--- .../src/main/resources/OH-INF/i18n/tesla.properties | 6 +++--- .../src/main/resources/OH-INF/thing/channels.xml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.tesla/README.md b/bundles/org.openhab.binding.tesla/README.md index 4387fdc0b51aa..c97448323a809 100644 --- a/bundles/org.openhab.binding.tesla/README.md +++ b/bundles/org.openhab.binding.tesla/README.md @@ -182,9 +182,9 @@ Additionally, these advanced channels are available (not all are available on al | shiftstate | String | Shift State | Indicates the state of the transmission, “P”, “D”, “R”, or “N” | | sidemirrorheaters | Switch | Side Mirror Heaters | Indicates if the side mirror heaters are switched on | | smartpreconditioning | Switch | Smart Preconditioning | Indicates if smart preconditioning is switched on | -| softwareupdateavailable | Switch | Update Available | Car software update available, automatically generated on non-empty "update version" | -| softwareupdatestatus | String | Update Status | Car software update status, e.g. "downloading_wifi_wait", "installing" | -| softwareupdateversion | String | Update Version | Car software version to update to, e.g. "2023.32.9" or empty | +| softwareupdateavailable | Switch | Update Available | Car software or map update available, automatically generated on non-empty "update version" | +| softwareupdatestatus | String | Update Status | Car software or map update status, e.g. "downloading_wifi_wait", "installing" | +| softwareupdateversion | String | Update Version | Car software or map version to update to, e.g. "2023.32.9", "EU-2023.32-14783" for map updates, or empty | | soc | Number | State of Charge | State of Charge, in % | | state | String | State | “online”, “asleep”, “waking” | | steeringwheelheater | Switch | Steering Wheel Heater | Turns On/Off the steering wheel heater | 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 cdf0ab7d8c8a3..dd38a0070f1c1 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 @@ -323,11 +323,11 @@ channel-type.tesla.smartpreconditioning.description = Indicates if smart precond channel-type.tesla.soc.label = State of Charge channel-type.tesla.soc.description = State of Charge, in % channel-type.tesla.softwareupdateavailable.label = Update Available -channel-type.tesla.softwareupdateavailable.description = Car software update available +channel-type.tesla.softwareupdateavailable.description = Car software or map update available channel-type.tesla.softwareupdatestatus.label = Update Status -channel-type.tesla.softwareupdatestatus.description = Car software update status +channel-type.tesla.softwareupdatestatus.description = Car software or map update status channel-type.tesla.softwareupdateversion.label = Update Version -channel-type.tesla.softwareupdateversion.description = Car software version to update to +channel-type.tesla.softwareupdateversion.description = Car software or map version to update to channel-type.tesla.speed.label = Speed channel-type.tesla.speed.description = Vehicle speed channel-type.tesla.state.label = State diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml index 5facdaffb60fe..e6046793495c2 100644 --- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml @@ -7,19 +7,19 @@ Switch - Car software update available + Car software or map update available String - Car software update status + Car software or map update status String - Car software version to update to + Car software or map version to update to From 90d2d836ddf3ca3c3248966e72cf3429dd4e9b00 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 18 Nov 2023 12:44:09 +0100 Subject: [PATCH 089/146] [hdpowerview] Improve color channel for Repeater and remove redundant brightness channel (#15880) * Improve color channel * Log warning for unexpected color command --------- Signed-off-by: Jacob Laursen --- .../org.openhab.binding.hdpowerview/README.md | 15 +-- .../internal/HDPowerViewBindingConstants.java | 1 - .../hdpowerview/internal/dto/Color.java | 8 ++ .../handler/HDPowerViewRepeaterHandler.java | 99 +++++++++++++------ .../OH-INF/i18n/hdpowerview.properties | 3 +- .../main/resources/OH-INF/thing/repeater.xml | 6 +- .../main/resources/OH-INF/update/update.xml | 14 +-- 7 files changed, 88 insertions(+), 58 deletions(-) diff --git a/bundles/org.openhab.binding.hdpowerview/README.md b/bundles/org.openhab.binding.hdpowerview/README.md index cda450a74d1d4..339069015ed18 100644 --- a/bundles/org.openhab.binding.hdpowerview/README.md +++ b/bundles/org.openhab.binding.hdpowerview/README.md @@ -129,12 +129,13 @@ On Generation 3 gateways the signal strength is displayed in dBm (deciBel-milliW ### Channels for Repeaters (Thing type `repeater`)[1/2] -| Channel | Item Type | Description | -|-----------------|-----------|---------------------------------------------------------------------------------------------| -| color | Color | Controls the color of the LED ring. A switch item can be linked: ON = white, OFF = turn off | -| brightness | Dimmer | Controls the brightness of the LED ring. | -| identify | String | Flash repeater to identify. Valid values are: `IDENTIFY` | -| blinkingEnabled | Switch | Blink during commands. | +| Channel | Item Type | Description | +|-----------------|-----------|----------------------------------------------------------| +| color | Color | Controls the color of the LED ring. | +| color | Dimmer | Controls the brightness of the LED ring. | +| color | Switch | Switches the LED ring on or off. | +| identify | String | Flash repeater to identify. Valid values are: `IDENTIFY` | +| blinkingEnabled | Switch | Blink during commands. | ### Roller Shutter Up/Down Position vs. Open/Close State @@ -292,7 +293,7 @@ Repeater items[1/2]: ```java Color Bedroom_Repeater_Color "Bedroom Repeater Color" {channel="hdpowerview:repeater:home:r16384:color"} -Dimmer Bedroom_Repeater_Brightness "Bedroom Repeater Brightness" {channel="hdpowerview:repeater:home:r16384:brightness"} +Dimmer Bedroom_Repeater_Brightness "Bedroom Repeater Brightness" {channel="hdpowerview:repeater:home:r16384:color"} 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"} ``` diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java index 6649024bae82c..34d070858e999 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java @@ -51,7 +51,6 @@ public class HDPowerViewBindingConstants { public static final String CHANNEL_SHADE_REPEATER_RSSI = "repeaterRssi"; public static final String CHANNEL_REPEATER_COLOR = "color"; - public static final String CHANNEL_REPEATER_BRIGHTNESS = "brightness"; public static final String CHANNEL_REPEATER_IDENTIFY = "identify"; public static final String CHANNEL_REPEATER_BLINKING_ENABLED = "blinkingEnabled"; diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/dto/Color.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/dto/Color.java index 631cfb6ba7540..3d6c33ca92965 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/dto/Color.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/dto/Color.java @@ -34,6 +34,10 @@ public Color(int brightness, java.awt.Color color) { this(brightness, color.getRed(), color.getGreen(), color.getBlue()); } + public Color(int brightness, Color color) { + this(brightness, color.red, color.green, color.blue); + } + public Color(int brightness, int red, int green, int blue) { this.brightness = brightness; this.red = red; @@ -41,6 +45,10 @@ public Color(int brightness, int red, int green, int blue) { this.blue = blue; } + public boolean isBlack() { + return red == 0 && green == 0 && blue == 0; + } + @Override public String toString() { return String.format("%d.%d.%d/%d%%", red, green, blue, brightness); diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java index 1d806fb9bf982..e5d456b4aaeba 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException; import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException; import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StringType; @@ -37,6 +38,7 @@ import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; import org.openhab.core.types.UnDefType; import org.openhab.core.util.ColorUtil; import org.slf4j.Logger; @@ -54,6 +56,7 @@ public class HDPowerViewRepeaterHandler extends AbstractHubbedThingHandler { private static final int REFRESH_INTERVAL_MINUTES = 5; private static final int IDENTITY_PERIOD_SECONDS = 3; + private static final int BRIGHTNESS_STEP_PERCENT = 5; private static final String COMMAND_IDENTIFY = "IDENTIFY"; private @Nullable ScheduledFuture refreshStatusFuture = null; @@ -94,40 +97,17 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.warn("Missing bridge handler"); return; } + if (command == RefreshType.REFRESH) { + scheduleRefreshJob(); + return; + } HDPowerViewWebTargets webTargets = bridge.getWebTargets(); try { RepeaterData repeaterData; switch (channelUID.getId()) { case CHANNEL_REPEATER_COLOR: - if (command instanceof HSBType hsbCommand) { - Color currentColor = webTargets.getRepeater(repeaterId).color; - if (currentColor != null) { - var color = new Color(currentColor.brightness, ColorUtil.hsbTosRgb(hsbCommand)); - repeaterData = webTargets.setRepeaterColor(repeaterId, color); - scheduler.submit(() -> updatePropertyAndStates(repeaterData)); - } - } else if (command instanceof OnOffType) { - Color currentColor = webTargets.getRepeater(repeaterId).color; - if (currentColor != null) { - var color = command == OnOffType.ON - ? new Color(currentColor.brightness, java.awt.Color.WHITE) - : new Color(currentColor.brightness, java.awt.Color.BLACK); - repeaterData = webTargets.setRepeaterColor(repeaterId, color); - scheduler.submit(() -> updatePropertyAndStates(repeaterData)); - } - } - break; - case CHANNEL_REPEATER_BRIGHTNESS: - if (command instanceof PercentType brightnessCommand) { - Color currentColor = webTargets.getRepeater(repeaterId).color; - if (currentColor != null) { - var color = new Color(brightnessCommand.intValue(), currentColor.red, currentColor.green, - currentColor.blue); - repeaterData = webTargets.setRepeaterColor(repeaterId, color); - scheduler.submit(() -> updatePropertyAndStates(repeaterData)); - } - } + handleColorCommand(command, webTargets); break; case CHANNEL_REPEATER_IDENTIFY: if (command instanceof StringType stringCommand) { @@ -163,6 +143,54 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } + private void handleColorCommand(Command command, HDPowerViewWebTargets webTargets) throws HubException { + if (command instanceof HSBType hsbCommand) { + var color = new Color(hsbCommand.getBrightness().intValue(), ColorUtil.hsbTosRgb(hsbCommand)); + RepeaterData repeaterData = webTargets.setRepeaterColor(repeaterId, color); + scheduler.submit(() -> updatePropertyAndStates(repeaterData)); + return; + } + Color currentColor = webTargets.getRepeater(repeaterId).color; + if (currentColor == null) { + return; + } + Color newColor; + if (command instanceof PercentType brightnessCommand) { + newColor = applyBrightnessToColor(currentColor, brightnessCommand.intValue()); + } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) { + int brightness = switch (increaseDecreaseCommand) { + case INCREASE -> currentColor.brightness + BRIGHTNESS_STEP_PERCENT; + case DECREASE -> currentColor.brightness - BRIGHTNESS_STEP_PERCENT; + }; + brightness = brightness < 0 ? 0 : brightness > 100 ? 100 : brightness; + newColor = applyBrightnessToColor(currentColor, brightness); + } else if (command instanceof OnOffType) { + // Light is turned off either by RGB black or zero brightness. + int brightness; + if (command == OnOffType.ON) { + // Turn on with maximum brightness level per default, + // if no existing brightness level is available. + brightness = currentColor.brightness > 0 ? currentColor.brightness : 100; + } else { + // Turn off by zero brightness to preserve color. + brightness = 0; + } + newColor = applyBrightnessToColor(currentColor, brightness); + } else { + logger.warn("Unsupported command: {}", command); + return; + } + RepeaterData repeaterData = webTargets.setRepeaterColor(repeaterId, newColor); + scheduler.submit(() -> updatePropertyAndStates(repeaterData)); + } + + private Color applyBrightnessToColor(Color currentColor, int brightness) { + // If light is off by RGB black, then reset to white since otherwise brightness + // would have no effect; otherwise preserve color. + return currentColor.isBlack() ? new Color(brightness, java.awt.Color.WHITE) + : new Color(brightness, currentColor); + } + private void cancelResetIdentifyStateJob() { ScheduledFuture scheduledJob = resetIdentifyStateFuture; if (scheduledJob != null) { @@ -182,7 +210,7 @@ private void scheduleRefreshJob() { private void cancelRefreshJob() { ScheduledFuture future = this.refreshStatusFuture; if (future != null) { - future.cancel(false); + future.cancel(true); } this.refreshStatusFuture = null; } @@ -228,10 +256,17 @@ private void updatePropertyAndStates(RepeaterData repeaterData) { Color color = repeaterData.color; if (color != null) { logger.debug("Repeater color data received: {}", color.toString()); - updateState(CHANNEL_REPEATER_COLOR, HSBType.fromRGB(color.red, color.green, color.red)); - updateState(CHANNEL_REPEATER_BRIGHTNESS, new PercentType(color.brightness)); + HSBType hsb; + if (color.isBlack()) { + // Light is off when RGB black, so discard brightness as otherwise it would appear on. + hsb = HSBType.BLACK; + } else { + hsb = HSBType.fromRGB(color.red, color.green, color.red); + hsb = new HSBType(hsb.getHue(), hsb.getSaturation(), new PercentType(color.brightness)); + } + updateState(CHANNEL_REPEATER_COLOR, hsb); } - updateState(CHANNEL_REPEATER_BLINKING_ENABLED, repeaterData.blinkEnabled ? OnOffType.ON : OnOffType.OFF); + updateState(CHANNEL_REPEATER_BLINKING_ENABLED, OnOffType.from(repeaterData.blinkEnabled)); } } 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 fc10667c8eb4a..8785d2082b3fe 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 @@ -11,8 +11,7 @@ thing-type.hdpowerview.hub.label = PowerView Hub thing-type.hdpowerview.hub.description = Hunter Douglas (Luxaflex) PowerView Hub thing-type.hdpowerview.repeater.label = PowerView Repeater thing-type.hdpowerview.repeater.description = Hunter Douglas (Luxaflex) PowerView Repeater -thing-type.hdpowerview.repeater.channel.brightness.description = Controls the brightness of the LED ring -thing-type.hdpowerview.repeater.channel.color.description = Controls the color of the LED ring +thing-type.hdpowerview.repeater.channel.color.description = Controls the color and brightness of the LED ring thing-type.hdpowerview.shade.label = PowerView Shade thing-type.hdpowerview.shade.description = Hunter Douglas (Luxaflex) PowerView Gen 1/2 Shade thing-type.hdpowerview.shade.channel.hubRssi.label = Hub RSSI diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/repeater.xml b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/repeater.xml index c415c5140f19b..60babc67de520 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/repeater.xml +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/repeater.xml @@ -13,16 +13,14 @@ - Controls the color of the LED ring - - - Controls the brightness of the LED ring + Controls the color and brightness of the LED ring + 1 Hunter Douglas (Luxaflex) PowerView Repeater diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/update/update.xml b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/update/update.xml index 70a692f2f238d..bf5468878a3ea 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/update/update.xml +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/update/update.xml @@ -3,19 +3,9 @@ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd"> - + - - powerview:shade-position - - - powerview:shade-position - - The secondary vertical position (on top-down/bottom-up shades) - - - powerview:shade-vane - + From 5dff9d20e25cac582f586fa009cf21ab75e43508 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 18 Nov 2023 16:24:10 +0100 Subject: [PATCH 090/146] Fix color state update (#15908) Signed-off-by: Jacob Laursen --- .../internal/handler/HDPowerViewRepeaterHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java index e5d456b4aaeba..a8442a767b6c5 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java @@ -261,7 +261,7 @@ private void updatePropertyAndStates(RepeaterData repeaterData) { // Light is off when RGB black, so discard brightness as otherwise it would appear on. hsb = HSBType.BLACK; } else { - hsb = HSBType.fromRGB(color.red, color.green, color.red); + hsb = HSBType.fromRGB(color.red, color.green, color.blue); hsb = new HSBType(hsb.getHue(), hsb.getSaturation(), new PercentType(color.brightness)); } updateState(CHANNEL_REPEATER_COLOR, hsb); From 5e1eec362b02a8d59e138f1287f8c0fafb9b4b53 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Sat, 18 Nov 2023 16:28:44 +0100 Subject: [PATCH 091/146] =?UTF-8?q?[shelly]=C2=A0Fix=20Gen2=20auth,=20impr?= =?UTF-8?q?oved=20security=20for=20Gen1=20auth,=20improved=20discovery=20(?= =?UTF-8?q?#15898)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Markus Michels --- .../internal/api/ShellyApiException.java | 4 + .../internal/api/ShellyApiInterface.java | 4 +- .../internal/api/ShellyDeviceProfile.java | 48 ++++++------ .../shelly/internal/api/ShellyHttpClient.java | 6 +- .../internal/api1/Shelly1ApiJsonDTO.java | 5 +- .../internal/api1/Shelly1CoapHandler.java | 4 +- .../shelly/internal/api1/Shelly1HttpApi.java | 36 +++++++-- .../internal/api2/Shelly2ApiClient.java | 5 ++ .../internal/api2/Shelly2ApiJsonDTO.java | 7 +- .../shelly/internal/api2/Shelly2ApiRpc.java | 54 ++++++-------- .../shelly/internal/api2/ShellyBluApi.java | 14 ++-- .../discovery/ShellyDiscoveryParticipant.java | 15 +++- .../internal/handler/ShellyBaseHandler.java | 73 ++++++++----------- .../internal/handler/ShellyLightHandler.java | 4 +- .../manager/ShellyManagerActionPage.java | 2 +- .../manager/ShellyManagerOtaPage.java | 5 +- .../manager/ShellyManagerOverviewPage.java | 6 +- .../internal/manager/ShellyManagerPage.java | 3 +- 18 files changed, 164 insertions(+), 131 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java index 845219bf92c8b..6c6dc2a9588de 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java @@ -118,6 +118,10 @@ public boolean isConnectionError() { || exType == NoRouteToHostException.class; } + public boolean isNoRouteToHost() { + return getCauseClass() == NoRouteToHostException.class; + } + public boolean isUnknownHost() { return getCauseClass() == UnknownHostException.class; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java index 8b7716007d83a..1f9443824e41a 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java @@ -15,6 +15,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice; @@ -42,7 +43,8 @@ public interface ShellyApiInterface { ShellySettingsDevice getDeviceInfo() throws ShellyApiException; - ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException; + ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice device) + throws ShellyApiException; ShellySettingsStatus getStatus() throws ShellyApiException; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 814917e26bad7..6f96910b56059 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -24,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsGlobal; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput; @@ -47,24 +48,21 @@ @NonNullByDefault public class ShellyDeviceProfile { private final Logger logger = LoggerFactory.getLogger(ShellyDeviceProfile.class); - private static final Pattern VERSION_PATTERN = Pattern.compile("v\\d+\\.\\d+\\.\\d+(-[a-z0-9]*)?"); + private static final Pattern GEN1_VERSION_PATTERN = Pattern.compile("v\\d+\\.\\d+\\.\\d+(-[a-z0-9]*)?"); + private static final Pattern GEN2_VERSION_PATTERN = Pattern.compile("\\d+\\.\\d+\\.\\d+(-[a-fh-z0-9]*)?"); public boolean initialized = false; // true when initialized public String thingName = ""; - public String deviceType = ""; public boolean extFeatures = false; public String settingsJson = ""; + public ShellySettingsDevice device = new ShellySettingsDevice(); public ShellySettingsGlobal settings = new ShellySettingsGlobal(); public ShellySettingsStatus status = new ShellySettingsStatus(); - public String hostname = ""; public String name = ""; - public String model = ""; - public String mode = ""; public boolean discoverable = true; - public boolean auth = false; public boolean alwaysOn = true; public boolean isGen2 = false; public boolean isBlu = false; @@ -72,7 +70,6 @@ public class ShellyDeviceProfile { public String hwRev = ""; public String hwBatchId = ""; - public String mac = ""; public String fwVersion = ""; public String fwDate = ""; @@ -118,10 +115,13 @@ public class ShellyDeviceProfile { public ShellyDeviceProfile() { } - public ShellyDeviceProfile initialize(String thingType, String jsonIn) throws ShellyApiException { + public ShellyDeviceProfile initialize(String thingType, String jsonIn, @Nullable ShellySettingsDevice device) + throws ShellyApiException { Gson gson = new Gson(); - initialized = false; + if (device != null) { + this.device = device; + } initFromThingType(thingType); @@ -141,36 +141,36 @@ public ShellyDeviceProfile initialize(String thingType, String jsonIn) throws Sh settings = fromJson(gson, json, ShellySettingsGlobal.class); // General settings + if (getString(device.hostname).isEmpty() && !getString(device.mac).isEmpty()) { + device.hostname = device.mac.length() >= 12 ? "shelly-" + device.mac.toUpperCase().substring(6, 11) + : "unknown"; + } name = getString(settings.name); - deviceType = getString(settings.device.type); - mac = getString(settings.device.mac); - hostname = !getString(settings.device.hostname).isEmpty() ? settings.device.hostname.toLowerCase() - : mac.length() >= 12 ? "shelly-" + mac.toUpperCase().substring(6, 11) : "unknown"; - mode = getString(settings.mode).toLowerCase(); hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : ""; hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : ""; - fwDate = substringBefore(settings.fw, "/"); - fwVersion = extractFwVersion(settings.fw); + fwDate = substringBefore(device.fw, "-"); + fwVersion = extractFwVersion(device.fw); ShellyVersionDTO version = new ShellyVersionDTO(); extFeatures = version.compare(fwVersion, SHELLY_API_FW_110) >= 0; discoverable = (settings.discoverable == null) || settings.discoverable; + String mode = getString(device.mode); isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER); inColor = isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR); - numRelays = !isLight ? getInteger(settings.device.numOutputs) : 0; + numRelays = !isLight ? getInteger(device.numOutputs) : 0; if ((numRelays > 0) && (settings.relays == null)) { numRelays = 0; } hasRelays = (numRelays > 0) || isDimmer; - numRollers = getInteger(settings.device.numRollers); + numRollers = getInteger(device.numRollers); numInputs = settings.inputs != null ? settings.inputs.size() : hasRelays ? isRoller ? 2 : 1 : 0; isEMeter = settings.emeters != null; - numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters); + numMeters = !isEMeter ? getInteger(device.numMeters) : getInteger(device.numEMeters); if ((numMeters == 0) && isLight) { // RGBW2 doesn't report, but has one - numMeters = inColor ? 1 : getInteger(settings.device.numOutputs); + numMeters = inColor ? 1 : getInteger(device.numOutputs); } initialized = true; @@ -199,8 +199,9 @@ public void initFromThingType(String name) { isGen2 = isGeneration2(thingType); isBlu = isBluSeries(thingType); // e.g. SBBT for BLU Button - isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2) - || deviceType.equalsIgnoreCase(SHELLYDT_PLUSDIMMERUS) + String type = getString(device.type); + isDimmer = type.equalsIgnoreCase(SHELLYDT_DIMMER) || type.equalsIgnoreCase(SHELLYDT_DIMMER2) + || type.equalsIgnoreCase(SHELLYDT_PLUSDIMMERUS) || thingType.equalsIgnoreCase(THING_TYPE_SHELLYPLUSDIMMERUS_STR); isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR); isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR) @@ -390,7 +391,8 @@ public static String extractFwVersion(@Nullable String version) { .replace("/v1.12-", "/v1.12.0"); // Extract version from string, e.g. 20210226-091047/v1.10.0-rc2-89-g623b41ec0-master - Matcher matcher = VERSION_PATTERN.matcher(vers); + Matcher matcher = version.startsWith("v") ? GEN1_VERSION_PATTERN.matcher(vers) + : GEN2_VERSION_PATTERN.matcher(vers); if (matcher.find()) { return matcher.group(0); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java index 5b04bc58a604b..7ed4363450bc8 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java @@ -69,6 +69,7 @@ public class ShellyHttpClient { protected int timeoutErrors = 0; protected int timeoutsRecovered = 0; private ShellyDeviceProfile profile; + protected boolean basicAuth = false; public ShellyHttpClient(String thingName, ShellyThingInterface thing) { this(thingName, thing.getThingConfig(), thing.getHttpClient()); @@ -83,9 +84,6 @@ public ShellyHttpClient(String thingName, ShellyThingConfiguration config, HttpC this.httpClient.setConnectTimeout(SHELLY_API_TIMEOUT_MS); } - public void initialize() throws ShellyApiException { - } - public void setConfig(String thingName, ShellyThingConfiguration config) { this.thingName = thingName; this.config = config; @@ -167,7 +165,7 @@ private ShellyApiResult innerRequest(HttpMethod method, String uri, @Nullable Sh authHeader = formatAuthResponse(uri, buildAuthResponse(uri, auth, SHELLY2_AUTHDEF_USER, config.password)); } else { - if (!uri.equals(SHELLYRPC_ENDPOINT)) { + if (basicAuth) { String bearer = config.userId + ":" + config.password; authHeader = HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(bearer.getBytes()); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java index c5304fe4e1ce1..d836e728f2c4d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java @@ -261,6 +261,10 @@ public class Shelly1ApiJsonDTO { public static class ShellySettingsDevice { public String type; + public String mode; // Gen 1 + public String id; // Gen2: service name + public String name; // Gen2: configured device name + public String profile; // Gen 2 public String mac; public String hostname; public String fw; @@ -563,7 +567,6 @@ public static class ShellySettingsUpdate { public static class ShellySettingsGlobal { // https://shelly-api-docs.shelly.cloud/#shelly1pm-settings - public ShellySettingsDevice device = new ShellySettingsDevice(); @SerializedName("wifi_ap") public ShellySettingsWiFiAp wifiAp = new ShellySettingsWiFiAp(); @SerializedName("wifi_sta") diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java index 23c9cdede0164..3de692e224034 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java @@ -182,10 +182,10 @@ public void processResponse(@Nullable Response response) { for (Option opt : options) { if (opt.getNumber() == COIOT_OPTION_GLOBAL_DEVID) { String devid = opt.getStringValue(); - if (devid.contains("#") && profile.mac != null) { + if (devid.contains("#") && profile.device.mac != null) { // Format: ## String macid = substringBetween(devid, "#", "#"); - if (profile.mac.toUpperCase().contains(macid.toUpperCase())) { + if (getString(profile.device.mac).toUpperCase().contains(macid.toUpperCase())) { match = true; break; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java index 69a4d7bd50110..330b2be360618 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java @@ -22,6 +22,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyApiInterface; @@ -79,10 +80,26 @@ public Shelly1HttpApi(String thingName, ShellyThingConfiguration config, HttpCli this.profile = new ShellyDeviceProfile(); } + @Override + public void initialize() throws ShellyApiException { + profile.device = getDeviceInfo(); + } + @Override public ShellySettingsDevice getDeviceInfo() throws ShellyApiException { ShellySettingsDevice info = callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class); info.gen = 1; + basicAuth = getBool(info.auth); + + if (getString(info.mode).isEmpty()) { // older Gen1 Firmware + if (getInteger(info.numRollers) > 0) { + info.mode = SHELLY_CLASS_ROLLER; + } else if (getInteger(info.numOutputs) > 0) { + info.mode = SHELLY_CLASS_RELAY; + } else { + info.mode = ""; + } + } return info; } @@ -104,7 +121,14 @@ public String getDebugLog(String id) throws ShellyApiException { * @throws ShellyApiException */ @Override - public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { + public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice device) + throws ShellyApiException { + if (device != null) { + profile.device = device; + } + if (profile.device.type == null) { + profile.device = getDeviceInfo(); + } String json = httpRequest(SHELLY_URL_SETTINGS); if (json.contains("\"type\":\"SHDM-")) { logger.trace("{}: Detected a Shelly Dimmer: fix Json (replace lights[] tag with dimmers[]", thingName); @@ -112,10 +136,10 @@ public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiEx } // Map settings to device profile for Light and Sense - profile.initialize(thingType, json); + profile.initialize(thingType, json, profile.device); // 2nd level initialization - profile.thingName = profile.hostname; + profile.thingName = profile.device.hostname; if (profile.isLight && (profile.numMeters == 0)) { logger.debug("{}: Get number of meters from light status", thingName); ShellyStatusLight status = getLightStatus(); @@ -396,10 +420,10 @@ public String setCloud(boolean enabled) throws ShellyApiException { */ @Override public void setLightMode(String mode) throws ShellyApiException { - if (!mode.isEmpty() && !profile.mode.equals(mode)) { + if (!mode.isEmpty() && !profile.device.mode.equals(mode)) { setLightSetting(SHELLY_API_MODE, mode); - profile.mode = mode; - profile.inColor = profile.isLight && profile.mode.equalsIgnoreCase(SHELLY_MODE_COLOR); + profile.device.mode = mode; + profile.inColor = profile.isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR); } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java index a8c771fe67ff6..2805d1310107f 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java @@ -148,6 +148,11 @@ public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpC MAP_ROLLER_STATE.put(SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP); MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only } + protected static final Map MAP_PROFILE = new HashMap<>(); + static { + MAP_PROFILE.put(SHELLY_CLASS_RELAY, SHELLY2_PROFILE_RELAY); + MAP_PROFILE.put(SHELLY_CLASS_ROLLER, SHELLY2_PROFILE_COVER); + } protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java index 672f9e3b8cc96..ea309929f51b2 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java @@ -82,7 +82,7 @@ public class Shelly2ApiJsonDTO { // Component types public static final String SHELLY2_PROFILE_RELAY = "switch"; - public static final String SHELLY2_PROFILE_ROLLER = "cover"; + public static final String SHELLY2_PROFILE_COVER = "cover"; // Button types/modes public static final String SHELLY2_BTNT_MOMENTARY = "momentary"; @@ -183,13 +183,14 @@ public static class Shelly2DeviceSettings { public String id; public String mac; public String model; + public String profile; public Integer gen; @SerializedName("fw_id") - public String firmware; + public String fw; public String ver; public String app; @SerializedName("auth_en") - public Boolean authEnable; + public Boolean auth; @SerializedName("auth_domain") public String authDomain; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java index d35273bcccccb..17efc1d84ebfa 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java @@ -113,11 +113,6 @@ public Shelly2ApiRpc(String thingName, ShellyThingTable thingTable, ShellyThingI this.thingName = thingName; this.thing = thing; this.thingTable = thingTable; - try { - getProfile().initFromThingType(thing.getThingType()); - } catch (ShellyApiException e) { - logger.info("{}: Shelly2 API initialization failed!", thingName, e); - } } /** @@ -161,9 +156,17 @@ public void startScan() { @SuppressWarnings("null") @Override - public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { + public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice devInfo) + throws ShellyApiException { ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile(); + if (devInfo != null) { + profile.device = devInfo; + } + if (profile.device.type == null) { + profile.device = getDeviceInfo(); + } + Shelly2GetConfigResult dc = apiRequest(SHELLYRPC_METHOD_GETCONFIG, null, Shelly2GetConfigResult.class); profile.isGen2 = true; profile.settingsJson = gson.toJson(dc); @@ -195,29 +198,18 @@ public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiEx profile.numRelays = profile.settings.relays != null ? profile.settings.relays.size() : 0; profile.numRollers = profile.settings.rollers != null ? profile.settings.rollers.size() : 0; profile.hasRelays = profile.numRelays > 0 || profile.numRollers > 0; - profile.mode = ""; - if (profile.hasRelays) { - profile.mode = profile.isRoller ? SHELLY_CLASS_ROLLER : SHELLY_CLASS_RELAY; - } - - ShellySettingsDevice device = getDeviceInfo(); - profile.settings.device = device; - if (!getString(device.fw).isEmpty()) { - profile.fwDate = substringBefore(device.fw, "/"); - profile.fwVersion = profile.status.update.oldVersion = "v" + substringAfter(device.fw, "/"); + if (getString(profile.device.mode).isEmpty() && profile.hasRelays) { + profile.device.mode = profile.isRoller ? SHELLY_CLASS_ROLLER : SHELLY_CLASS_RELAY; } - profile.hostname = device.hostname; - profile.deviceType = device.type; - profile.mac = device.mac; - profile.auth = device.auth; + ShellySettingsDevice device = profile.device; profile.isGen2 = device.gen == 2; if (config.serviceName.isEmpty()) { - config.serviceName = getString(profile.hostname); + config.serviceName = getString(profile.device.hostname); } - profile.fwDate = substringBefore(device.fw, "/"); - profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-"); - profile.status.update.oldVersion = profile.fwVersion; + profile.settings.fw = device.fw; + profile.fwDate = substringBefore(substringBefore(device.fw, "/"), "-"); + profile.fwVersion = profile.status.update.oldVersion = ShellyDeviceProfile.extractFwVersion(device.fw); profile.status.hasUpdate = profile.status.update.hasUpdate = false; if (dc.eth != null) { @@ -741,11 +733,13 @@ public ShellySettingsDevice getDeviceInfo() throws ShellyApiException { Shelly2DeviceSettings device = callApi("/shelly", Shelly2DeviceSettings.class); ShellySettingsDevice info = new ShellySettingsDevice(); info.hostname = getString(device.id); - info.fw = getString(device.firmware); + info.name = getString(device.name); + info.fw = getString(device.fw); info.type = getString(device.model); info.mac = getString(device.mac); - info.auth = getBool(device.authEnable); + info.auth = getBool(device.auth); info.gen = getInteger(device.gen); + info.mode = mapValue(MAP_PROFILE, device.profile); return info; } @@ -770,16 +764,16 @@ public ShellySettingsStatus getStatus() throws ShellyApiException { profile.settings.sleepMode.period = ds.sys.wakeupPeriod / 60; } - status.hasUpdate = status.update.hasUpdate = false; - status.update.oldVersion = getProfile().fwVersion; if (ds.sys.availableUpdates != null) { status.update.hasUpdate = ds.sys.availableUpdates.stable != null; if (ds.sys.availableUpdates.stable != null) { - status.update.newVersion = "v" + getString(ds.sys.availableUpdates.stable.version); + status.update.newVersion = ShellyDeviceProfile + .extractFwVersion(getString(ds.sys.availableUpdates.stable.version)); status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.newVersion) < 0; } if (ds.sys.availableUpdates.beta != null) { - status.update.betaVersion = "v" + getString(ds.sys.availableUpdates.beta.version); + status.update.betaVersion = ShellyDeviceProfile + .extractFwVersion(getString(ds.sys.availableUpdates.beta.version)); status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.betaVersion) < 0; } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java index 533d4448e8940..5eabce2a73d53 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState; @@ -119,9 +120,13 @@ public ShellySettingsDevice getDeviceInfo() throws ShellyApiException { } @Override - public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { + public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice devInfo) + throws ShellyApiException { ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile(); + if (devInfo != null) { + profile.device = devInfo; + } profile.isBlu = true; profile.settingsJson = "{}"; profile.thingName = thingName; @@ -131,13 +136,8 @@ public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiEx } ShellySettingsDevice device = getDeviceInfo(); - profile.settings.device = device; - profile.hostname = device.hostname; - profile.deviceType = device.type; - profile.mac = device.mac; - profile.auth = device.auth; if (config.serviceName.isEmpty()) { - config.serviceName = getString(profile.hostname); + config.serviceName = getString(profile.device.hostname); } profile.fwDate = substringBefore(device.fw, "/"); profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-"); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java index 967a658641949..793b710f7db9e 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java @@ -31,6 +31,7 @@ import org.openhab.binding.shelly.internal.api.ShellyApiInterface; import org.openhab.binding.shelly.internal.api.ShellyApiResult; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice; import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi; import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc; import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration; @@ -144,17 +145,23 @@ public DiscoveryResult createResult(final ServiceInfo service) { boolean gen2 = "2".equals(service.getPropertyString("gen")); ShellyApiInterface api = null; + ShellySettingsDevice devInfo; try { api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) : new Shelly1HttpApi(name, config, httpClient); api.initialize(); - profile = api.getDeviceProfile(thingType); + devInfo = api.getDeviceInfo(); + model = devInfo.type; + if (devInfo.name != null) { + deviceName = devInfo.name; + } + profile = api.getDeviceProfile(thingType, devInfo); + api.close(); logger.debug("{}: Shelly settings : {}", name, profile.settingsJson); deviceName = profile.name; - model = profile.deviceType; - mode = profile.mode; + mode = devInfo.mode; properties = ShellyBaseHandler.fillDeviceProperties(profile); logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType, - profile.deviceType, mode.isEmpty() ? "" : mode, deviceName); + devInfo.type, mode.isEmpty() ? "" : mode, deviceName); // get thing type from device name thingUID = ShellyThingCreator.getThingUID(name, model, mode, false); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index c76a181e45876..1a35a432b1afc 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -273,45 +273,41 @@ public boolean initializeThing() throws ShellyApiException { updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING, messages.get("status.unknown.initializing")); - profile.initFromThingType(thingType); // do some basic initialization - // Gen 1 only: Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing // could not be fully initialized here. In this case the CoAP messages triggers auto-initialization (like the // Action URL does when enabled) + profile.initFromThingType(thingType); if (coap != null && config.eventsCoIoT && !profile.alwaysOn) { coap.start(thingName, config); } // Initialize API access, exceptions will be catched by initialize() api.initialize(); - ShellySettingsDevice devInfo = api.getDeviceInfo(); - if (getBool(devInfo.auth) && config.password.isEmpty()) { + ShellySettingsDevice device = profile.device = api.getDeviceInfo(); + if (getBool(device.auth) && config.password.isEmpty()) { setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials"); return false; } if (config.serviceName.isEmpty()) { - config.serviceName = getString(profile.hostname).toLowerCase(); + config.serviceName = getString(device.hostname).toLowerCase(); } api.setConfig(thingName, config); - ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType); - tmpPrf.isGen2 = gen2; - tmpPrf.auth = devInfo.auth; // missing in /settings - + ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType, profile.device); + String mode = getString(tmpPrf.device.mode); if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) { - changeThingType(thingName, tmpPrf.mode); + changeThingType(thingName, mode); return false; // force re-initialization } // Validate device mode String reqMode = thingType.contains("-") ? substringAfter(thingType, "-") : ""; - if (!reqMode.isEmpty() && !tmpPrf.mode.equals(reqMode)) { - setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode", tmpPrf.mode, - reqMode); + if (!reqMode.isEmpty() && !mode.equals(reqMode)) { + setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode", mode, reqMode); return false; } - if (!getString(devInfo.coiot).isEmpty()) { + if (!getString(tmpPrf.device.coiot).isEmpty()) { // New Shelly devices might use a different endpoint for the CoAP listener - tmpPrf.coiotEndpoint = devInfo.coiot; + tmpPrf.coiotEndpoint = tmpPrf.device.coiot; } if (tmpPrf.settings.sleepMode != null && !tmpPrf.isTRV) { // Sensor, usually 12h, H&T in USB mode 10min @@ -566,13 +562,10 @@ protected void refreshStatus() { status = "offline.conf-error-access-denied"; } else if (isWatchdogStarted()) { if (!isWatchdogExpired()) { + logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); if (profile.alwaysOn) { // suppress for battery powered sensors logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); } - } else { - if (isThingOnline()) { - status = "offline.status-error-watchdog"; - } } } else if (e.isJSONException()) { status = "offline.status-error-unexpected-api-result"; @@ -606,22 +599,18 @@ protected void refreshStatus() { private void showThingConfig(ShellyDeviceProfile profile) { logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName, - profile.hostname, profile.deviceType, profile.hwRev, profile.hwBatchId, profile.fwVersion, + profile.device.hostname, profile.device.type, profile.hwRev, profile.hwBatchId, profile.fwVersion, profile.fwDate); - logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.hostname, profile.settingsJson); - logger.debug( - """ - {}: Device \ - hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{}), ext. Switch Add-On: {}\ - ,isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}, BLU Gateway support: {}\ - ,alwaysOn:{}, updatePeriod:{}sec\ - """, - thingName, profile.hasRelays, profile.numRelays, profile.isRoller, profile.numRollers, profile.isDimmer, - profile.numMeters, profile.isEMeter, profile.settings.extSwitch != null ? "installed" : "n/a", - profile.isSensor, profile.isDW, profile.hasBattery, - profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", profile.isSense, - profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2, profile.inColor, - profile.alwaysOn, profile.updatePeriod, config.enableBluGateway); + logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.device.hostname, profile.settingsJson); + logger.debug("{}: Device " + + "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{}), ext. Switch Add-On: {}" + + ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}, BLU Gateway support: {}" + + ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller, + profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter, + profile.settings.extSwitch != null ? "installed" : "n/a", profile.isSensor, profile.isDW, + profile.hasBattery, profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", + profile.isSense, profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2, + profile.inColor, profile.alwaysOn, profile.updatePeriod, config.enableBluGateway); if (profile.status.extTemperature != null || profile.status.extHumidity != null || profile.status.extVoltage != null || profile.status.extAnalogInput != null) { logger.debug("{}: Shelly Add-On detected with at least 1 external sensor", thingName); @@ -1059,15 +1048,15 @@ private void checkVersion(ShellyDeviceProfile prf, ShellySettingsStatus status) // no fw version available (e.g. BLU device) return; } - ShellyVersionDTO version = new ShellyVersionDTO(); if (version.checkBeta(getString(prf.fwVersion))) { - logger.info("{}: {}", prf.hostname, messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); + logger.info("{}: {}", prf.device.hostname, + messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); } else { String minVersion = !gen2 ? SHELLY_API_MIN_FWVERSION : SHELLY2_API_MIN_FWVERSION; if (version.compare(prf.fwVersion, minVersion) < 0) { - logger.warn("{}: {}", prf.hostname, - messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate, minVersion)); + logger.warn("{}: {}", prf.device.hostname, + messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); } } if (!gen2 && bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0) @@ -1450,10 +1439,10 @@ public static Map fillDeviceProperties(ShellyDeviceProfile profi Map properties = new TreeMap<>(); properties.put(PROPERTY_VENDOR, VENDOR); if (profile.isInitialized()) { - properties.put(PROPERTY_MODEL_ID, getString(profile.settings.device.type)); - properties.put(PROPERTY_MAC_ADDRESS, profile.mac); + properties.put(PROPERTY_MODEL_ID, getString(profile.device.type)); + properties.put(PROPERTY_MAC_ADDRESS, profile.device.mac); properties.put(PROPERTY_FIRMWARE_VERSION, profile.fwVersion + "/" + profile.fwDate); - properties.put(PROPERTY_DEV_MODE, profile.mode); + properties.put(PROPERTY_DEV_MODE, profile.device.mode); if (profile.hasRelays) { properties.put(PROPERTY_NUM_RELAYS, String.valueOf(profile.numRelays)); properties.put(PROPERTY_NUM_ROLLERS, String.valueOf(profile.numRollers)); @@ -1481,7 +1470,7 @@ public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiExce try { refreshSettings |= forceRefresh; if (refreshSettings) { - profile = api.getDeviceProfile(thingType); + profile = api.getDeviceProfile(thingType, null); if (!isThingOnline()) { logger.debug("{}: Device profile re-initialized (thingType={})", thingName, thingType); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java index 8fc49526f68b0..b9a901612bb6d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java @@ -82,7 +82,7 @@ public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throw try { ShellyColorUtils oldCol = getCurrentColors(lightId); - oldCol.mode = profile.mode; + oldCol.mode = profile.device.mode; ShellyColorUtils col = new ShellyColorUtils(oldCol); boolean update = true; @@ -317,7 +317,7 @@ public boolean updateDeviceStatus(ShellySettingsStatus genericStatus) throws She } ShellyStatusLight status = api.getLightStatus(); - logger.trace("{}: Updating light status in {} mode, {} channel(s)", thingName, profile.mode, + logger.trace("{}: Updating light status in {} mode, {} channel(s)", thingName, profile.device.mode, status.lights.size()); // In white mode we have multiple channels diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java index ef47191441e1c..6f6abdbd0fa7d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java @@ -377,7 +377,7 @@ public static Map getActions(ShellyDeviceProfile profile) { list.put(ACTION_RES_STATS, "Reset Statistics"); list.put(ACTION_RESTART, "Reboot Device"); - if (gen2) { + if (!gen2 || !profile.isBlu) { list.put(ACTION_PROTECT, "Protect Device"); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java index ed7efa891cbdb..d4ce439f1dcf0 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java @@ -87,7 +87,7 @@ public ShellyMgrResponse generatePage(String path, Map paramet String deviceType = getDeviceType(properties); String uri = !url.isEmpty() && connection.equals(CONNECTION_TYPE_CUSTOM) ? url - : getFirmwareUrl(config.deviceIp, deviceType, profile.mode, version, + : getFirmwareUrl(config.deviceIp, deviceType, profile.device.mode, version, connection.equals(CONNECTION_TYPE_LOCAL)); if (connection.equalsIgnoreCase(CONNECTION_TYPE_INTERNET)) { // If target @@ -100,7 +100,8 @@ public ShellyMgrResponse generatePage(String path, Map paramet } } else if (connection.equalsIgnoreCase(CONNECTION_TYPE_LOCAL)) { // redirect to local server -> http://:/shelly/manager/ota?deviceType=xxx&version=xxx - String modeParm = !profile.mode.isEmpty() ? "&" + URLPARM_DEVMODE + "=" + profile.mode : ""; + String modeParm = !profile.device.mode.isEmpty() ? "&" + URLPARM_DEVMODE + "=" + profile.device.mode + : ""; url = URLPARM_URL + "=http://" + localIp + ":" + localPort + SHELLY_MGR_OTA_URI + urlEncode( "?" + URLPARM_DEVTYPE + "=" + deviceType + modeParm + "&" + URLPARM_VERSION + "=" + version); } else if (connection.equalsIgnoreCase(CONNECTION_TYPE_CUSTOM)) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java index ddc696e80543a..7c0bdd0218d84 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java @@ -143,7 +143,7 @@ private String fillFirmwareHtml(ShellyDeviceProfile profile, String uid, String try { if (!profile.isGen2) { // currently there is no public firmware repo for Gen2 logger.debug("{}: Load firmware version list for device type {}", LOG_PREFIX, deviceType); - FwRepoEntry fw = getFirmwareRepoEntry(deviceType, profile.mode); + FwRepoEntry fw = getFirmwareRepoEntry(deviceType, profile.device.mode); pVersion = extractFwVersion(fw.version); bVersion = extractFwVersion(fw.betaVer); @@ -238,7 +238,9 @@ private boolean applyFilter(ShellyManagerInterface handler, String filter) { // return handler.getChannelValue(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE) == OnOffType.ON; return getBool(profile.status.hasUpdate); case FILTER_UNPROTECTED: - return !profile.auth; + if (profile.device.auth != null) { + return !profile.device.auth; + } case "*": default: return true; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java index a70b4472d9f8d..5d7e46bfa1dbf 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java @@ -254,7 +254,8 @@ protected Map fillProperties(Map properties, Str properties.put(ATTRIBUTE_APR_TRESHOLD, profile.settings.apRoaming != null ? getOption(profile.settings.apRoaming.threshold) : "n/a"); properties.put(ATTRIBUTE_PWD_PROTECT, - profile.auth ? "enabled, user=" + getString(profile.settings.login.username) : "disabled"); + getBool(profile.device.auth) ? "enabled, user=" + getString(profile.settings.login.username) + : "disabled"); String tz = getString(profile.settings.timezone); properties.put(ATTRIBUTE_TIMEZONE, (tz.isEmpty() ? "n/a" : tz) + ", auto-detect: " + getBool(profile.settings.tzautodetect)); From 10094fd114f77a8d0c6be7ea096c1a93766d4dbd Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Sat, 18 Nov 2023 11:30:48 -0800 Subject: [PATCH 092/146] [myq] Remove MyQ Binding (#15911) Fixes #15910 Signed-off-by: Dan Cunningham --- CODEOWNERS | 1 - bom/openhab-addons/pom.xml | 5 - bundles/org.openhab.binding.myq/NOTICE | 20 - bundles/org.openhab.binding.myq/README.md | 72 -- bundles/org.openhab.binding.myq/pom.xml | 25 - .../src/main/feature/feature.xml | 10 - .../myq/internal/MyQBindingConstants.java | 38 - .../myq/internal/MyQDiscoveryService.java | 103 --- .../myq/internal/MyQHandlerFactory.java | 77 -- .../config/MyQAccountConfiguration.java | 27 - .../config/MyQDeviceConfiguration.java | 25 - .../binding/myq/internal/dto/AccountDTO.java | 24 - .../binding/myq/internal/dto/AccountsDTO.java | 24 - .../binding/myq/internal/dto/DeviceDTO.java | 32 - .../myq/internal/dto/DeviceStateDTO.java | 36 - .../binding/myq/internal/dto/DevicesDTO.java | 26 - .../internal/handler/MyQAccountHandler.java | 675 ------------------ .../internal/handler/MyQDeviceHandler.java | 28 - .../handler/MyQGarageDoorHandler.java | 133 ---- .../myq/internal/handler/MyQLampHandler.java | 97 --- .../src/main/resources/OH-INF/addon/addon.xml | 11 - .../main/resources/OH-INF/config/config.xml | 37 - .../main/resources/OH-INF/i18n/myq.properties | 42 -- .../resources/OH-INF/thing/thing-types.xml | 79 -- bundles/pom.xml | 1 - 25 files changed, 1648 deletions(-) delete mode 100644 bundles/org.openhab.binding.myq/NOTICE delete mode 100644 bundles/org.openhab.binding.myq/README.md delete mode 100644 bundles/org.openhab.binding.myq/pom.xml delete mode 100644 bundles/org.openhab.binding.myq/src/main/feature/feature.xml delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQBindingConstants.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQDiscoveryService.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQHandlerFactory.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQAccountConfiguration.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQDeviceConfiguration.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountDTO.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountsDTO.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceDTO.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceStateDTO.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DevicesDTO.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQAccountHandler.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQDeviceHandler.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQGarageDoorHandler.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQLampHandler.java delete mode 100644 bundles/org.openhab.binding.myq/src/main/resources/OH-INF/addon/addon.xml delete mode 100644 bundles/org.openhab.binding.myq/src/main/resources/OH-INF/config/config.xml delete mode 100644 bundles/org.openhab.binding.myq/src/main/resources/OH-INF/i18n/myq.properties delete mode 100644 bundles/org.openhab.binding.myq/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 04e86052e8046..026cd04a6134a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -227,7 +227,6 @@ /bundles/org.openhab.binding.mycroft/ @dalgwen /bundles/org.openhab.binding.mybmw/ @weymann @ntruchsess /bundles/org.openhab.binding.mynice/ @clinique -/bundles/org.openhab.binding.myq/ @digitaldan /bundles/org.openhab.binding.mystrom/ @pail23 /bundles/org.openhab.binding.nanoleaf/ @stefan-hoehn /bundles/org.openhab.binding.neato/ @jjlauterbach diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 879b8c19fe96c..662e4ab204904 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1121,11 +1121,6 @@ org.openhab.binding.mynice ${project.version} - - org.openhab.addons.bundles - org.openhab.binding.myq - ${project.version} - org.openhab.addons.bundles org.openhab.binding.mystrom diff --git a/bundles/org.openhab.binding.myq/NOTICE b/bundles/org.openhab.binding.myq/NOTICE deleted file mode 100644 index 0ca708bef198a..0000000000000 --- a/bundles/org.openhab.binding.myq/NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -This content is produced and maintained by the openHAB project. - -* Project home: https://www.openhab.org - -== Declared Project Licenses - -This program and the accompanying materials are made available under the terms -of the Eclipse Public License 2.0 which is available at -https://www.eclipse.org/legal/epl-2.0/. - -== Source Code - -https://github.com/openhab/openhab-addons - -== Third-party Content - -jsoup -* License: MIT License -* Project: https://jsoup.org/ -* Source: https://github.com/jhy/jsoup diff --git a/bundles/org.openhab.binding.myq/README.md b/bundles/org.openhab.binding.myq/README.md deleted file mode 100644 index 510285cfa6ce8..0000000000000 --- a/bundles/org.openhab.binding.myq/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# MyQ Binding - -This binding integrates with the [The Chamberlain Group MyQ](https://www.myq.com) cloud service. It allows monitoring and control over [MyQ](https://www.myq.com) enabled garage doors manufactured by LiftMaster, Chamberlain and Craftsman. - -## Supported Things - -### Account - -This represents the MyQ cloud account and uses the same credentials needed when using the MyQ mobile application. - -ThingTypeUID: `account` - -### Garage Door - -This represents a garage door associated with an account. Multiple garage doors are supported. - -ThingTypeUID: `garagedoor` - -### Lamp - -This represents a lamp associated with an account. Multiple lamps are supported. - -ThingTypeUID: `lamp` - -## Discovery - -Once an account has been added, garage doors and lamps will automatically be discovered and added to the inbox. - -## Channels - -| Channel | Item Type | Thing Type | States | -|---------------|---------------|------------------|--------------------------------------------------------| -| status | String | garagedoor | opening, closed, closing, stopped, transition, unknown | -| rollershutter | Rollershutter | garagedoor | UP, DOWN, 0%, 100% | -| closeError | Switch | garagedoor | ON (has error), OFF (doesn't have error) | -| openError | Switch | garagedoor | ON (has error), OFF (doesn't have error) | -| switch | Switch | garagedoor, lamp | ON (open), OFF (closed) | - -## Full Example - -### Thing Configuration - -```java -Bridge myq:account:home "MyQ Account" [ username="foo@bar.com", password="secret", refreshInterval=60 ] { - Thing garagedoor abcd12345 "MyQ Garage Door" [ serialNumber="abcd12345" ] - Thing lamp efgh6789 "MyQ Lamp" [ serialNumber="efgh6789" ] -} -``` - -### Items - -```java -String MyQGarageDoor1Status "Door Status [%s]" {channel = "myq:garagedoor:home:abcd12345:status"} -Switch MyQGarageDoor1Switch "Door Switch [%s]" {channel = "myq:garagedoor:home:abcd12345:switch"} -Switch MyQGarageDoor1CloseError "Door Close Error [%s]" {channel = "myq:garagedoor:home:abcd12345:closeError"} -Switch MyQGarageDoor1OpenError "Door OpenError [%s]" {channel = "myq:garagedoor:home:abcd12345:openError"} -Rollershutter MyQGarageDoor1Rollershutter "Door Rollershutter [%s]" {channel = "myq:garagedoor:home:abcd12345:rollershutter"} -Switch MyQGarageDoorLamp "Lamp [%s]" {channel = "myq:lamp:home:efgh6789:switch"} -} -``` - -### Sitemaps - -```perl -sitemap MyQ label="MyQ Demo Sitemap" { - Frame label="Garage Door" { - String item=MyQGarageDoor1Status - Switch item=MyQGarageDoor1Switch - Rollershutter item=MyQGarageDoor1Rollershutter - } -} -``` diff --git a/bundles/org.openhab.binding.myq/pom.xml b/bundles/org.openhab.binding.myq/pom.xml deleted file mode 100644 index 7a776f7f3bb8c..0000000000000 --- a/bundles/org.openhab.binding.myq/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - 4.0.0 - - - org.openhab.addons.bundles - org.openhab.addons.reactor.bundles - 4.1.0-SNAPSHOT - - - org.openhab.binding.myq - - openHAB Add-ons :: Bundles :: MyQ Binding - - - - org.jsoup - jsoup - 1.14.3 - provided - - - diff --git a/bundles/org.openhab.binding.myq/src/main/feature/feature.xml b/bundles/org.openhab.binding.myq/src/main/feature/feature.xml deleted file mode 100644 index 0c938aa7afec7..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/feature/feature.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - - - openhab-runtime-base - mvn:org.jsoup/jsoup/1.14.3 - mvn:org.openhab.addons.bundles/org.openhab.binding.myq/${project.version} - - diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQBindingConstants.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQBindingConstants.java deleted file mode 100644 index fc31bb39a2847..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQBindingConstants.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal; - -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.ThingTypeUID; - -/** - * The {@link MyQBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQBindingConstants { - - public static final String BINDING_ID = "myq"; - - public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account"); - public static final ThingTypeUID THING_TYPE_GARAGEDOOR = new ThingTypeUID(BINDING_ID, "garagedoor"); - public static final ThingTypeUID THING_TYPE_LAMP = new ThingTypeUID(BINDING_ID, "lamp"); - public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_GARAGEDOOR, - THING_TYPE_LAMP); - public static final Set SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set.of(THING_TYPE_GARAGEDOOR, - THING_TYPE_LAMP); -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQDiscoveryService.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQDiscoveryService.java deleted file mode 100644 index 48863f22893ca..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQDiscoveryService.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal; - -import static org.openhab.binding.myq.internal.MyQBindingConstants.BINDING_ID; - -import java.util.List; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.binding.myq.internal.handler.MyQAccountHandler; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.config.discovery.DiscoveryService; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerService; - -/** - * The {@link MyQDiscoveryService} is responsible for discovering MyQ things - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService { - - private static final Set SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set - .of(MyQBindingConstants.THING_TYPE_GARAGEDOOR, MyQBindingConstants.THING_TYPE_LAMP); - private @Nullable MyQAccountHandler accountHandler; - - public MyQDiscoveryService() { - super(SUPPORTED_DISCOVERY_THING_TYPES_UIDS, 1, true); - } - - @Override - public Set getSupportedThingTypes() { - return SUPPORTED_DISCOVERY_THING_TYPES_UIDS; - } - - @Override - public void startScan() { - MyQAccountHandler accountHandler = this.accountHandler; - if (accountHandler != null) { - List devices = accountHandler.devicesCache(); - if (devices != null) { - devices.forEach(device -> { - ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, device.deviceFamily); - if (SUPPORTED_DISCOVERY_THING_TYPES_UIDS.contains(thingTypeUID)) { - ThingUID thingUID = new ThingUID(thingTypeUID, accountHandler.getThing().getUID(), - device.serialNumber.toLowerCase()); - DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel("MyQ " + device.name) - .withProperty(Thing.PROPERTY_SERIAL_NUMBER, thingUID.getId()) - .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) - .withBridge(accountHandler.getThing().getUID()).build(); - thingDiscovered(result); - } - }); - } - } - } - - @Override - public void startBackgroundDiscovery() { - startScan(); - } - - @Override - public void setThingHandler(ThingHandler handler) { - if (handler instanceof MyQAccountHandler myqAccountHandler) { - accountHandler = myqAccountHandler; - } - } - - @Override - public @Nullable ThingHandler getThingHandler() { - return accountHandler; - } - - @Override - public void activate() { - super.activate(null); - } - - @Override - public void deactivate() { - super.deactivate(); - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQHandlerFactory.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQHandlerFactory.java deleted file mode 100644 index 475c9fc5f15b8..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQHandlerFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal; - -import static org.openhab.binding.myq.internal.MyQBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.myq.internal.handler.MyQAccountHandler; -import org.openhab.binding.myq.internal.handler.MyQGarageDoorHandler; -import org.openhab.binding.myq.internal.handler.MyQLampHandler; -import org.openhab.core.auth.client.oauth2.OAuthFactory; -import org.openhab.core.io.net.http.HttpClientFactory; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.BaseThingHandlerFactory; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; - -/** - * The {@link MyQHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -@Component(configurationPid = "binding.myq", service = ThingHandlerFactory.class) -public class MyQHandlerFactory extends BaseThingHandlerFactory { - private final HttpClient httpClient; - private OAuthFactory oAuthFactory; - - @Activate - public MyQHandlerFactory(final @Reference HttpClientFactory httpClientFactory, - final @Reference OAuthFactory oAuthFactory) { - this.httpClient = httpClientFactory.getCommonHttpClient(); - this.oAuthFactory = oAuthFactory; - } - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) { - return new MyQAccountHandler((Bridge) thing, httpClient, oAuthFactory); - } - - if (THING_TYPE_GARAGEDOOR.equals(thingTypeUID)) { - return new MyQGarageDoorHandler(thing); - } - - if (THING_TYPE_LAMP.equals(thingTypeUID)) { - return new MyQLampHandler(thing); - } - - return null; - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQAccountConfiguration.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQAccountConfiguration.java deleted file mode 100644 index 245261f6a3897..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQAccountConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.config; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link MyQAccountConfiguration} class contains fields mapping thing configuration parameters. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQAccountConfiguration { - public String username = ""; - public String password = ""; - public Integer refreshInterval = 60; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQDeviceConfiguration.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQDeviceConfiguration.java deleted file mode 100644 index 1a5ed875762c8..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQDeviceConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.config; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link MyQDeviceConfiguration} class contains fields mapping thing configuration parameters. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQDeviceConfiguration { - public String serialNumber = ""; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountDTO.java deleted file mode 100644 index 7a74a3968e665..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.dto; - -/** - * The {@link AccountDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class AccountDTO { - public String id; - public String name; - public String createdBy; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountsDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountsDTO.java deleted file mode 100644 index 6346476bdf083..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountsDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.dto; - -import java.util.List; - -/** - * The {@link AccountsDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class AccountsDTO { - public List accounts; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceDTO.java deleted file mode 100644 index 96b7749113fc5..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.dto; - -/** - * The {@link DeviceDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class DeviceDTO { - public String href; - public String serialNumber; - public String deviceFamily; - public String devicePlatform; - public String deviceType; - public String name; - public String createdDate; - public String accountId; - public DeviceStateDTO state; - public String parentDevice; - public String parentDeviceId; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceStateDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceStateDTO.java deleted file mode 100644 index 02d306e1acf04..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceStateDTO.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.dto; - -/** - * The {@link DeviceStateDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class DeviceStateDTO { - - public Boolean gdoLockConnected; - public Boolean attachedWorkLightErrorPresent; - public String learnStatus; - public Boolean hasCamera; - public String lampState; - public String batteryBackupState; - public String doorState; - public String lastUpdate; - public Boolean isUnattendedOpenAllowed; - public Boolean isUnattendedCloseAllowed; - public Integer serviceCycleCount; - public Integer absoluteCycleCount; - public Boolean online; - public String lastStatus; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DevicesDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DevicesDTO.java deleted file mode 100644 index 9f1e99789e414..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DevicesDTO.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.dto; - -import java.util.List; - -/** - * The {@link DevicesDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class DevicesDTO { - public String href; - public Integer count; - public List items; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQAccountHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQAccountHandler.java deleted file mode 100644 index 1911c73375685..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQAccountHandler.java +++ /dev/null @@ -1,675 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.handler; - -import static org.openhab.binding.myq.internal.MyQBindingConstants.*; - -import java.io.IOException; -import java.net.CookieStore; -import java.net.HttpCookie; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpContentResponse; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.FormContentProvider; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.util.Fields; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.openhab.binding.myq.internal.MyQDiscoveryService; -import org.openhab.binding.myq.internal.config.MyQAccountConfiguration; -import org.openhab.binding.myq.internal.dto.AccountDTO; -import org.openhab.binding.myq.internal.dto.AccountsDTO; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.binding.myq.internal.dto.DevicesDTO; -import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener; -import org.openhab.core.auth.client.oauth2.AccessTokenResponse; -import org.openhab.core.auth.client.oauth2.OAuthClientService; -import org.openhab.core.auth.client.oauth2.OAuthException; -import org.openhab.core.auth.client.oauth2.OAuthFactory; -import org.openhab.core.auth.client.oauth2.OAuthResponseException; -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.ThingStatusDetail; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.BaseBridgeHandler; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; - -/** - * The {@link MyQAccountHandler} is responsible for communicating with the MyQ API based on an account. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQAccountHandler extends BaseBridgeHandler implements AccessTokenRefreshListener { - private static final int REQUEST_TIMEOUT_MS = 10_000; - - /* - * MyQ oAuth relate fields - */ - private static final String CLIENT_SECRET = "VUQ0RFhuS3lQV3EyNUJTdw=="; - private static final String CLIENT_ID = "ANDROID_CGI_MYQ"; - private static final String REDIRECT_URI = "com.myqops://android"; - private static final String SCOPE = "MyQ_Residential offline_access"; - /* - * MyQ authentication API endpoints - */ - private static final String LOGIN_BASE_URL = "https://partner-identity.myq-cloud.com"; - private static final String LOGIN_AUTHORIZE_URL = LOGIN_BASE_URL + "/connect/authorize"; - private static final String LOGIN_TOKEN_URL = LOGIN_BASE_URL + "/connect/token"; - // this should never happen, but lets be safe and give up after so many redirects - private static final int LOGIN_MAX_REDIRECTS = 30; - /* - * MyQ device and account API endpoints - */ - private static final String ACCOUNTS_URL = "https://accounts.myq-cloud.com/api/v6.0/accounts"; - private static final String DEVICES_URL = "https://devices.myq-cloud.com/api/v5.2/Accounts/%s/Devices"; - private static final String CMD_LAMP_URL = "https://account-devices-lamp.myq-cloud.com/api/v5.2/Accounts/%s/lamps/%s/%s"; - private static final String CMD_DOOR_URL = "https://account-devices-gdo.myq-cloud.com/api/v5.2/Accounts/%s/door_openers/%s/%s"; - - private static final Integer RAPID_REFRESH_SECONDS = 5; - private final Logger logger = LoggerFactory.getLogger(MyQAccountHandler.class); - private final Gson gsonLowerCase = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - private final OAuthFactory oAuthFactory; - private @Nullable Future normalPollFuture; - private @Nullable Future rapidPollFuture; - private @Nullable AccountsDTO accounts; - - private List devicesCache = new ArrayList(); - private @Nullable OAuthClientService oAuthService; - private Integer normalRefreshSeconds = 60; - private HttpClient httpClient; - private String username = ""; - private String password = ""; - private String userAgent = ""; - // force login, even if we have a token - private boolean needsLogin = false; - - public MyQAccountHandler(Bridge bridge, HttpClient httpClient, final OAuthFactory oAuthFactory) { - super(bridge); - this.httpClient = httpClient; - this.oAuthFactory = oAuthFactory; - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - } - - @Override - public void initialize() { - MyQAccountConfiguration config = getConfigAs(MyQAccountConfiguration.class); - normalRefreshSeconds = config.refreshInterval; - username = config.username; - password = config.password; - // MyQ can get picky about blocking user agents apparently - userAgent = ""; // no agent string - needsLogin = true; - updateStatus(ThingStatus.UNKNOWN); - restartPolls(false); - } - - @Override - public void dispose() { - stopPolls(); - OAuthClientService oAuthService = this.oAuthService; - if (oAuthService != null) { - oAuthService.removeAccessTokenRefreshListener(this); - oAuthFactory.ungetOAuthService(getThing().toString()); - this.oAuthService = null; - } - } - - @Override - public void handleRemoval() { - oAuthFactory.deleteServiceAndAccessToken(getThing().toString()); - super.handleRemoval(); - } - - @Override - public Collection> getServices() { - return Set.of(MyQDiscoveryService.class); - } - - @Override - public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { - List localDeviceCaches = devicesCache; - if (childHandler instanceof MyQDeviceHandler deviceHandler) { - localDeviceCaches.stream().filter(d -> deviceHandler.getSerialNumber().equalsIgnoreCase(d.serialNumber)) - .findFirst().ifPresent(deviceHandler::handleDeviceUpdate); - } - } - - @Override - public void onAccessTokenResponse(AccessTokenResponse tokenResponse) { - logger.debug("Auth Token Refreshed, expires in {}", tokenResponse.getExpiresIn()); - } - - /** - * Sends a door action to the MyQ API - * - * @param device - * @param action - */ - public void sendDoorAction(DeviceDTO device, String action) { - sendAction(device, action, CMD_DOOR_URL); - } - - /** - * Sends a lamp action to the MyQ API - * - * @param device - * @param action - */ - public void sendLampAction(DeviceDTO device, String action) { - sendAction(device, action, CMD_LAMP_URL); - } - - private void sendAction(DeviceDTO device, String action, String urlFormat) { - if (getThing().getStatus() != ThingStatus.ONLINE) { - logger.debug("Account offline, ignoring action {}", action); - return; - } - - try { - ContentResponse response = sendRequest( - String.format(urlFormat, device.accountId, device.serialNumber, action), HttpMethod.PUT, null, - null); - if (HttpStatus.isSuccess(response.getStatus())) { - restartPolls(true); - } else { - logger.debug("Failed to send action {} : {}", action, response.getContentAsString()); - } - } catch (InterruptedException | MyQCommunicationException | MyQAuthenticationException e) { - logger.debug("Could not send action", e); - } - } - - /** - * Last known state of MyQ Devices - * - * @return cached MyQ devices - */ - public @Nullable List devicesCache() { - return devicesCache; - } - - private void stopPolls() { - stopNormalPoll(); - stopRapidPoll(); - } - - private synchronized void stopNormalPoll() { - stopFuture(normalPollFuture); - normalPollFuture = null; - } - - private synchronized void stopRapidPoll() { - stopFuture(rapidPollFuture); - rapidPollFuture = null; - } - - private void stopFuture(@Nullable Future future) { - if (future != null) { - future.cancel(true); - } - } - - private synchronized void restartPolls(boolean rapid) { - stopPolls(); - if (rapid) { - normalPollFuture = scheduler.scheduleWithFixedDelay(this::normalPoll, 35, normalRefreshSeconds, - TimeUnit.SECONDS); - rapidPollFuture = scheduler.scheduleWithFixedDelay(this::rapidPoll, 3, RAPID_REFRESH_SECONDS, - TimeUnit.SECONDS); - } else { - normalPollFuture = scheduler.scheduleWithFixedDelay(this::normalPoll, 0, normalRefreshSeconds, - TimeUnit.SECONDS); - } - } - - private void normalPoll() { - stopRapidPoll(); - fetchData(); - } - - private void rapidPoll() { - fetchData(); - } - - private synchronized void fetchData() { - try { - if (accounts == null) { - getAccounts(); - } - getDevices(); - } catch (MyQCommunicationException e) { - logger.debug("MyQ communication error", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } catch (MyQAuthenticationException e) { - logger.debug("MyQ authentication error", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); - stopPolls(); - } catch (InterruptedException e) { - // we were shut down, ignore - } - } - - /** - * This attempts to navigate the MyQ oAuth login flow in order to obtain a @AccessTokenResponse - * - * @return AccessTokenResponse token - * @throws InterruptedException - * @throws MyQCommunicationException - * @throws MyQAuthenticationException - */ - private AccessTokenResponse login() - throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - try { - // make sure we have a fresh session - URI authUri = new URI(LOGIN_BASE_URL); - CookieStore store = httpClient.getCookieStore(); - store.get(authUri).forEach(cookie -> { - store.remove(authUri, cookie); - }); - - String codeVerifier = generateCodeVerifier(); - - ContentResponse loginPageResponse = getLoginPage(codeVerifier); - - // load the login page to get cookies and form parameters - Document loginPage = Jsoup.parse(loginPageResponse.getContentAsString()); - Element form = loginPage.select("form").first(); - Element requestToken = loginPage.select("input[name=__RequestVerificationToken]").first(); - Element returnURL = loginPage.select("input[name=ReturnUrl]").first(); - - if (form == null || requestToken == null) { - throw new MyQCommunicationException("Could not load login page"); - } - - // url that the form will submit to - String action = LOGIN_BASE_URL + form.attr("action"); - - // post our user name and password along with elements from the scraped form - String location = postLogin(action, requestToken.attr("value"), returnURL.attr("value")); - if (location == null) { - throw new MyQAuthenticationException("Could not login with credentials"); - } - - // finally complete the oAuth flow and retrieve a JSON oAuth token response - ContentResponse tokenResponse = getLoginToken(location, codeVerifier); - String loginToken = tokenResponse.getContentAsString(); - - try { - AccessTokenResponse accessTokenResponse = gsonLowerCase.fromJson(loginToken, AccessTokenResponse.class); - if (accessTokenResponse == null) { - throw new MyQAuthenticationException("Could not parse token response"); - } - getOAuthService().importAccessTokenResponse(accessTokenResponse); - return accessTokenResponse; - } catch (JsonSyntaxException e) { - throw new MyQCommunicationException("Invalid Token Response " + loginToken); - } - } catch (IOException | ExecutionException | TimeoutException | OAuthException | URISyntaxException e) { - throw new MyQCommunicationException(e.getMessage()); - } - } - - private void getAccounts() throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - ContentResponse response = sendRequest(ACCOUNTS_URL, HttpMethod.GET, null, null); - accounts = parseResultAndUpdateStatus(response, gsonLowerCase, AccountsDTO.class); - } - - private void getDevices() throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - AccountsDTO localAccounts = accounts; - if (localAccounts == null) { - return; - } - - List currentDevices = new ArrayList(); - - for (AccountDTO account : localAccounts.accounts) { - ContentResponse response = sendRequest(String.format(DEVICES_URL, account.id), HttpMethod.GET, null, null); - DevicesDTO devices = parseResultAndUpdateStatus(response, gsonLowerCase, DevicesDTO.class); - currentDevices.addAll(devices.items); - devices.items.forEach(device -> { - ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, device.deviceFamily); - if (SUPPORTED_DISCOVERY_THING_TYPES_UIDS.contains(thingTypeUID)) { - for (Thing thing : getThing().getThings()) { - ThingHandler handler = thing.getHandler(); - if (handler != null && ((MyQDeviceHandler) handler).getSerialNumber() - .equalsIgnoreCase(device.serialNumber)) { - ((MyQDeviceHandler) handler).handleDeviceUpdate(device); - } - } - } - }); - } - devicesCache = currentDevices; - } - - private synchronized ContentResponse sendRequest(String url, HttpMethod method, @Nullable ContentProvider content, - @Nullable String contentType) - throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - AccessTokenResponse tokenResponse = null; - // if we don't need to force a login, attempt to use the token we have - if (!needsLogin) { - try { - tokenResponse = getOAuthService().getAccessTokenResponse(); - } catch (OAuthException | IOException | OAuthResponseException e) { - // ignore error, will try to login below - logger.debug("Error accessing token, will attempt to login again", e); - } - } - - // if no token, or we need to login, do so now - if (tokenResponse == null) { - tokenResponse = login(); - needsLogin = false; - } - - Request request = httpClient.newRequest(url).method(method).agent(userAgent) - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .header("Authorization", authTokenHeader(tokenResponse)); - if (content != null & contentType != null) { - request = request.content(content, contentType); - } - - // use asyc jetty as the API service will response with a 401 error when credentials are wrong, - // but not a WWW-Authenticate header which causes Jetty to throw a generic execution exception which - // prevents us from knowing the response code - logger.trace("Sending {} to {}", request.getMethod(), request.getURI()); - final CompletableFuture futureResult = new CompletableFuture<>(); - request.send(new BufferingResponseListener() { - @NonNullByDefault({}) - @Override - public void onComplete(Result result) { - Response response = result.getResponse(); - futureResult.complete(new HttpContentResponse(response, getContent(), getMediaType(), getEncoding())); - } - }); - - try { - ContentResponse result = futureResult.get(); - logger.trace("Account Response - status: {} content: {}", result.getStatus(), result.getContentAsString()); - return result; - } catch (ExecutionException e) { - throw new MyQCommunicationException(e.getMessage()); - } - } - - private T parseResultAndUpdateStatus(ContentResponse response, Gson parser, Class classOfT) - throws MyQCommunicationException { - if (HttpStatus.isSuccess(response.getStatus())) { - try { - T responseObject = parser.fromJson(response.getContentAsString(), classOfT); - if (responseObject != null) { - if (getThing().getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } - return responseObject; - } else { - throw new MyQCommunicationException("Bad response from server"); - } - } catch (JsonSyntaxException e) { - throw new MyQCommunicationException("Invalid JSON Response " + response.getContentAsString()); - } - } else if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) { - // our tokens no longer work, will need to login again - needsLogin = true; - throw new MyQCommunicationException("Token was rejected for request"); - } else { - throw new MyQCommunicationException( - "Invalid Response Code " + response.getStatus() + " : " + response.getContentAsString()); - } - } - - /** - * Returns the MyQ login page which contains form elements and cookies needed to login - * - * @param codeVerifier - * @return - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - private ContentResponse getLoginPage(String codeVerifier) - throws InterruptedException, ExecutionException, TimeoutException { - try { - Request request = httpClient.newRequest(LOGIN_AUTHORIZE_URL) // - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) // - .param("client_id", CLIENT_ID) // - .param("code_challenge", generateCodeChallange(codeVerifier)) // - .param("code_challenge_method", "S256") // - .param("redirect_uri", REDIRECT_URI) // - .param("response_type", "code") // - .param("scope", SCOPE) // - .agent(userAgent).followRedirects(true); - request.header("Accept", "\"*/*\""); - request.header("Authorization", - "Basic " + Base64.getEncoder().encodeToString((CLIENT_ID + ":").getBytes())); - logger.debug("Sending {} to {}", request.getMethod(), request.getURI()); - ContentResponse response = request.send(); - logger.debug("Login Code {} Response {}", response.getStatus(), response.getContentAsString()); - return response; - } catch (NoSuchAlgorithmException e) { - throw new ExecutionException(e.getCause()); - } - } - - /** - * Sends configured credentials and elements from the login page in order to obtain a redirect location header value - * - * @param url - * @param requestToken - * @param returnURL - * @return The location header value - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - @Nullable - private String postLogin(String url, String requestToken, String returnURL) - throws InterruptedException, ExecutionException, TimeoutException { - /* - * on a successful post to this page we will get several redirects, and a final 301 to: - * com.myqops://ios?code=0123456789&scope=MyQ_Residential%20offline_access&iss=https%3A%2F%2Fpartner-identity. - * myq-cloud.com - * - * We can then take the parameters out of this location and continue the process - */ - Fields fields = new Fields(); - fields.add("Email", username); - fields.add("Password", password); - fields.add("__RequestVerificationToken", requestToken); - fields.add("ReturnUrl", returnURL); - - Request request = httpClient.newRequest(url).method(HttpMethod.POST) // - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) // - .content(new FormContentProvider(fields)) // - .agent(userAgent) // - .followRedirects(false); - setCookies(request); - - logger.debug("Posting Login to {}", url); - ContentResponse response = request.send(); - - String location = null; - - // follow redirects until we match our REDIRECT_URI or hit a redirect safety limit - for (int i = 0; i < LOGIN_MAX_REDIRECTS && HttpStatus.isRedirection(response.getStatus()); i++) { - - String loc = response.getHeaders().get("location"); - if (logger.isTraceEnabled()) { - logger.trace("Redirect Login: Code {} Location Header: {} Response {}", response.getStatus(), loc, - response.getContentAsString()); - } - if (loc == null) { - logger.debug("No location value"); - break; - } - if (loc.indexOf(REDIRECT_URI) == 0) { - location = loc; - break; - } - request = httpClient.newRequest(LOGIN_BASE_URL + loc).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .agent(userAgent).followRedirects(false); - setCookies(request); - response = request.send(); - } - return location; - } - - /** - * Final step of the login process to get an oAuth access response token - * - * @param redirectLocation - * @param codeVerifier - * @return - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - private ContentResponse getLoginToken(String redirectLocation, String codeVerifier) - throws InterruptedException, ExecutionException, TimeoutException { - try { - Map params = parseLocationQuery(redirectLocation); - - Fields fields = new Fields(); - fields.add("client_id", CLIENT_ID); - fields.add("client_secret", Base64.getEncoder().encodeToString(CLIENT_SECRET.getBytes())); - fields.add("code", params.get("code")); - fields.add("code_verifier", codeVerifier); - fields.add("grant_type", "authorization_code"); - fields.add("redirect_uri", REDIRECT_URI); - fields.add("scope", params.get("scope")); - - Request request = httpClient.newRequest(LOGIN_TOKEN_URL) // - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) // - .content(new FormContentProvider(fields)) // - .method(HttpMethod.POST) // - .agent(userAgent).followRedirects(true); - setCookies(request); - ContentResponse response = request.send(); - if (logger.isTraceEnabled()) { - logger.trace("Login Code {} Response {}", response.getStatus(), response.getContentAsString()); - } - return response; - } catch (URISyntaxException e) { - throw new ExecutionException(e.getCause()); - } - } - - private OAuthClientService getOAuthService() { - OAuthClientService oAuthService = this.oAuthService; - if (oAuthService == null || oAuthService.isClosed()) { - oAuthService = oAuthFactory.createOAuthClientService(getThing().toString(), LOGIN_TOKEN_URL, - LOGIN_AUTHORIZE_URL, CLIENT_ID, CLIENT_SECRET, SCOPE, false); - oAuthService.addAccessTokenRefreshListener(this); - this.oAuthService = oAuthService; - } - return oAuthService; - } - - private String generateCodeVerifier() { - SecureRandom secureRandom = new SecureRandom(); - byte[] codeVerifier = new byte[32]; - secureRandom.nextBytes(codeVerifier); - return Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier); - } - - private String generateCodeChallange(String codeVerifier) throws NoSuchAlgorithmException { - byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII); - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - messageDigest.update(bytes, 0, bytes.length); - byte[] digest = messageDigest.digest(); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - - private Map parseLocationQuery(String location) throws URISyntaxException { - URI uri = new URI(location); - return Arrays.stream(uri.getQuery().split("&")).map(str -> str.split("=")) - .collect(Collectors.toMap(str -> str[0], str -> str[1])); - } - - private void setCookies(Request request) { - for (HttpCookie c : httpClient.getCookieStore().getCookies()) { - request.cookie(c); - } - } - - private String authTokenHeader(AccessTokenResponse tokenResponse) { - return tokenResponse.getTokenType() + " " + tokenResponse.getAccessToken(); - } - - /** - * Exception for authenticated related errors - */ - class MyQAuthenticationException extends Exception { - private static final long serialVersionUID = 1L; - - public MyQAuthenticationException(String message) { - super(message); - } - } - - /** - * Generic exception for non authentication related errors when communicating with the MyQ service. - */ - class MyQCommunicationException extends IOException { - private static final long serialVersionUID = 1L; - - public MyQCommunicationException(@Nullable String message) { - super(message); - } - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQDeviceHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQDeviceHandler.java deleted file mode 100644 index b216daa4c0c45..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQDeviceHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.myq.internal.dto.DeviceDTO; - -/** - * The {@link MyQDeviceHandler} is responsible for handling device updates - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public interface MyQDeviceHandler { - void handleDeviceUpdate(DeviceDTO device); - - String getSerialNumber(); -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQGarageDoorHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQGarageDoorHandler.java deleted file mode 100644 index 7f7591600a11c..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQGarageDoorHandler.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.myq.internal.MyQBindingConstants; -import org.openhab.binding.myq.internal.config.MyQDeviceConfiguration; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.PercentType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.library.types.UpDownType; -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.ThingStatusDetail; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.thing.binding.BridgeHandler; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.UnDefType; - -/** - * The {@link MyQGarageDoorHandler} is responsible for handling commands for a garage door thing, which are - * sent to one of the channels. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQGarageDoorHandler extends BaseThingHandler implements MyQDeviceHandler { - private @Nullable DeviceDTO device; - private String serialNumber; - - public MyQGarageDoorHandler(Thing thing) { - super(thing); - serialNumber = getConfigAs(MyQDeviceConfiguration.class).serialNumber; - } - - @Override - public void initialize() { - updateStatus(ThingStatus.UNKNOWN); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateState(); - return; - } - Bridge bridge = getBridge(); - final DeviceDTO localDevice = device; - if (bridge != null && localDevice != null) { - BridgeHandler handler = bridge.getHandler(); - if (handler != null) { - String cmd = null; - if (command instanceof OnOffType) { - cmd = command == OnOffType.ON ? "open" : "close"; - } - if (command instanceof UpDownType) { - cmd = command == UpDownType.UP ? "open" : "close"; - } - if (command instanceof PercentType percentage) { - cmd = percentage.as(UpDownType.class) == UpDownType.UP ? "open" : "close"; - } - if (command instanceof StringType) { - cmd = command.toString(); - } - if (cmd != null) { - ((MyQAccountHandler) handler).sendDoorAction(localDevice, cmd); - } - } - } - } - - @Override - public String getSerialNumber() { - return serialNumber; - } - - protected void updateState() { - final DeviceDTO localDevice = device; - if (localDevice != null) { - String doorState = localDevice.state.doorState; - updateState("status", new StringType(doorState)); - switch (doorState) { - case "open": - case "opening": - case "closing": - case "stopped": - case "transition": - updateState("switch", OnOffType.ON); - updateState("rollershutter", UpDownType.UP); - break; - case "closed": - updateState("switch", OnOffType.OFF); - updateState("rollershutter", UpDownType.DOWN); - break; - default: - updateState("switch", UnDefType.UNDEF); - updateState("rollershutter", UnDefType.UNDEF); - break; - } - updateState("closeerror", localDevice.state.isUnattendedCloseAllowed ? OnOffType.OFF : OnOffType.ON); - updateState("openerror", localDevice.state.isUnattendedOpenAllowed ? OnOffType.OFF : OnOffType.ON); - } - } - - @Override - public void handleDeviceUpdate(DeviceDTO device) { - if (!MyQBindingConstants.THING_TYPE_GARAGEDOOR.getId().equals(device.deviceFamily)) { - return; - } - this.device = device; - if (device.state.online) { - updateStatus(ThingStatus.ONLINE); - updateState(); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device reports as offline"); - } - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQLampHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQLampHandler.java deleted file mode 100644 index 04cb77b92c03f..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQLampHandler.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.myq.internal.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.myq.internal.MyQBindingConstants; -import org.openhab.binding.myq.internal.config.MyQDeviceConfiguration; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.core.library.types.OnOffType; -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.ThingStatusDetail; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.thing.binding.BridgeHandler; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; - -/** - * The {@link MyQLampHandler} is responsible for handling commands for a lamp thing, which are - * sent to one of the channels. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQLampHandler extends BaseThingHandler implements MyQDeviceHandler { - private @Nullable DeviceDTO device; - private String serialNumber; - - public MyQLampHandler(Thing thing) { - super(thing); - serialNumber = getConfigAs(MyQDeviceConfiguration.class).serialNumber; - } - - @Override - public void initialize() { - updateStatus(ThingStatus.UNKNOWN); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateState(); - return; - } - - if (command instanceof OnOffType) { - Bridge bridge = getBridge(); - final DeviceDTO localDevice = device; - if (bridge != null && localDevice != null) { - BridgeHandler handler = bridge.getHandler(); - if (handler != null) { - ((MyQAccountHandler) handler).sendLampAction(localDevice, command == OnOffType.ON ? "on" : "off"); - } - } - } - } - - @Override - public String getSerialNumber() { - return serialNumber; - } - - protected void updateState() { - final DeviceDTO localDevice = device; - if (localDevice != null) { - String lampState = localDevice.state.lampState; - updateState("switch", "on".equals(lampState) ? OnOffType.ON : OnOffType.OFF); - } - } - - @Override - public void handleDeviceUpdate(DeviceDTO device) { - if (!MyQBindingConstants.THING_TYPE_LAMP.getId().equals(device.deviceFamily)) { - return; - } - this.device = device; - if (device.state.online) { - updateStatus(ThingStatus.ONLINE); - updateState(); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device reports as offline"); - } - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/addon/addon.xml deleted file mode 100644 index a63b7da74fde1..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/addon/addon.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - binding - MyQ Binding - The MyQ binding allows monitoring and control of garage doors that are MyQ enabled. - cloud - - diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/config/config.xml deleted file mode 100644 index beaf772f2412f..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/config/config.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Account username - - - - Account password - password - - - - Specifies the refresh interval in seconds - 60 - - - - - - - Serial number of the garage door - - - - - - - Serial number of the lamp - - - - diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/i18n/myq.properties b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/i18n/myq.properties deleted file mode 100644 index 7fafc355e7588..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/i18n/myq.properties +++ /dev/null @@ -1,42 +0,0 @@ -# add-on - -addon.myq.name = MyQ Binding -addon.myq.description = The MyQ binding allows monitoring and control of garage doors that are MyQ enabled. - -# thing types - -thing-type.myq.account.label = MyQ Account -thing-type.myq.account.description = MyQ Cloud Account -thing-type.myq.garagedoor.label = MyQ Garage Door -thing-type.myq.garagedoor.description = MyQ Garage Door -thing-type.myq.lamp.label = MyQ Lamp -thing-type.myq.lamp.description = MyQ Lamp - -# thing types config - -thing-type.config.myq.account.password.label = password -thing-type.config.myq.account.password.description = Account password -thing-type.config.myq.account.refreshInterval.label = Refresh Interval -thing-type.config.myq.account.refreshInterval.description = Specifies the refresh interval in seconds -thing-type.config.myq.account.username.label = User Name -thing-type.config.myq.account.username.description = Account username -thing-type.config.myq.garagedoor.serialNumber.label = Serial Number -thing-type.config.myq.garagedoor.serialNumber.description = Serial number of the garage door -thing-type.config.myq.lamp.serialNumber.label = Serial Number -thing-type.config.myq.lamp.serialNumber.description = Serial number of the lamp - -# channel types - -channel-type.myq.doorcloseerror.label = Garage Door Close Error -channel-type.myq.dooropenerror.label = Garage Door Open Error -channel-type.myq.doorrollershutter.label = Garage Door Rollershutter -channel-type.myq.doorstatus.label = Garage Door Status -channel-type.myq.doorstatus.state.option.open = Open -channel-type.myq.doorstatus.state.option.opening = Opening -channel-type.myq.doorstatus.state.option.closed = Closed -channel-type.myq.doorstatus.state.option.closing = Closing -channel-type.myq.doorstatus.state.option.stopped = Stopped -channel-type.myq.doorstatus.state.option.transition = Transitioning -channel-type.myq.doorstatus.state.option.unknown = Unknown -channel-type.myq.doorswitch.label = Garage Door Switch -channel-type.myq.lampswitch.label = Lamp Switch diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/thing/thing-types.xml deleted file mode 100644 index 56fb3cb420e94..0000000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/thing/thing-types.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - MyQ Cloud Account - - - - - - - - - MyQ Garage Door - - - - - - - - serialNumber - - - - - - - - - MyQ Lamp - - - - serialNumber - - - - - String - - - - - - - - - - - - - - - Switch - - - - Rollershutter - - - - Switch - - - - - Switch - - - - - Switch - - - diff --git a/bundles/pom.xml b/bundles/pom.xml index 4ab10b59b2f7a..dfdad3c4e0b86 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -259,7 +259,6 @@ org.openhab.binding.mybmw org.openhab.binding.mycroft org.openhab.binding.mynice - org.openhab.binding.myq org.openhab.binding.mystrom org.openhab.binding.nanoleaf org.openhab.binding.neato From 9fc293829165e4d314701a85d78efd2741b7384e Mon Sep 17 00:00:00 2001 From: maniac103 Date: Sat, 18 Nov 2023 20:38:55 +0100 Subject: [PATCH 093/146] [homematic] Fix duplication of LONG_REPEATED events for HM devices (#15906) Depending on device configuration and used central [1], HM devices may indicate long press repetition either by a single PRESS_CONT event or by a PRESS_CONT + PRESS_LONG combination. In the latter case, make sure to not generate a LONG_REPEATED trigger channel event for both PRESS_CONT and PRESS_LONG, but instead keep LONG_REPEATED generation to the PRESS_CONT handling. [1] I'm not sure what a real CCU is doing, but for Homegear, a configured long press timeout of less than 1s generates only PRESS_CONT, while a timeout of more than 1s generates PRESS_CONT + PRESS_LONG ... see [2]. [2] https://github.com/Homegear/Homegear-HomeMaticBidCoS/blob/master/src/BidCoSPeer.cpp#L1711-L1716 Signed-off-by: Danny Baumann --- .../ButtonVirtualDatapointHandler.java | 19 ++++++++++++------- .../virtual/ButtonDatapointTest.java | 7 +++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonVirtualDatapointHandler.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonVirtualDatapointHandler.java index 5caa73d36fdf3..589c015c55088 100644 --- a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonVirtualDatapointHandler.java +++ b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonVirtualDatapointHandler.java @@ -68,6 +68,7 @@ public void handleEvent(VirtualGateway gateway, HmDatapoint dp) { HmDatapoint vdp = getVirtualDatapoint(channel); int usPos = dp.getName().indexOf("_"); String pressType = usPos == -1 ? dp.getName() : dp.getName().substring(usPos + 1); + boolean usesLongStart = devicesUsingLongStartEvent.contains(deviceSerial); boolean isLongPressActive = CommonTriggerEvents.LONG_PRESSED.equals(vdp.getValue()) || LONG_REPEATED_EVENT.equals(vdp.getValue()); if (MiscUtils.isTrueValue(dp.getValue())) { @@ -78,14 +79,18 @@ public void handleEvent(VirtualGateway gateway, HmDatapoint dp) { break; } case "LONG": - if (isLongPressActive) { + if (usesLongStart) { // HM-IP devices do long press repetitions via LONG instead of CONT events, // so clear previous value to force re-triggering of event - vdp.setValue(null); - vdp.setValue(LONG_REPEATED_EVENT); + if (isLongPressActive) { + vdp.setValue(null); + vdp.setValue(LONG_REPEATED_EVENT); + } } else { - // HM devices start long press via LONG events - vdp.setValue(CommonTriggerEvents.LONG_PRESSED); + // HM devices start long press via LONG events, but also may keep sending them + // alongside CONT repetition events. In case a long press is already active, we just + // acknowledge those events by setting the value again, to make sure to not re-trigger events + vdp.setValue(isLongPressActive ? LONG_REPEATED_EVENT : CommonTriggerEvents.LONG_PRESSED); } break; case "LONG_START": @@ -107,10 +112,10 @@ public void handleEvent(VirtualGateway gateway, HmDatapoint dp) { break; default: vdp.setValue(null); - logger.warn("Unexpected vaule '{}' for PRESS virtual datapoint", pressType); + logger.warn("Unexpected value '{}' for PRESS virtual datapoint", pressType); } } else { - String usedStartEvent = devicesUsingLongStartEvent.contains(deviceSerial) ? "LONG_START" : "LONG"; + String usedStartEvent = usesLongStart ? "LONG_START" : "LONG"; if (usedStartEvent.equals(pressType) && LONG_REPEATED_EVENT.equals(vdp.getValue())) { // If we're currently processing a repeated long-press event, don't let the initial LONG // event time out the repetitions, the CONT delay handler will take care of it diff --git a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonDatapointTest.java b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonDatapointTest.java index c7106c5b6cb4b..dc49e38781bf7 100644 --- a/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonDatapointTest.java +++ b/bundles/org.openhab.binding.homematic/src/test/java/org/openhab/binding/homematic/internal/communicator/virtual/ButtonDatapointTest.java @@ -70,6 +70,12 @@ public void testLongPressHm() throws IOException, HomematicClientException { HmDatapoint contPressDp = createPressDatapointFrom(longPressDp, "PRESS_CONT", Boolean.TRUE); mockEventReceiver.eventReceived(contPressDp); assertThat(buttonVirtualDatapoint.getValue(), is("LONG_REPEATED")); + assertThat(buttonVirtualDatapoint.getPreviousValue(), nullValue()); + + // Receiving another LONG event during the long press should be ignored + mockEventReceiver.eventReceived(longPressDp); + assertThat(buttonVirtualDatapoint.getValue(), is("LONG_REPEATED")); + assertThat(buttonVirtualDatapoint.getPreviousValue(), is("LONG_REPEATED")); HmDatapoint releaseDp = createPressDatapointFrom(longPressDp, "PRESS_LONG_RELEASE", Boolean.TRUE); mockEventReceiver.eventReceived(releaseDp); @@ -87,6 +93,7 @@ public void testLongPressHmIp() throws IOException, HomematicClientException { HmDatapoint contPressDp = createPressDatapointFrom(longPressDp, "PRESS_LONG", Boolean.TRUE); mockEventReceiver.eventReceived(contPressDp); assertThat(buttonVirtualDatapoint.getValue(), is("LONG_REPEATED")); + assertThat(buttonVirtualDatapoint.getPreviousValue(), nullValue()); HmDatapoint releaseDp = createPressDatapointFrom(longPressDp, "PRESS_LONG_RELEASE", Boolean.TRUE); mockEventReceiver.eventReceived(releaseDp); From 822181de384945ed547a1929f5ac4728e62c71bb Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sat, 18 Nov 2023 13:31:49 -0700 Subject: [PATCH 094/146] [mqtt.homeassistant] Support color temp on JSON schema lights (#14839) * [mqtt.homeassistant] support color temp on JSON schema lights also adds a color_mode channel if color temp is possible, so you can know how the bulb is behaving * put color mode channel construction into buildChannels() --------- Signed-off-by: Cody Cutrer --- .../internal/component/JSONSchemaLight.java | 69 +++++++++++++++++-- .../internal/component/LightColorMode.java | 9 +++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java index 01e8dcb9fe02b..8650018426da4 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java @@ -20,12 +20,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; +import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.homeassistant.internal.exception.UnsupportedComponentException; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.ChannelUID; import org.openhab.core.types.Command; import org.openhab.core.types.State; @@ -47,6 +50,7 @@ @NonNullByDefault public class JSONSchemaLight extends AbstractRawSchemaLight { private static final BigDecimal SCALE_FACTOR = new BigDecimal("2.55"); // string to not lose precision + private static final BigDecimal BIG_DECIMAL_HUNDRED = new BigDecimal(100); private final Logger logger = LoggerFactory.getLogger(JSONSchemaLight.class); @@ -67,14 +71,23 @@ protected static class Color { protected @Nullable Integer transition; } + TextValue colorModeValue; + public JSONSchemaLight(ComponentFactory.ComponentConfiguration builder) { super(builder); + colorModeValue = new TextValue(); } @Override protected void buildChannels() { + List supportedColorModes = channelConfiguration.supportedColorModes; + if (supportedColorModes != null && supportedColorModes.contains(LightColorMode.COLOR_MODE_COLOR_TEMP)) { + colorModeValue = new TextValue( + supportedColorModes.stream().map(LightColorMode::serializedName).toArray(String[]::new)); + buildChannel(COLOR_MODE_CHANNEL_ID, colorModeValue, "Color Mode", this).isAdvanced(true).build(); + } + if (channelConfiguration.colorMode) { - List supportedColorModes = channelConfiguration.supportedColorModes; if (supportedColorModes == null || channelConfiguration.supportedColorModes.isEmpty()) { throw new UnsupportedComponentException("JSON schema light with color modes '" + getHaID() + "' does not define supported_color_modes!"); @@ -83,6 +96,12 @@ protected void buildChannels() { if (LightColorMode.hasColorChannel(supportedColorModes)) { hasColorChannel = true; } + + if (supportedColorModes.contains(LightColorMode.COLOR_MODE_COLOR_TEMP)) { + buildChannel(COLOR_TEMP_CHANNEL_ID, colorTempValue, "Color Temperature", this) + .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleColorTempCommand(command)) + .build(); + } } if (hasColorChannel) { @@ -118,7 +137,7 @@ protected void publishState(HSBType state) { json.color = new JSONState.Color(); if (channelConfiguration.supportedColorModes.contains(LightColorMode.COLOR_MODE_HS)) { json.color.h = state.getHue().toBigDecimal(); - json.color.s = state.getSaturation().toBigDecimal(); + json.color.s = state.getSaturation().toBigDecimal().divide(BIG_DECIMAL_HUNDRED); } else if (LightColorMode.hasRGB(Objects.requireNonNull(channelConfiguration.supportedColorModes))) { var rgb = state.toRGB(); json.color.r = rgb[0].toBigDecimal().multiply(SCALE_FACTOR).intValue(); @@ -126,8 +145,8 @@ protected void publishState(HSBType state) { json.color.b = rgb[2].toBigDecimal().multiply(SCALE_FACTOR).intValue(); } else { // if (channelConfiguration.supportedColorModes.contains(COLOR_MODE_XY)) var xy = state.toXY(); - json.color.x = xy[0].toBigDecimal(); - json.color.y = xy[1].toBigDecimal(); + json.color.x = xy[0].toBigDecimal().divide(BIG_DECIMAL_HUNDRED); + json.color.y = xy[1].toBigDecimal().divide(BIG_DECIMAL_HUNDRED); } } } @@ -163,6 +182,30 @@ protected boolean handleCommand(Command command) { return false; } + private boolean handleColorTempCommand(Command command) { + JSONState json = new JSONState(); + + if (command instanceof DecimalType) { + command = new QuantityType<>(((DecimalType) command).toBigDecimal(), Units.MIRED); + } + if (command instanceof QuantityType) { + QuantityType mireds = ((QuantityType) command).toInvertibleUnit(Units.MIRED); + if (mireds == null) { + logger.warn("Unable to convert {} to mireds", command); + return false; + } + json.state = "ON"; + json.colorTemp = mireds.toBigDecimal().intValue(); + } else { + return false; + } + + String jsonCommand = getGson().toJson(json); + logger.debug("Publishing new state '{}' of light {} to MQTT.", jsonCommand, getName()); + rawChannel.getState().publishValue(new StringType(jsonCommand)); + return false; + } + @Override public void updateChannelState(ChannelUID channel, State state) { ChannelStateUpdateListener listener = this.channelStateUpdateListener; @@ -204,6 +247,14 @@ public void updateChannelState(ChannelUID channel, State state) { } } + if (jsonState.colorTemp != null) { + colorTempValue.update(new QuantityType(Objects.requireNonNull(jsonState.colorTemp), Units.MIRED)); + listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_TEMP_CHANNEL_ID), + colorTempValue.getChannelState()); + + colorModeValue.update(new StringType(LightColorMode.COLOR_MODE_COLOR_TEMP.serializedName())); + } + if (jsonState.color != null) { PercentType brightness = brightnessValue.getChannelState() instanceof PercentType ? (PercentType) brightnessValue.getChannelState() @@ -216,14 +267,24 @@ public void updateChannelState(ChannelUID channel, State state) { if (jsonState.color.h != null && jsonState.color.s != null) { colorValue.update(new HSBType(new DecimalType(Objects.requireNonNull(jsonState.color.h)), new PercentType(Objects.requireNonNull(jsonState.color.s)), brightness)); + colorModeValue.update(new StringType(LightColorMode.COLOR_MODE_HS.serializedName())); } else if (jsonState.color.x != null && jsonState.color.y != null) { HSBType newColor = HSBType.fromXY(jsonState.color.x.floatValue(), jsonState.color.y.floatValue()); colorValue.update(new HSBType(newColor.getHue(), newColor.getSaturation(), brightness)); + colorModeValue.update(new StringType(LightColorMode.COLOR_MODE_XY.serializedName())); } else if (jsonState.color.r != null && jsonState.color.g != null && jsonState.color.b != null) { colorValue.update(HSBType.fromRGB(jsonState.color.r, jsonState.color.g, jsonState.color.b)); + colorModeValue.update(new StringType(LightColorMode.COLOR_MODE_RGB.serializedName())); } } + if (jsonState.colorMode != null) { + colorModeValue.update(new StringType(jsonState.colorMode.serializedName())); + } + + listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_MODE_CHANNEL_ID), + colorModeValue.getChannelState()); + if (hasColorChannel) { listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), colorValue.getChannelState()); } else if (brightnessChannel != null) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightColorMode.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightColorMode.java index cb8aa6fb950b7..a7d364ff7544e 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightColorMode.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/LightColorMode.java @@ -61,4 +61,13 @@ public static boolean hasColorChannel(List supportedColorModes) public static boolean hasRGB(List supportedColorModes) { return WITH_RGB.stream().anyMatch(cm -> supportedColorModes.contains(cm)); } + + public String serializedName() { + try { + return LightColorMode.class.getDeclaredField(toString()).getAnnotation(SerializedName.class).value(); + } catch (NoSuchFieldException e) { + // can't happen + throw new IllegalStateException(e); + } + } } From 8f835216aba03e06accb224a117a7909eaf26ac7 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Sat, 18 Nov 2023 21:50:10 +0100 Subject: [PATCH 095/146] [shelly] BLU Motion, optimize ShellyManager for BLU devices (#15401) Signed-off-by: Markus Michels --- bundles/org.openhab.binding.shelly/README.md | 15 ++- .../internal/ShellyBindingConstants.java | 1 + .../internal/api/ShellyDeviceProfile.java | 6 +- .../internal/api2/Shelly2ApiClient.java | 92 ++++++++----------- .../internal/api2/Shelly2ApiJsonDTO.java | 4 + .../shelly/internal/api2/ShellyBluApi.java | 13 +++ .../discovery/ShellyThingCreator.java | 5 + .../internal/handler/ShellyBaseHandler.java | 1 + .../handler/ShellyBluSensorHandler.java | 4 + .../manager/ShellyManagerActionPage.java | 13 ++- .../manager/ShellyManagerConstants.java | 3 + .../manager/ShellyManagerOverviewPage.java | 16 +++- .../internal/manager/ShellyManagerPage.java | 2 +- .../provider/ShellyChannelDefinitions.java | 8 +- .../resources/OH-INF/i18n/shelly.properties | 3 +- .../OH-INF/thing/shellyBlu_sensor.xml | 13 +++ .../main/resources/scripts/oh-blu-scanner.js | 4 +- 17 files changed, 133 insertions(+), 70 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index f7daae283a2ab..406cffc19b72b 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -29,6 +29,8 @@ The binding supports both hardware generations - Generation 1: The original Shelly devices like the Shelly 1, Shelly 2.5, Shelly Flood etc. - Generation 2: The new Plus / Pro series of devices +- Shelly Plus Mini: Shelly Plus devices in compact format +- Shelly BLU: Bluetooth based series of devices The binding provides the same feature set across all devices as good as possible and depending on device specific features. @@ -120,7 +122,7 @@ The binding provides the same feature set across all devices as good as possible | ----------------- | ------------------------------------------------------ | --------- | | shellyblubutton | Shelly BLU Button 1 | SBBT | | shellybludw | Shelly BLU Door/Windows | SBDW | - +| shellyblumotion | Shelly BLU Motion | SBMO | ## Binding Configuration @@ -1465,6 +1467,17 @@ See notes on discovery of Shelly BLU devices above. | | lowBattery | Switch | yes | Low battery alert (< 20%) | | device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet | +## Shelly BLU Motion Sensor (thing-type: shellyblumotion) + +See notes on discovery of Shelly BLU devices above. + +| Group | Channel | Type | read-only | Description | +| ------- | ------------- | -------- | --------- | ----------------------------------------------------------------------------------- | +| sensors | motion | Switch | yes | ON: Motion detected | +| battery | batteryLevel | Number | yes | Battery Level in % | +| | lowBattery | Switch | yes | Low battery alert (< 20%) | +| device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet | + ## Shelly Wall Displays | Group | Channel | Type | read-only | Description | diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index 3f3f23524c8df..34957a2b44cd6 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -103,6 +103,7 @@ public class ShellyBindingConstants { // Shelly BLU THING_TYPE_SHELLYBLUBUTTON, // THING_TYPE_SHELLYBLUDW, // + THING_TYPE_SHELLYBLUMOTION, // THING_TYPE_SHELLYPROTECTED, // THING_TYPE_SHELLYUNKNOWN); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 6f96910b56059..23cce5cff1a93 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -220,7 +220,8 @@ public void initFromThingType(String name) { isHT = thingType.equals(THING_TYPE_SHELLYHT_STR) || thingType.equals(THING_TYPE_SHELLYPLUSHT_STR); isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR) || thingType.equals(THING_TYPE_SHELLYBLUDW_STR); - isMotion = thingType.startsWith(THING_TYPE_SHELLYMOTION_STR); + isMotion = thingType.startsWith(THING_TYPE_SHELLYMOTION_STR) + || thingType.equals(THING_TYPE_SHELLYBLUMOTION_STR); isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR); isIX = thingType.equals(THING_TYPE_SHELLYIX3_STR) || thingType.equals(THING_TYPE_SHELLYPLUSI4_STR) || thingType.equals(THING_TYPE_SHELLYPLUSI4DC_STR); @@ -401,7 +402,8 @@ public static String extractFwVersion(@Nullable String version) { } public static boolean isGeneration2(String thingType) { - return thingType.startsWith("shellyplus") || thingType.startsWith("shellypro") || isBluSeries(thingType); + return thingType.startsWith("shellyplus") || thingType.startsWith("shellypro") + || thingType.startsWith("shellymini") || isBluSeries(thingType); } public static boolean isBluSeries(String thingType) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java index 2805d1310107f..7ff8a699218e7 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java @@ -18,7 +18,6 @@ import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -102,57 +101,46 @@ public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpC super(thingName, config, httpClient); } - protected static final Map MAP_INMODE_BTNTYPE = new HashMap<>(); - static { - MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY); - MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE); - MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE); - MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY); - } - - protected static final Map MAP_INPUT_EVENT_TYPE = new HashMap<>(); - static { - MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH); - MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH); - MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH); - MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH); - MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH); - MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH); - } - - protected static final Map MAP_INPUT_EVENT_ID = new HashMap<>(); - static { - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF); - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON); - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH); - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH); - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH); - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH); - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH); - MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH); - } - - protected static final Map MAP_INPUT_MODE = new HashMap<>(); - static { - MAP_INPUT_MODE.put(SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON); - MAP_INPUT_MODE.put(SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE); - MAP_INPUT_MODE.put(SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON); - } - - protected static final Map MAP_ROLLER_STATE = new HashMap<>(); - static { - MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN); - MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE); - MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING); // Gen2-only - MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING); // Gen2-only - MAP_ROLLER_STATE.put(SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP); - MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only - } - protected static final Map MAP_PROFILE = new HashMap<>(); - static { - MAP_PROFILE.put(SHELLY_CLASS_RELAY, SHELLY2_PROFILE_RELAY); - MAP_PROFILE.put(SHELLY_CLASS_ROLLER, SHELLY2_PROFILE_COVER); - } + protected static final Map MAP_INMODE_BTNTYPE = Map.of(// + SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY, // + SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE, // + SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE, // + SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY); + + protected static final Map MAP_INPUT_EVENT_TYPE = Map.of(// + SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH, // + SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH, // + SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH, // + SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH, // + SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH, // + SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH); + + protected static final Map MAP_INPUT_EVENT_ID = Map.of(// + SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF, // + SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON, // + SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH, // + SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH, // + SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH, // + SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH, // + SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH, // + SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH); + + protected static final Map MAP_INPUT_MODE = Map.of(// + SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON, // + SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE, // + SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON); + + protected static final Map MAP_ROLLER_STATE = Map.of(// + SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN, // + SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE, // + SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING, // Gen2-only + SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING, // Gen2-only + SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP, // + SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only + + protected static final Map MAP_PROFILE = Map.of(// + SHELLY_CLASS_RELAY, SHELLY2_PROFILE_RELAY, // + SHELLY_CLASS_ROLLER, SHELLY2_PROFILE_COVER); protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java index ea309929f51b2..664eb42807fc0 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java @@ -1085,6 +1085,10 @@ public class Shelly2NotifyEventMessage { public Integer windowState; @SerializedName("Rotation") public Double rotation; + @SerializedName("Motion") + public Integer motionState; + @SerializedName("Temperature") + public Double temperature; public Integer rssi; public Integer tx_power; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java index 5eabce2a73d53..7869b6fd79a37 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java @@ -27,6 +27,7 @@ import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorSleepMode; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorTmp; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus; @@ -271,12 +272,22 @@ public void onNotifyEvent(Shelly2RpcNotifyEvent message) { sensorData.lux.isValid = true; sensorData.lux.value = (double) e.data.illuminance; } + if (e.data.temperature != null) { + if (sensorData.tmp == null) { + sensorData.tmp = new ShellySensorTmp(); + } + sensorData.tmp.tC = e.data.temperature; + sensorData.tmp.isValid = true; + } if (e.data.rotation != null) { if (sensorData.accel == null) { sensorData.accel = new ShellySensorAccel(); } sensorData.accel.tilt = e.data.rotation.intValue(); } + if (e.data.motionState != null) { + sensorData.motion = e.data.motionState == 1; + } if (e.data.buttonEvent != null) { ShellyInputState input = deviceStatus.inputs != null ? deviceStatus.inputs.get(0) @@ -314,6 +325,8 @@ public static String buildBluServiceName(String name, String mac) throws Illegal return (THING_TYPE_SHELLYBLUBUTTON_STR + "-" + mac).toLowerCase(); case SHELLYDT_BLUDW: return (THING_TYPE_SHELLYBLUDW_STR + "-" + mac).toLowerCase(); + case SHELLYDT_BLUMOTION: + return (THING_TYPE_SHELLYBLUMOTION_STR + "-" + mac).toLowerCase(); default: throw new IllegalArgumentException("Unsupported BLU device model " + model); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java index 467c23c684f97..9417205a5f9eb 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java @@ -113,6 +113,7 @@ public class ShellyThingCreator { // Shelly BLU Series public static final String SHELLYDT_BLUBUTTON = "SBBT"; public static final String SHELLYDT_BLUDW = "SBDW"; + public static final String SHELLYDT_BLUMOTION = "SBMO"; // Thing names public static final String THING_TYPE_SHELLY1_STR = "shelly1"; @@ -191,6 +192,7 @@ public class ShellyThingCreator { public static final String THING_TYPE_SHELLYBLU_PREFIX = "shellyblu"; public static final String THING_TYPE_SHELLYBLUBUTTON_STR = THING_TYPE_SHELLYBLU_PREFIX + "button"; public static final String THING_TYPE_SHELLYBLUDW_STR = THING_TYPE_SHELLYBLU_PREFIX + "dw"; + public static final String THING_TYPE_SHELLYBLUMOTION_STR = THING_TYPE_SHELLYBLU_PREFIX + "motion"; // Password protected or unknown device public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice"; @@ -306,6 +308,8 @@ public class ShellyThingCreator { public static final ThingTypeUID THING_TYPE_SHELLYBLUBUTTON = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYBLUBUTTON_STR); public static final ThingTypeUID THING_TYPE_SHELLYBLUDW = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYBLUDW_STR); + public static final ThingTypeUID THING_TYPE_SHELLYBLUMOTION = new ThingTypeUID(BINDING_ID, + THING_TYPE_SHELLYBLUMOTION_STR); private static final Map THING_TYPE_MAPPING = new LinkedHashMap<>(); static { @@ -383,6 +387,7 @@ public class ShellyThingCreator { // BLU Series THING_TYPE_MAPPING.put(SHELLYDT_BLUBUTTON, THING_TYPE_SHELLYBLUBUTTON_STR); THING_TYPE_MAPPING.put(SHELLYDT_BLUDW, THING_TYPE_SHELLYBLUDW_STR); + THING_TYPE_MAPPING.put(SHELLYDT_BLUMOTION, THING_TYPE_SHELLYBLUMOTION_STR); // Wall displays THING_TYPE_MAPPING.put(SHELLYDT_PLUSWALLDISPLAY, THING_TYPE_SHELLYPLUSWALLDISPLAY_STR); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index 1a35a432b1afc..2871943edabb9 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -1019,6 +1019,7 @@ protected void initializeThingConfig() { config.password = bindingConfig.defaultPassword; logger.debug("{}: Using default password from bindingConfig (userId={})", thingName, config.userId); } + if (config.updateInterval == 0) { config.updateInterval = UPDATE_STATUS_INTERVAL_SECONDS * UPDATE_SKIP_COUNT; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java index d79967a0dd728..0c392678dbc00 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java @@ -67,6 +67,10 @@ public static void addBluThing(String gateway, Shelly2NotifyEvent e, ShellyThing ttype = THING_TYPE_SHELLYBLUDW_STR; tuid = THING_TYPE_SHELLYBLUDW; break; + case SHELLYDT_BLUMOTION: + ttype = THING_TYPE_SHELLYBLUMOTION_STR; + tuid = THING_TYPE_SHELLYBLUMOTION; + break; default: logger.debug("{}: Unsupported BLU device model {}, MAC={}", gateway, model, mac); return; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java index 6f6abdbd0fa7d..f98b0916dff8a 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java @@ -376,7 +376,9 @@ public static Map getActions(ShellyDeviceProfile profile) { boolean gen2 = profile.isGen2; list.put(ACTION_RES_STATS, "Reset Statistics"); - list.put(ACTION_RESTART, "Reboot Device"); + if (!profile.isBlu) { + list.put(ACTION_RESTART, "Reboot Device"); + } if (!gen2 || !profile.isBlu) { list.put(ACTION_PROTECT, "Protect Device"); } @@ -413,10 +415,13 @@ && getBool(profile.settings.wifiSta.enabled)) { !profile.settings.bluetooth ? "Enable Bluetooth" : "Disable Bluetooth"); } - boolean set = profile.settings.cloud != null && getBool(profile.settings.cloud.enabled); - list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud"); + if (!profile.isBlu) { + boolean set = profile.settings.cloud != null && getBool(profile.settings.cloud.enabled); + list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud"); + + list.put(ACTION_RESET, "-Factory Reset"); + } - list.put(ACTION_RESET, "-Factory Reset"); if (!gen2 && profile.extFeatures) { list.put(ACTION_OTACHECK, "Check for Update"); boolean debug_enable = getBool(profile.settings.debugEnable); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerConstants.java index 4bc16c72db149..fdee5a4c5d3d8 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerConstants.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.shelly.internal.manager; +import static org.openhab.binding.shelly.internal.ShellyBindingConstants.CONFIG_DEVICEIP; + import java.nio.charset.StandardCharsets; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -82,6 +84,7 @@ public class ShellyManagerConstants { public static final String ATTRIBUTE_MESSAGE = "message"; public static final String ATTRIBUTE_TOTAL_DEV = "totalDevices"; public static final String ATTRIBUTE_STATUS_ICON = "iconStatus"; + public static final String ATTRIBUTE_DEVICEIP = CONFIG_DEVICEIP; public static final String ATTRIBUTE_DISPLAY_NAME = "displayName"; public static final String ATTRIBUTE_DEV_STATUS = "deviceStatus"; public static final String ATTRIBUTE_DEBUG_MODE = "debugMode"; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java index 7c0bdd0218d84..ff43076997254 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java @@ -117,6 +117,11 @@ public ShellyMgrResponse generateContent(String path, Map para properties.put(ATTRIBUTE_FIRMWARE_SEL, ""); properties.put(ATTRIBUTE_ACTION_LIST, ""); } + if (profile.isBlu) { + properties.put(ATTRIBUTE_DISPLAY_NAME, profile.thingName); + properties.put(ATTRIBUTE_DEVICEIP, "n/a"); + properties.put(PROPERTY_WIFI_NETW, "Bluetooth"); + } html += loadHTML(OVERVIEW_DEVICE, properties); } } catch (ShellyApiException e) { @@ -193,10 +198,13 @@ private String fillFirmwareHtml(ShellyDeviceProfile profile, String uid, String logger.debug("{}: Unable to retrieve firmware list: {}", LOG_PREFIX, e.toString()); } - html += "\t\t\t\t\t\n"; - - html += "\t\t\t\t\n\t\t\t"; + html += "\t\t\t\t\t\n\t\t\t\t\n\t\t\t"; return html; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java index 5d7e46bfa1dbf..d4196e1a8cb70 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java @@ -538,7 +538,7 @@ protected static String getDeviceType(Map properties) { } protected static String getDeviceIp(Map properties) { - return getString(properties.get("deviceIp")); + return getString(properties.get(ATTRIBUTE_DEVICEIP)); } protected static String getDeviceName(Map properties) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java index 4bb96355f834c..6f03bfc79ba7a 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java @@ -657,19 +657,19 @@ public ShellyChannel(ShellyTranslationProvider messages, String group, String ch this.typeId = typeId; groupLabel = getText(PREFIX_GROUP + group + ".label"); - if (groupLabel.contains(PREFIX_GROUP)) { + if (groupLabel.startsWith(PREFIX_GROUP)) { groupLabel = ""; } groupDescription = getText(PREFIX_GROUP + group + ".description"); - if (groupDescription.contains(PREFIX_GROUP)) { + if (groupDescription.startsWith(PREFIX_GROUP)) { groupDescription = ""; } label = getText(PREFIX_CHANNEL + typeId.replace(':', '.') + ".label"); - if (label.contains(PREFIX_CHANNEL)) { + if (label.startsWith(PREFIX_CHANNEL)) { label = ""; } description = getText(PREFIX_CHANNEL + typeId + ".description"); - if (description.contains(PREFIX_CHANNEL)) { + if (description.startsWith(PREFIX_CHANNEL)) { description = ""; // no resource found } } diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties index 7af710eef9136..caa4b42438ff0 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties @@ -42,7 +42,7 @@ message.event.triggered = Event triggered: {0} message.event.filtered = Event filtered: {0} message.coap.init.failed = Unable to start CoIoT: {0} message.discovery.disabled = Device is marked as non-discoverable, will be skipped -message.discovery.protected = Device {0} is protected and reports 'Access denied', check userId/password +message.discovery.protected = Device {0} is protected and reports 'Access denied', check userId/password. message.discovery.failed = Device discovery of device with address {0} failed: {1} message.roller.calibrating = Device is not calibrated, use Shelly App to perform initial roller calibration. message.roller.favmissing = Roller position favorites are not supported by installed firmware or not configured in the Shelly App @@ -122,6 +122,7 @@ thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch wit # BLU devices thing-type.shelly.shellyblubutton.description = Shelly BLU Button thing-type.shelly.shellybludw.description = Shelly BLU Door/Window Sensor +thing-type.shelly.shellyblumotion.description = Shelly BLU Motion Sensor # Wall Displays thing-type.shelly.shellywalldisplay.description = Shelly Wall Display with sensors and input/output diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyBlu_sensor.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyBlu_sensor.xml index c3c8daaeca45d..d809f572807dd 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyBlu_sensor.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyBlu_sensor.xml @@ -32,4 +32,17 @@ + + + @text/thing-type.shelly.shellyblumotion.description + Sensor + + + + + + + serviceName + + diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js b/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js index c02dbc9532080..c1583a92ec715 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js +++ b/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js @@ -4,7 +4,7 @@ * Version 0.2 */ -let ALLTERCO_DEVICE_NAME_PREFIX = ["SBBT", "SBDW"]; +let ALLTERCO_DEVICE_NAME_PREFIX = ["SBBT", "SBDW", "SBMO"]; let ALLTERCO_MFD_ID_STR = "0ba9"; let BTHOME_SVC_ID_STR = "fcd2"; @@ -25,9 +25,11 @@ let int24 = 5; let BTH = []; BTH[0x00] = { n: "pid", t: uint8 }; BTH[0x01] = { n: "Battery", t: uint8, u: "%" }; +BTH[0x02] = { n: "Temperature", t: int16, f: 0.01 }; BTH[0x05] = { n: "Illuminance", t: uint24, f: 0.01 }; BTH[0x1a] = { n: "Door", t: uint8 }; BTH[0x20] = { n: "Moisture", t: uint8 }; +BTH[0x21] = { n: "Motion", t: uint8 }; BTH[0x2d] = { n: "Window", t: uint8 }; BTH[0x3a] = { n: "Button", t: uint8 }; BTH[0x3f] = { n: "Rotation", t: int16, f: 0.1 }; From ffa7dc6a5bd098cf9ad370de40bee5043ba3b07f Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sat, 18 Nov 2023 14:46:57 -0700 Subject: [PATCH 096/146] [mqtt.homeassistant] handle null component name (#15427) * [mqtt.homeassistant] handle null component name channels from such components will not have a group. this is now done by zigbee2mqtt for the "default" component of a device, such as the light. HASS encourages this as of release 2023.8 Signed-off-by: Cody Cutrer --- .../internal/ComponentChannel.java | 2 +- .../internal/component/AbstractComponent.java | 73 +++++++++++++++---- .../internal/component/AlarmControlPanel.java | 9 +-- .../internal/component/Camera.java | 4 +- .../internal/component/Climate.java | 2 +- .../internal/component/Cover.java | 3 +- .../component/DefaultSchemaLight.java | 11 +-- .../internal/component/DeviceTrigger.java | 3 +- .../homeassistant/internal/component/Fan.java | 3 +- .../internal/component/JSONSchemaLight.java | 7 +- .../internal/component/Lock.java | 4 +- .../internal/component/Number.java | 3 +- .../internal/component/Select.java | 3 +- .../internal/component/Sensor.java | 3 +- .../internal/component/Vacuum.java | 2 +- .../dto/AbstractChannelConfiguration.java | 8 +- .../handler/HomeAssistantThingHandler.java | 37 +++++----- .../HomeAssistantMQTTImplementationTest.java | 4 +- 18 files changed, 111 insertions(+), 70 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java index b016a6960e0bd..d63a304d5efc7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java @@ -224,7 +224,7 @@ public ComponentChannel build(boolean addToComponent) { ChannelType type; ChannelTypeUID channelTypeUID; - channelUID = new ChannelUID(component.getGroupUID(), channelID); + channelUID = component.buildChannelUID(channelID); channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID, channelUID.getGroupId() + "_" + channelID); channelState = new HomeAssistantChannelState( diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index 203218baa75ae..87625305d300b 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; @@ -31,8 +32,10 @@ import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.type.ChannelDefinition; import org.openhab.core.thing.type.ChannelGroupDefinition; import org.openhab.core.thing.type.ChannelGroupType; @@ -54,8 +57,8 @@ public abstract class AbstractComponent // Component location fields private final ComponentConfiguration componentConfiguration; - protected final ChannelGroupTypeUID channelGroupTypeUID; - protected final ChannelGroupUID channelGroupUID; + protected final @Nullable ChannelGroupTypeUID channelGroupTypeUID; + protected final @Nullable ChannelGroupUID channelGroupUID; protected final HaID haID; // Channels and configuration @@ -83,10 +86,15 @@ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig this.haID = componentConfiguration.getHaID(); - String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId()); + if (channelConfiguration.getName() != null) { + String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId()); - this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId); - this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId); + this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId); + this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId); + } else { + this.channelGroupTypeUID = null; + this.channelGroupUID = null; + } this.configSeen = false; @@ -142,7 +150,10 @@ public void setConfigSeen() { * @param channelTypeProvider The channel type provider */ public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) { - channelTypeProvider.setChannelGroupType(getGroupTypeUID(), getType()); + ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID; + if (groupTypeUID != null) { + channelTypeProvider.setChannelGroupType(groupTypeUID, Objects.requireNonNull(getType())); + } channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider)); } @@ -154,20 +165,31 @@ public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) { */ public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) { channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider)); - channelTypeProvider.removeChannelGroupType(getGroupTypeUID()); + ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID; + if (groupTypeUID != null) { + channelTypeProvider.removeChannelGroupType(groupTypeUID); + } + } + + public ChannelUID buildChannelUID(String channelID) { + final ChannelGroupUID groupUID = channelGroupUID; + if (groupUID != null) { + return new ChannelUID(groupUID, channelID); + } + return new ChannelUID(componentConfiguration.getThingUID(), channelID); } /** * Each HomeAssistant component corresponds to a Channel Group Type. */ - public ChannelGroupTypeUID getGroupTypeUID() { + public @Nullable ChannelGroupTypeUID getGroupTypeUID() { return channelGroupTypeUID; } /** * The unique id of this component. */ - public ChannelGroupUID getGroupUID() { + public @Nullable ChannelGroupUID getGroupUID() { return channelGroupUID; } @@ -175,7 +197,16 @@ public ChannelGroupUID getGroupUID() { * Component (Channel Group) name. */ public String getName() { - return channelConfiguration.getName(); + String result = channelConfiguration.getName(); + + Device device = channelConfiguration.getDevice(); + if (result == null && device != null) { + result = device.getName(); + } + if (result == null) { + result = haID.objectID; + } + return result; } /** @@ -207,11 +238,19 @@ public int getConfigHash() { /** * Return the channel group type. */ - public ChannelGroupType getType() { + public @Nullable ChannelGroupType getType() { + ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID; + if (groupTypeUID == null) { + return null; + } final List channelDefinitions = channels.values().stream().map(ComponentChannel::type) .collect(Collectors.toList()); - return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, getName()) - .withChannelDefinitions(channelDefinitions).build(); + return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions) + .build(); + } + + public List getChannels() { + return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList()); } /** @@ -225,8 +264,12 @@ public void resetState() { /** * Return the channel group definition for this component. */ - public ChannelGroupDefinition getGroupDefinition() { - return new ChannelGroupDefinition(channelGroupUID.getId(), getGroupTypeUID(), getName(), null); + public @Nullable ChannelGroupDefinition getGroupDefinition() { + ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID; + if (groupTypeUID == null) { + return null; + } + return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID, getName(), null); } public HaID getHaID() { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java index 8d213e7075cd8..5f60eec5fad53 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java @@ -74,24 +74,23 @@ public AlarmControlPanel(ComponentFactory.ComponentConfiguration componentConfig final String[] stateEnum = { channelConfiguration.stateDisarmed, channelConfiguration.stateArmedHome, channelConfiguration.stateArmedAway, channelConfiguration.statePending, channelConfiguration.stateTriggered }; - buildChannel(STATE_CHANNEL_ID, new TextValue(stateEnum), channelConfiguration.getName(), - componentConfiguration.getUpdateListener()) + buildChannel(STATE_CHANNEL_ID, new TextValue(stateEnum), getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())// .build(); String commandTopic = channelConfiguration.commandTopic; if (commandTopic != null) { buildChannel(SWITCH_DISARM_CHANNEL_ID, new TextValue(new String[] { channelConfiguration.payloadDisarm }), - channelConfiguration.getName(), componentConfiguration.getUpdateListener()) + getName(), componentConfiguration.getUpdateListener()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); buildChannel(SWITCH_ARM_HOME_CHANNEL_ID, - new TextValue(new String[] { channelConfiguration.payloadArmHome }), channelConfiguration.getName(), + new TextValue(new String[] { channelConfiguration.payloadArmHome }), getName(), componentConfiguration.getUpdateListener()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); buildChannel(SWITCH_ARM_AWAY_CHANNEL_ID, - new TextValue(new String[] { channelConfiguration.payloadArmAway }), channelConfiguration.getName(), + new TextValue(new String[] { channelConfiguration.payloadArmAway }), getName(), componentConfiguration.getUpdateListener()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java index c66119ff53bdf..bcf12f36087f7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java @@ -43,7 +43,7 @@ public Camera(ComponentFactory.ComponentConfiguration componentConfiguration) { ImageValue value = new ImageValue(); - buildChannel(CAMERA_CHANNEL_ID, value, channelConfiguration.getName(), - componentConfiguration.getUpdateListener()).stateTopic(channelConfiguration.topic).build(); + buildChannel(CAMERA_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) + .stateTopic(channelConfiguration.topic).build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index a2ad15e81b64a..5e68b56689ade 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -286,7 +286,7 @@ private ComponentChannel buildOptionalChannel(String channelId, Value valueState @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic, @Nullable Predicate commandFilter) { if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) { - return buildChannel(channelId, valueState, channelConfiguration.getName(), channelStateUpdateListener) + return buildChannel(channelId, valueState, getName(), channelStateUpdateListener) .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), commandTemplate) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java index ecfa3a48c6919..8191f16c2ec6f 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java @@ -56,8 +56,7 @@ public Cover(ComponentFactory.ComponentConfiguration componentConfiguration) { RollershutterValue value = new RollershutterValue(channelConfiguration.payloadOpen, channelConfiguration.payloadClose, channelConfiguration.payloadStop); - buildChannel(SWITCH_CHANNEL_ID, value, channelConfiguration.getName(), - componentConfiguration.getUpdateListener()) + buildChannel(SWITCH_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java index 092b898a695f6..8b90631d3a93a 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java @@ -281,7 +281,7 @@ public void updateChannelState(ChannelUID channel, State state) { colorValue.update(newOnState); } - listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), + listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), state.equals(OnOffType.ON) ? newOnState : HSBType.BLACK); } else if (brightnessChannel != null) { listener.updateChannelState(new ChannelUID(channel.getThingUID(), BRIGHTNESS_CHANNEL_ID), @@ -301,8 +301,7 @@ public void updateChannelState(ChannelUID channel, State state) { colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO, (PercentType) brightnessValue.getChannelState())); } - listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), - colorValue.getChannelState()); + listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); } else { listener.updateChannelState(channel, state); } @@ -330,13 +329,11 @@ public void updateChannelState(ChannelUID channel, State state) { HSBType xyColor = HSBType.fromXY(x, y); colorValue.update(new HSBType(xyColor.getHue(), xyColor.getSaturation(), brightness)); } - listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), - colorValue.getChannelState()); + listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); return; case RGB_CHANNEL_ID: colorValue.update((HSBType) state); - listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), - colorValue.getChannelState()); + listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); break; case RGBW_CHANNEL_ID: case RGBWW_CHANNEL_ID: diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java index 657483fc538a7..e3c914bef2d9e 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java @@ -65,8 +65,7 @@ public DeviceTrigger(ComponentFactory.ComponentConfiguration componentConfigurat value = new TextValue(); } - buildChannel(channelConfiguration.type, value, channelConfiguration.getName(), - componentConfiguration.getUpdateListener()) + buildChannel(channelConfiguration.type, value, getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java index 49be4ee9238c6..7d056ed10ca38 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java @@ -54,8 +54,7 @@ public Fan(ComponentFactory.ComponentConfiguration componentConfiguration) { super(componentConfiguration, ChannelConfiguration.class); OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff); - buildChannel(SWITCH_CHANNEL_ID, value, channelConfiguration.getName(), - componentConfiguration.getUpdateListener()) + buildChannel(SWITCH_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), channelConfiguration.commandTemplate) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java index 8650018426da4..5c89fc6e4dd4c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java @@ -286,12 +286,11 @@ public void updateChannelState(ChannelUID channel, State state) { colorModeValue.getChannelState()); if (hasColorChannel) { - listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), colorValue.getChannelState()); + listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); } else if (brightnessChannel != null) { - listener.updateChannelState(new ChannelUID(getGroupUID(), BRIGHTNESS_CHANNEL_ID), - brightnessValue.getChannelState()); + listener.updateChannelState(buildChannelUID(BRIGHTNESS_CHANNEL_ID), brightnessValue.getChannelState()); } else { - listener.updateChannelState(new ChannelUID(getGroupUID(), ON_OFF_CHANNEL_ID), onOffValue.getChannelState()); + listener.updateChannelState(buildChannelUID(ON_OFF_CHANNEL_ID), onOffValue.getChannelState()); } } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java index b50974a412aa5..ccfab925c9b48 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java @@ -58,8 +58,8 @@ public Lock(ComponentFactory.ComponentConfiguration componentConfiguration) { } buildChannel(SWITCH_CHANNEL_ID, - new OnOffValue(channelConfiguration.payloadLock, channelConfiguration.payloadUnlock), - channelConfiguration.getName(), componentConfiguration.getUpdateListener()) + new OnOffValue(channelConfiguration.payloadLock, channelConfiguration.payloadUnlock), getName(), + componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java index 947f46cc41a53..10519eb24312c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java @@ -82,8 +82,7 @@ public Number(ComponentFactory.ComponentConfiguration componentConfiguration) { NumberValue value = new NumberValue(channelConfiguration.min, channelConfiguration.max, channelConfiguration.step, UnitUtils.parseUnit(channelConfiguration.unitOfMeasurement)); - buildChannel(NUMBER_CHANNEL_ID, value, channelConfiguration.getName(), - componentConfiguration.getUpdateListener()) + buildChannel(NUMBER_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), channelConfiguration.commandTemplate) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java index 9ead86ef8af4b..d49dfbf1a7172 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java @@ -66,8 +66,7 @@ public Select(ComponentFactory.ComponentConfiguration componentConfiguration) { TextValue value = new TextValue(channelConfiguration.options); - buildChannel(SELECT_CHANNEL_ID, value, channelConfiguration.getName(), - componentConfiguration.getUpdateListener()) + buildChannel(SELECT_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), channelConfiguration.commandTemplate) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java index f9f8706b5b108..148a5b5e989ab 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java @@ -88,8 +88,7 @@ public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration) { boolean trigger = TRIGGER_ICONS.matcher(icon).matches(); - buildChannel(SENSOR_CHANNEL_ID, value, channelConfiguration.getName(), - getListener(componentConfiguration, value)) + buildChannel(SENSOR_CHANNEL_ID, value, getName(), getListener(componentConfiguration, value)) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())// .trigger(trigger).build(); } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java index 7fd9d6b901fb9..75de1517cf03d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java @@ -294,7 +294,7 @@ private ComponentChannel buildOptionalChannel(String channelId, Value valueState ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate, @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic) { if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) { - return buildChannel(channelId, valueState, channelConfiguration.getName(), channelStateUpdateListener) + return buildChannel(channelId, valueState, getName(), channelStateUpdateListener) .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), commandTemplate) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java index ae64e5d2445da..dec22608edb28 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/config/dto/AbstractChannelConfiguration.java @@ -33,8 +33,9 @@ @NonNullByDefault public abstract class AbstractChannelConfiguration { public static final char PARENT_TOPIC_PLACEHOLDER = '~'; + private static final String DEFAULT_THING_NAME = "Home Assistant Device"; - protected String name; + protected @Nullable String name; protected String icon = ""; protected int qos; // defaults to 0 according to HA specification @@ -93,6 +94,9 @@ public String getThingName() { if (result == null) { result = name; } + if (result == null) { + result = DEFAULT_THING_NAME; + } return result; } @@ -127,7 +131,7 @@ public Map appendToProperties(Map properties) { return properties; } - public String getName() { + public @Nullable String getName() { return name; } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java index 14612c5f67924..99a14d4abd9c4 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java @@ -18,6 +18,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -44,6 +45,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -53,7 +55,6 @@ import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.thing.type.ChannelDefinition; import org.openhab.core.thing.type.ChannelGroupDefinition; -import org.openhab.core.thing.type.ChannelGroupType; import org.openhab.core.thing.type.ThingType; import org.openhab.core.thing.util.ThingHelper; import org.slf4j.Logger; @@ -93,7 +94,7 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler protected final DiscoverComponents discoverComponents; private final Gson gson; - protected final Map> haComponents = new HashMap<>(); + protected final Map<@Nullable String, AbstractComponent> haComponents = new HashMap<>(); protected HandlerConfiguration config = new HandlerConfiguration(); private Set discoveryHomeAssistantIDs = new HashSet<>(); @@ -137,10 +138,6 @@ public void initialize() { for (Channel channel : thing.getChannels()) { final String groupID = channel.getUID().getGroupId(); - if (groupID == null) { - logger.warn("Channel {} has no groupd ID", channel.getLabel()); - continue; - } // Already restored component? @Nullable AbstractComponent component = haComponents.get(groupID); @@ -160,7 +157,12 @@ public void initialize() { try { component = ComponentFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, scheduler, gson, transformationServiceProvider); - haComponents.put(component.getGroupUID().getId(), component); + final ChannelGroupUID groupUID = component.getGroupUID(); + String id = null; + if (groupUID != null) { + id = groupUID.getId(); + } + haComponents.put(id, component); component.addChannelTypes(channelTypeProvider); } catch (ConfigurationException e) { logger.error("Cannot not restore component {}: {}", thing, e.getMessage()); @@ -228,9 +230,6 @@ protected void stop() { @Override public @Nullable ChannelState getChannelState(ChannelUID channelUID) { String groupID = channelUID.getGroupId(); - if (groupID == null) { - return null; - } AbstractComponent component; synchronized (haComponents) { // sync whenever discoverComponents is started component = haComponents.get(groupID); @@ -266,7 +265,12 @@ public void accept(List> discoveredComponentsList) { synchronized (haComponents) { // sync whenever discoverComponents is started for (AbstractComponent discovered : discoveredComponentsList) { - AbstractComponent known = haComponents.get(discovered.getGroupUID().getId()); + final ChannelGroupUID groupUID = discovered.getGroupUID(); + String id = null; + if (groupUID != null) { + id = groupUID.getId(); + } + AbstractComponent known = haComponents.get(id); // Is component already known? if (known != null) { if (discovered.getConfigHash() != known.getConfigHash()) { @@ -282,10 +286,10 @@ public void accept(List> discoveredComponentsList) { // Add channel and group types to the types registry discovered.addChannelTypes(channelTypeProvider); // Add component to the component map - haComponents.put(discovered.getGroupUID().getId(), discovered); + haComponents.put(id, discovered); // Start component / Subscribe to channel topics discovered.start(connection, scheduler, 0).exceptionally(e -> { - logger.warn("Failed to start component {}", discovered.getGroupUID(), e); + logger.warn("Failed to start component {}", discovered.getHaID(), e); return null; }); @@ -296,7 +300,7 @@ public void accept(List> discoveredComponentsList) { // We remove all conflicting old channels, they will be re-added below based on the new discovery logger.debug( "Received component {} with slightly different config. Making sure we re-create conflicting channels...", - discovered.getGroupUID()); + discovered.getHaID()); removeJustRediscoveredChannels(discoveredChannels); } @@ -346,9 +350,8 @@ private void updateThingType() { List channelDefs; synchronized (haComponents) { // sync whenever discoverComponents is started groupDefs = haComponents.values().stream().map(AbstractComponent::getGroupDefinition) - .collect(Collectors.toList()); - channelDefs = haComponents.values().stream().map(AbstractComponent::getType) - .map(ChannelGroupType::getChannelDefinitions).flatMap(List::stream) + .filter(Objects::nonNull).map(Objects::requireNonNull).collect(Collectors.toList()); + channelDefs = haComponents.values().stream().map(AbstractComponent::getChannels).flatMap(List::stream) .collect(Collectors.toList()); } ThingType thingType = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING) diff --git a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java index 4bf6be8374ce3..c514b3159e4ee 100644 --- a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java +++ b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -160,7 +161,8 @@ public void parseHATree() throws Exception { ComponentDiscovered cd = (haID, c) -> { haComponents.put(c.getGroupUID().getId(), c); c.addChannelTypes(channelTypeProvider); - channelTypeProvider.setChannelGroupType(c.getGroupTypeUID(), c.getType()); + channelTypeProvider.setChannelGroupType(Objects.requireNonNull(c.getGroupTypeUID()), + Objects.requireNonNull(c.getType())); latch.countDown(); }; From ec961c5d271d24f1b671b5027a7c718a0b79e9a4 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 18 Nov 2023 23:20:27 +0100 Subject: [PATCH 097/146] Fix openhab unit name (#15913) Signed-off-by: Jacob Laursen --- bundles/org.openhab.binding.bluetooth.bluez/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.bluetooth.bluez/README.md b/bundles/org.openhab.binding.bluetooth.bluez/README.md index 1a11b16491df9..d54fe306d7444 100644 --- a/bundles/org.openhab.binding.bluetooth.bluez/README.md +++ b/bundles/org.openhab.binding.bluetooth.bluez/README.md @@ -1,6 +1,7 @@ # Bluetooth BlueZ Adapter -This extension supports Bluetooth access via BlueZ and DBus on Linux. This is architecture agnostic and uses Unix Sockets. +This extension supports Bluetooth access via BlueZ and DBus on Linux. +This is architecture agnostic and uses Unix Sockets. # Setup @@ -33,7 +34,7 @@ Restart running services for changes to take effect. ```shell systemctl restart dbus systemctl restart bluetooth -systemctl restart openhab2 +systemctl restart openhab ``` ## Supported Things From 601c06f4f7ff164a37c228e93969f339f1437d8a Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sat, 18 Nov 2023 15:38:52 -0700 Subject: [PATCH 098/146] [mqtt.homeassistant] fix compilation problems (#15915) Due to #15427 and #14839 being developed independently, but merged to main successively Signed-off-by: Cody Cutrer --- .../homeassistant/internal/component/JSONSchemaLight.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java index 5c89fc6e4dd4c..8a270d01e01c7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java @@ -249,8 +249,7 @@ public void updateChannelState(ChannelUID channel, State state) { if (jsonState.colorTemp != null) { colorTempValue.update(new QuantityType(Objects.requireNonNull(jsonState.colorTemp), Units.MIRED)); - listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_TEMP_CHANNEL_ID), - colorTempValue.getChannelState()); + listener.updateChannelState(buildChannelUID(COLOR_TEMP_CHANNEL_ID), colorTempValue.getChannelState()); colorModeValue.update(new StringType(LightColorMode.COLOR_MODE_COLOR_TEMP.serializedName())); } @@ -282,8 +281,7 @@ public void updateChannelState(ChannelUID channel, State state) { colorModeValue.update(new StringType(jsonState.colorMode.serializedName())); } - listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_MODE_CHANNEL_ID), - colorModeValue.getChannelState()); + listener.updateChannelState(buildChannelUID(COLOR_MODE_CHANNEL_ID), colorModeValue.getChannelState()); if (hasColorChannel) { listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); From 7e7cb4c0f1095f69311ad1527f9d5a5f1d6df128 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sat, 18 Nov 2023 16:58:25 -0700 Subject: [PATCH 099/146] [mqtt.homeassistant] Handle empty device name (#15918) Follow on to #15427 ring-mqtt sends `"name": ""`, not `"name": null` or simply omitting it, so be sure to handle that way as well Signed-off-by: Cody Cutrer --- .../homeassistant/internal/component/AbstractComponent.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index 87625305d300b..ebe696ee01aa5 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -86,7 +86,8 @@ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig this.haID = componentConfiguration.getHaID(); - if (channelConfiguration.getName() != null) { + String name = channelConfiguration.getName(); + if (name != null && !name.isEmpty()) { String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId()); this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId); From c585de187447b4135c7716b59cb5ca596c387b4b Mon Sep 17 00:00:00 2001 From: Simonas Kazlauskas Date: Sun, 19 Nov 2023 14:59:32 +0000 Subject: [PATCH 100/146] [velbus] Review/rewrite documentation (#15537) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page was missing information in some places (e.g. some supported modules were not listed at all,) and some outdated information in others. Some examples were malformed, and some wording didn’t flow too well. The section on Channels is still pretty iffy, but I left this as an exercise for a future me or somebody else :) Signed-off-by: Simonas Kazlauskas --- bundles/org.openhab.binding.velbus/README.md | 262 +++++++++++++----- .../main/resources/OH-INF/config/config.xml | 8 +- .../resources/OH-INF/i18n/velbus.properties | 8 +- 3 files changed, 194 insertions(+), 84 deletions(-) diff --git a/bundles/org.openhab.binding.velbus/README.md b/bundles/org.openhab.binding.velbus/README.md index 2fc0d9fb1b507..873babc8d8f10 100644 --- a/bundles/org.openhab.binding.velbus/README.md +++ b/bundles/org.openhab.binding.velbus/README.md @@ -2,8 +2,12 @@ The Velbus binding integrates with a [Velbus](https://www.velbus.eu/) system through a Velbus configuration module (VMBRSUSB, VMB1USB or VMB1RS) or a network connection (TCP/IP). -The binding has been tested with a USB configuration module for universal mounting (VMB1USB). For optimal stability, the preferred configuration module is the VMBRSUSB module. +Consider deploying a TCP bridge – the officially developed [python-velbustcp] or one of the [third party projects][3rd-party-servers] – in between the configuration module and openHAB. +Doing so allows concurrent access to your Velbus system by both openHAB and the official configuration software. + +[python-velbustcp]: https://github.com/velbus/python-velbustcp +[3rd-party-servers]: https://github.com/StefCoene/velserver/wiki/TCP-server-for-Velbus The binding exposes basic actions from the Velbus System that can be triggered from the smartphone/tablet interface, as defined by the [Velbus Protocol info sheets](https://github.com/velbus). @@ -12,129 +16,231 @@ Pushbutton, temperature sensors and input module states are retrieved and made a ## Supported Things -A Velbus configuration module (e.g. VMBRSUSB) or a network server (e.g. [VelServer](https://github.com/StefCoene/velserver/wiki/TCP-server-for-Velbus)) is required as a "bridge" for accessing any other Velbus devices. +In addition to the bridge modules mentioned in the section above, the supported Velbus modules are: -The supported Velbus devices are: + + +| Type | Description | +|-------------|--------------------------------------------------------------------------------------------| +| vmb1bl | 1-channel blind control module for din rail | +| vmb1bls | 1-channel blind control module for universal mounting | +| vmb1dm | Dimmer module for inductive/resistive and capacitive load | +| vmb1led | 1-channel 0-10V controlled PWM dimmer for led strips | +| vmb1ry | 1-channel relay module | +| vmb1ryno | 1-channel relay module with potential-free changeover contact | +| vmb1rynos | 1-channel relay module with potential-free changeover contact | +| vmb1rys | 1-channel relay module with input | +| vmb1ts | Temperature Sensor Module | +| vmb2bl | 2-channel blind control module | +| vmb2ble | 2-channel blind control module with extended possibilities | +| vmb2pbn | Push-button interface for Niko 1- or 2-fold push-buttons | +| vmb4an | Analog I/O module | +| vmb4dc | 4-channel 0/1-10V dimmer controller | +| vmb4pb | 4 button interface module | +| vmb4ry | 4-channel relay module | +| vmb4ryld | 4-channel relay module with voltage outputs | +| vmb4ryno | 4-channel relay module with potential-free contacts | +| vmb6in | 6-channel input module | +| vmb6pbn | Push-button interface module for Niko 4- or 6-fold push-button | +| vmb7in | 7-channel input module (potentialfree + pulse) | +| vmb8ir | Infrared remote control receiver module | +| vmb8pb | 8-Channel Push Button module | +| vmb8pbu | Push-button interface with 8 channels for universal mounting | +| vmbdme | Dimmer for electronic/resistive load | +| vmbdmir | Single channel triac dimmer for resistive and inductive loads | +| vmbdmi | Single channel triac dimmer for resistive and inductive loads | +| vmbel1 | Edge-lit one touch button module | +| vmbel2 | Edge-lit two touch buttons module | +| vmbel4 | Edge-lit four touch buttons module | +| vmbelo | Edge-lit touch panel with Oled display | +| vmbelpir | Edge-lit Motion detector with one touch button | +| vmbgp1-2 | Glass control module with 1 touch key (Edition 2) | +| vmbgp1 | Glass control module with 1 touch key | +| vmbgp2-2 | Glass control module with 2 touch keys (Edition 2) | +| vmbgp2 | Glass control module with 2 touch keys | +| vmbgp4-2 | Glass control module with 4 touch keys (Edition 2) | +| vmbgp4 | Glass control module with 4 touch keys | +| vmbgp4pir-2 | Glass control module with 4 touch keys and built-in motion and twilight sensor (Edition 2) | +| vmbgp4pir | Glass control module with 4 touch keys and built-in motion and twilight sensor | +| vmbgpod-2 | Glass control module with oled display and temperature controller (Edition 2) | +| vmbgpod | Glass control module with oled display and temperature controller | +| vmbgpo | Glass control module with oled display | +| vmbin | 1-channel input module | +| vmbkp | Keypad interface module | +| vmbmeteo | Weather station with thermometer, anemometer, rain sensor and light sensor | +| vmbpirc | Motion and twilight sensor for ceiling mounting | +| vmbpirm | Mini motion and twilight sensor for recessed or surface mounting | +| vmbpiro | Outdoor motion, twilight and temperature sensor, Theben | +| vmbrfr8s | 8 channel RF receiver module | +| vmbvp1 | Doorbird interface module | ## Discovery The Velbus bridge cannot be discovered automatically. -It has to be added manually by defining the serial port of the Velbus Configuration module for the Velbus Serial Bridge or by defining the IP Address and port for the Velbus Network Bridge. +Configure it manually by defining the serial port of the Velbus Configuration module for the Velbus Serial Bridge or by defining the IP address and port for the Velbus Network Bridge, as described in the [`Thing Configuration`](#thing-configuration) section. -Once the bridge has been added as a thing, a manual scan can be launched to discover all other supported Velbus devices on the bus. -These devices will be available in the inbox. -The discovery scan will also retrieve the channel names of the Velbus devices. +Once the bridge has been configured with openHAB, a manual scan can be initiated to discover Velbus modules with an assigned address. +Addresses can be assigned via the official configuration software, and is a required step before a Velbus installation can work correctly. + +The discovery scan can take a few minutes to complete. +Modules discovered during this scan will appear in the inbox. This procedure will also retrieve the channel names of the Velbus devices. ## Thing Configuration The Velbus bridge needs to be added first. +### Velbus Serial Bridge + For the Velbus Serial Bridge it is necessary to specify the serial port device used for communication. -On Linux systems, this will usually be either `/dev/ttyS0`, `/dev/ttyUSB0` or `/dev/ttyACM0` (or a higher number than `0` if multiple devices are present). -On Windows it will be `COM1`, `COM2`, etc. -In the things file, this might look e.g. like +On Linux and other UNIX systems, it is recommended to use a more stable symbolic device path such as `/dev/serial/by-id/usb-Velleman_Projects_VMB1USB_Velbus_USB_interface-if00`, as it will always refer at to a Velbus configuration module, and not an arbitrary serial device. +If this is not a concern or an option, it is valid to refer to the serial device directly with a path such as `/dev/ttyS0`, `/dev/ttyUSB0` or `/dev/ttyACM0` (or a number other than `0` if multiple serial devices are connected.) + +On Windows `port` will refer to one of the COM devices such as `COM1`, `COM2`, etc. +The Device Manager system utility can be used to determine the exact COM port number to use. + +In a `.things` file, a USB connection to a Velbus configuration module might be configured like so: ```java +Bridge velbus:bridge:1 [ port="/dev/serial/by-id/usb-Velleman_Projects_VMB1USB_Velbus_USB_interface-if00" ] +// or Bridge velbus:bridge:1 [ port="COM1" ] ``` -For the Velbus Network Bridge it is necessary to specify the IP Address or hostname and the port of the Velbus network server. -This will usually be either the loopback address `127.0.0.1`, and port number. -Or the specific IP of the machine `10.0.0.110` , and port number. +### Velbus Network Bridge + +For the Velbus Network Bridge it is necessary to specify the address (either an IP address or a hostname) and the port of a Velbus network server. -In the things file, this might look like +In a `.things` file, a network bridge running on the same machine at port 6000 would be configured like so: ```java -Bridge velbus:networkbridge:1 "Velbus Network Bridge - Loopback" @ "Control" [ address="127.0.0.1", port=6000 ] +Bridge velbus:networkbridge:1 "Velbus Network Bridge - Loopback" @ "Control" [ address="localhost", port=6000 ] ``` -Optionally, both the serial bridge and the network bridge can also update the realtime clock, date and daylight savings status of the Velbus modules. -This is achieved by setting the Time Update Interval (in minutes) on the bridge, e.g.: +### Realtime Clock Synchronization + +Optionally, the openHAB Velbus binding can synchronize the realtime clock, date and daylight savings status of the Velbus modules. +This is achieved by setting the Time Update Interval (in minutes) on the bridge thing. For example: ```java Bridge velbus:bridge:1 [ port="COM1", timeUpdateInterval="360" ] ``` -The default time update interval is every 360 minutes. -Setting the interval to 0 minutes or leaving it empty disables the update of the realtime clock, date and daylight savings status of the Velbus modules. +If `timeUpdateInterval` is not specified, the time will be updated every 360 minutes by default. +In order to disable this behaviour, set the interval to 0 or an empty string. -In case of a connection error, the bridges can also try to reconnect automatically. -You can specify at which interval the bridge should try to reconnect by setting the Reconnection Interval (in seconds), e.g.: +### Reconnection + +In case of a connection error, a Velbus bridge will attempt to reconnect every 15 seconds by default. +You can modify the bridge reconnection interval by specifying the `reconnectionInterval` parameter (in seconds): ```java Bridge velbus:bridge:1 [ port="COM1", reconnectionInterval="15" ] ``` -The default reconnection interval is 15 seconds. +### Velbus modules -For the other Velbus devices, the thing configuration has the following syntax: +Adding Velbus modules to your openHAB configuration follows the conventions of your preferred configuration method. -```java -Thing velbus::: "Label" @ "Location" [CH1="Kitchen Light", CH2="Living Light"] -``` +* **UI-based configuration:** Invoke a manual scan from the Things menu in order to start the [discovery process](#discovery). +Discovered modules can be found in the inbox. +* **Textual `.thing` configuration** can declare Velbus modules either in a standalone fashion (a bridge is still required): -or nested in the bridge configuration: + ```java + Thing velbus::: "Label" @ "Location" [ CH1="Kitchen Light", CH2="Living Light" ] + ``` -```java - "Label" @ "Location" [CH1="Kitchen Light", CH2="Living Light"] -``` - -The following thing types are valid for configuration: + Or, more concisely, by nesting modules within the `Bridge` they’re connected to: -```text -vmb1bl, vmb1bls, vmb1dm, vmb1led, vmb1ry, vmb1ryno, vmb1rynos, vmb1rys, vmb1ts, vmb2bl, vmb2ble, vmb2pbn, vmb4an, vmb4dc, vmb4ry, vmb4ryld, vmb4ryno, vmb6in, vmb6pbn, vmb7in, vmb8ir, vmb8pb, vmb8pbu, vmbdme, vmbdmi, vmbdmir, vmbel1, vmbel2, vmbel4, vmbelo, vmbelpir, vmbgp1, vmbgp2, vmbgp4, vmbgp4pir, vmbgpo, vmbgpod, vmbmeteo, vmbpirc, vmbpirm, vmbpiro, vmbvp1 -``` + ```java + Bridge velbus:bridge:1 [ port="COM1" ] { + "Label" @ "Location" [ CH1="Kitchen Light", CH2="Living Light" ] + } + ``` -`thingId` is the hexadecimal Velbus address of the thing. + Here: -`"Label"` is an optional label for the thing. + * `` is the type of the Velbus module. Refer to the [Supported Things](#supported-things) table for valid `` values; + * `` is the hexadecimal address of the Velbus module; + * `"Label"` is an optional label for the thing; + * `@ "Location"` is an optional specification of the location of the thing; + * The `CHx="..."` properties are optional and can be used to specify names of the module channels. -`@ "Location"` is optional, and represents the location of the thing. +Individual module `Thing`’s channels can be linked to openHAB items via channel names like `velbus:vmb4ryld:1:0A:CH1`. Here, from left to right, the channel name consistes of the binding name, module type (` = vmb4ryld`), bridge id (`1`), module’s hexadecimal address (` = 0A`) and channel within the module (`CH1`). -`[CHx="..."]` is optional, and represents the name of channel x, e.g. CH1 specifies the name of channel 1. +#### Additional properties -For thing types with builtin sensors (e.g. temperature), the interval at which the sensors should be checked can be set by specifying the Refresh Interval, e.g.: +Some module types have additional functionality not represented well by the trigger channels. +A prime example of this is a temperature sensor, measurements of which must be polled. -```java -Thing velbus:vmbelo:: [refresh="300"] -``` +The following table lists these additional properties and the modules that support the corresponding property: -The default refresh interval for the sensors is 300 seconds. -Setting the refresh interval to 0 or leaving it empty will prevent the thing from periodically refreshing the sensor values. + + +| Property | Supported modules | Description | +|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| `counter1PulseMultiplier` | `vmb7in` | The pulse multiplier for counter 1 | +| `counter1Unit` | `vmb7in` | The unit for Counter 1. | +| `counter2PulseMultiplier` | `vmb7in` | The pulse multiplier for counter 2 | +| `counter2Unit` | `vmb7in` | The unit for Counter 2. | +| `counter3PulseMultiplier` | `vmb7in` | The pulse multiplier for counter 3 | +| `counter3Unit` | `vmb7in` | The unit for Counter 3. | +| `counter4PulseMultiplier` | `vmb7in` | The pulse multiplier for counter 4 | +| `counter4Unit` | `vmb7in` | The unit for Counter 4. | +| `dimspeed` | `vmb1dm`, `vmb1led`, `vmb4dc`, `vmbdme`, `vmbdmi`, `vmbdmir` | The time (in seconds) needed for dimming from 0 to 100%. | +| `refresh` | `vmb1ts`, `vmb4an`, `vmb7in`, `vmbel1`, `vmbel2`, `vmbel4`, `vmbelpir`, `vmbgp1`, `vmbgp1-2`, `vmbgp2`, `vmbgp2-2`, `vmbgp4`, `vmbgp4-2`, `vmbgp4pir`, `vmbgp4pir-2`, `vmbmeteo`, `vmbpirc`, `vmbpirm`, `vmbpiro` | Refresh interval for sensors or counters (in seconds), default 300. If set to 0 or left empty, no refresh will be scheduled. | ## Channels @@ -192,7 +298,7 @@ For thing type `vmb4ry` 4 channels are available `CH1` ... `CH4`. OnOff command types are supported. Thing types `vmbel1`, `vmbel2`, `vmbel4`, `vmbelpir`, `vmbgp1`, `vmbgp2`, `vmbgp4`, `vmbgp4pir` and `vmbpiro` have 8 trigger channels `input:CH1` ... `input:CH8` and one temperature channel `input:CH9`. -Pressed and Long_Pressed command types are supported on channels `button#CH1` and `button#CH2` for the thing type `vmbelpir`. +Pressed and Long_Pressed command types are supported on channels `button#CH1` and `button#CH2` for the thing type `vmbelpir`. Pressed and Long_Pressed command types are supported on channels `button#CH1` ... `button#CH8` for the thing types `vmbel1`, `vmbel2`, `vmbel4`, `vmbgp1`, `vmbgp2`, `vmbgp4`, `vmbgp4pir` and `vmbpiro`. The thing types `vmbel1` and `vmbgp1` have one channel to steer the button LED feedback `feedback:CH1`. The thing types `vmbel2` and `vmbgp2` have two channels to steer the button LED feedback `feedback:CH1` and `feedback:CH2`. @@ -222,10 +328,14 @@ Go to the Items list, select the Item, add a State Description Metadata, and set ## Full Example + + .things: ```java -Bridge velbus:bridge:1 [ port="COM1"] { +Bridge velbus:bridge:1 [ port="COM1" ] { vmb2ble 01 vmb2pbn 02 vmb6pbn 03 @@ -246,14 +356,14 @@ Bridge velbus:bridge:1 [ port="COM1"] { .items: ```java -Switch LivingRoom {channel="velbus:vmb4ryld:1:06:CH1"} # Switch for onOff type action -Switch KitchenButton {velbus:vmb2pbn:1:05:button#CH1} # Switch for Pressed and Long_Pressed type actions -Dimmer TVRoom {channel="velbus:vmb4dc:1:07:CH2"} # Changing brightness dimmer type action -Rollershutter Kitchen {channel="velbus:vmb2ble:1:01"} # Controlling rollershutter or blind type action - -Number Temperature_LivingRoom "Temperature [%.1f °C]" channel="velbus:vmbgp1:1:08:CH09"} -Number Temperature_Corridor "Temperature [%.1f °C]" channel="velbus:vmbgpo:1:0C:CH33"} -Number Temperature_Outside "Temperature [%.1f °C]" channel="velbus:vmbpiro:1:0E:CH09"} +Switch LivingRoom { channel="velbus:vmb4ryld:1:06:CH1" } # Switch for onOff type action +Switch KitchenButton { channel="velbus:vmb2pbn:1:05:button#CH1" } # Switch for Pressed and Long_Pressed type actions +Dimmer TVRoom { channel="velbus:vmb4dc:1:07:CH2" } # Changing brightness dimmer type action +Rollershutter Kitchen { channel="velbus:vmb2ble:1:01" } # Controlling rollershutter or blind type action + +Number Temperature_LivingRoom "Temperature [%.1f °C]" { channel="velbus:vmbgp1:1:08:CH09" } +Number Temperature_Corridor "Temperature [%.1f °C]" { channel="velbus:vmbgpo:1:0C:CH33" } +Number Temperature_Outside "Temperature [%.1f °C]" { channel="velbus:vmbpiro:1:0E:CH09" } ``` .sitemap: diff --git a/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/config/config.xml index 24eab60c4db61..cd7ab69b0cab9 100644 --- a/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/config/config.xml @@ -333,7 +333,7 @@ - The pulse multiplier for counter 1 + The pulse multiplier for counter 1. 1 @@ -356,7 +356,7 @@ - The pulse multiplier for counter 2 + The pulse multiplier for counter 2. 1 @@ -379,7 +379,7 @@ - The pulse multiplier for counter 3 + The pulse multiplier for counter 3. 1 @@ -402,7 +402,7 @@ - The pulse multiplier for counter 4 + The pulse multiplier for counter 4. 1 diff --git a/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/i18n/velbus.properties b/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/i18n/velbus.properties index 5fb7cab2e160e..fba843337328c 100644 --- a/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/i18n/velbus.properties +++ b/bundles/org.openhab.binding.velbus/src/main/resources/OH-INF/i18n/velbus.properties @@ -309,7 +309,7 @@ thing-type.config.velbus.7channelDeviceWithCounters.CH7.description = The name o thing-type.config.velbus.7channelDeviceWithCounters.address.label = Address thing-type.config.velbus.7channelDeviceWithCounters.address.description = The velbus address of the device thing-type.config.velbus.7channelDeviceWithCounters.counter1PulseMultiplier.label = Counter 1 Pulse Multiplier -thing-type.config.velbus.7channelDeviceWithCounters.counter1PulseMultiplier.description = The pulse multiplier for counter 1 +thing-type.config.velbus.7channelDeviceWithCounters.counter1PulseMultiplier.description = The pulse multiplier for counter 1. thing-type.config.velbus.7channelDeviceWithCounters.counter1PulseMultiplier.option.1 = x1 thing-type.config.velbus.7channelDeviceWithCounters.counter1PulseMultiplier.option.2.5 = x2.5 thing-type.config.velbus.7channelDeviceWithCounters.counter1PulseMultiplier.option.0.05 = x0.05 @@ -320,7 +320,7 @@ thing-type.config.velbus.7channelDeviceWithCounters.counter1Unit.option.kWh = kW thing-type.config.velbus.7channelDeviceWithCounters.counter1Unit.option.liters = liters thing-type.config.velbus.7channelDeviceWithCounters.counter1Unit.option.m³ = m³ thing-type.config.velbus.7channelDeviceWithCounters.counter2PulseMultiplier.label = Counter 2 Pulse Multiplier -thing-type.config.velbus.7channelDeviceWithCounters.counter2PulseMultiplier.description = The pulse multiplier for counter 2 +thing-type.config.velbus.7channelDeviceWithCounters.counter2PulseMultiplier.description = The pulse multiplier for counter 2. thing-type.config.velbus.7channelDeviceWithCounters.counter2PulseMultiplier.option.1 = x1 thing-type.config.velbus.7channelDeviceWithCounters.counter2PulseMultiplier.option.2.5 = x2.5 thing-type.config.velbus.7channelDeviceWithCounters.counter2PulseMultiplier.option.0.05 = x0.05 @@ -331,7 +331,7 @@ thing-type.config.velbus.7channelDeviceWithCounters.counter2Unit.option.kWh = kW thing-type.config.velbus.7channelDeviceWithCounters.counter2Unit.option.liters = liters thing-type.config.velbus.7channelDeviceWithCounters.counter2Unit.option.m³ = m³ thing-type.config.velbus.7channelDeviceWithCounters.counter3PulseMultiplier.label = Counter 3 Pulse Multiplier -thing-type.config.velbus.7channelDeviceWithCounters.counter3PulseMultiplier.description = The pulse multiplier for counter 3 +thing-type.config.velbus.7channelDeviceWithCounters.counter3PulseMultiplier.description = The pulse multiplier for counter 3. thing-type.config.velbus.7channelDeviceWithCounters.counter3PulseMultiplier.option.1 = x1 thing-type.config.velbus.7channelDeviceWithCounters.counter3PulseMultiplier.option.2.5 = x2.5 thing-type.config.velbus.7channelDeviceWithCounters.counter3PulseMultiplier.option.0.05 = x0.05 @@ -342,7 +342,7 @@ thing-type.config.velbus.7channelDeviceWithCounters.counter3Unit.option.kWh = kW thing-type.config.velbus.7channelDeviceWithCounters.counter3Unit.option.liters = liters thing-type.config.velbus.7channelDeviceWithCounters.counter3Unit.option.m³ = m³ thing-type.config.velbus.7channelDeviceWithCounters.counter4PulseMultiplier.label = Counter 4 Pulse Multiplier -thing-type.config.velbus.7channelDeviceWithCounters.counter4PulseMultiplier.description = The pulse multiplier for counter 4 +thing-type.config.velbus.7channelDeviceWithCounters.counter4PulseMultiplier.description = The pulse multiplier for counter 4. thing-type.config.velbus.7channelDeviceWithCounters.counter4PulseMultiplier.option.1 = x1 thing-type.config.velbus.7channelDeviceWithCounters.counter4PulseMultiplier.option.2.5 = x2.5 thing-type.config.velbus.7channelDeviceWithCounters.counter4PulseMultiplier.option.0.05 = x0.05 From 340fd3c1822998c42dee4a082600f9101606e625 Mon Sep 17 00:00:00 2001 From: Giovanni Fabiani Date: Sun, 19 Nov 2023 16:57:13 +0100 Subject: [PATCH 101/146] [openwebnet] Energy: add totalizers channels for energy kWh consumed for today and current month (#15837) * feat(energy): get current day and current month totalizers * fixed unit (Power:Energy), refreshPeriod config and scheduler --------- Signed-off-by: Giovanni Fabiani Co-authored-by: Conte Andrea --- .../org.openhab.binding.openwebnet/README.md | 31 +++-- .../internal/OpenWebNetBindingConstants.java | 3 + .../handler/OpenWebNetEnergyHandler.java | 116 ++++++++++++++++-- .../OH-INF/i18n/openwebnet.properties | 6 + .../resources/OH-INF/thing/BusEnergyMeter.xml | 9 +- .../main/resources/OH-INF/thing/channels.xml | 16 +++ .../main/resources/OH-INF/update/update.xml | 10 ++ 7 files changed, 167 insertions(+), 24 deletions(-) diff --git a/bundles/org.openhab.binding.openwebnet/README.md b/bundles/org.openhab.binding.openwebnet/README.md index ab2ce17790a8e..472d37c187f01 100644 --- a/bundles/org.openhab.binding.openwebnet/README.md +++ b/bundles/org.openhab.binding.openwebnet/README.md @@ -137,6 +137,7 @@ For any manually added device, you must configure: - dry Contact or IR Interface `99`: add `3` before --> `where="399"` - energy meter F520/F521 numbered `1`: add `5` before --> `where="51"` - energy meter F522/F523 numbered `4`: add `7` before and `#0` after --> `where="74#0"` + - energy meter F520/F521 the `energyRefreshPeriod` configuration parameter sets the number of minutes (the minimum value is 30, the maximum value is 1440) between refreshes for energy totalizers (default: 30 minutes) --> `energyRefreshPeriod` = 35 - alarm zone `2` --> `where="2"` - example for Zigbee devices: `where=765432101#9`. The ID of the device (ADDR part) is usually written in hexadecimal on the device itself, for example `ID 0074CBB1`: convert to decimal (`7654321`) and add `01#9` at the end to obtain `where=765432101#9`. For 2-unit switch devices (`zb_on_off_switch2u`), last part should be `00#9`. @@ -208,15 +209,17 @@ OPEN command to execute: *5*8#134## ### Lighting, Automation, Basic/CEN/CEN+ Scenario Events, Dry Contact / IR Interfaces, Power and Aux channels -| Channel Type ID (channel ID) | Applies to Thing Type IDs | Item Type | Description | Read/Write | +| Channel Type ID (channel ID) | Applies to Thing Type IDs | Item Type | Description | Read/Write | |-----------------------------------------|---------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------|:-----------:| -| `switch` or `switch_01`/`02` for Zigbee | `bus_on_off_switch`, `zb_on_off_switch`, `zb_on_off_switch2u` | Switch | To switch the device `ON` and `OFF` | R/W | -| `brightness` | `bus_dimmer`, `zb_dimmer` | Dimmer | To adjust the brightness value (Percent, `ON`, `OFF`) | R/W | -| `shutter` | `bus_automation` | Rollershutter | To activate roller shutters (`UP`, `DOWN`, `STOP`, Percent - [see Shutter position](#shutter-position)) | R/W | -| `scenario`   | `bus_scenario_control` | String | Trigger channel for Basic scenario events [see possible values](#scenario-channels) | R (TRIGGER) | -| `button#X` | `bus_cen_scenario_control`, `bus_cenplus_scenario_control` | String | Trigger channel for CEN/CEN+ scenario events [see possible values](#scenario-channels) | R (TRIGGER) | -| `sensor` | `bus_dry_contact_ir` | Switch | Indicates if a Dry Contact Interface is `ON`/`OFF`, or if an IR Sensor is detecting movement (`ON`), or not (`OFF`) | R | -| `power` | `bus_energy_meter` | Number:Power | The current active power usage from Energy Meter | R | +| `switch` or `switch_01`/`02` for Zigbee | `bus_on_off_switch`, `zb_on_off_switch`, `zb_on_off_switch2u` | Switch | To switch the device `ON` and `OFF` | R/W | +| `brightness` | `bus_dimmer`, `zb_dimmer` | Dimmer | To adjust the brightness value (Percent, `ON`, `OFF`) | R/W | +| `shutter` | `bus_automation` | Rollershutter | To activate roller shutters (`UP`, `DOWN`, `STOP`, Percent - [see Shutter position](#shutter-position)) | R/W | +| `scenario`   | `bus_scenario_control` | String | Trigger channel for Basic scenario events [see possible values](#scenario-channels) | R (TRIGGER) | +| `button#X` | `bus_cen_scenario_control`, `bus_cenplus_scenario_control` | String | Trigger channel for CEN/CEN+ scenario events [see possible values](#scenario-channels) | R (TRIGGER) | +| `sensor` | `bus_dry_contact_ir` | Switch | Indicates if a Dry Contact Interface is `ON`/`OFF`, or if an IR Sensor is detecting movement (`ON`), or not (`OFF`) | R | +| `power` | `bus_energy_meter` | Number:Power | The current active power usage from Energy Meter | R | +| `energyToday` | `bus_energy_meter` | Number:Energy | Current day energy totalizer | R | +| `energyThisMonth` | `bus_energy_meter` | Number:Energy | Current month energy totalizer | R | | `aux` | `bus_aux` | String | Possible commands: `ON`, `OFF`, `TOGGLE`, `STOP`, `UP`, `DOWN`, `ENABLED`, `DISABLED`, `RESET_GEN`, `RESET_BI`, `RESET_TRI`. Only `ON` and `OFF` are supported for now | R/W | ### Alarm channels @@ -368,6 +371,10 @@ Rollershutter iLR_shutter "Shutter [%.0f %%]" (g Number:Power iCENTRAL_Ta "Power [%.0f %unit%]" { channel="openwebnet:bus_energy_meter:mybridge:CENTRAL_Ta:power" } Number:Power iCENTRAL_Tb "Power [%.0f %unit%]" { channel="openwebnet:bus_energy_meter:mybridge:CENTRAL_Tb:power" } +Number:Energy iCENTRAL_Ta_day "Energy Day [%.1f]" { channel="openwebnet:bus_energy_meter:mybridge:CENTRAL_Ta:energyToday"} +Number:Energy iCENTRAL_Tb_day "Energy Day [%.1f]" { channel="openwebnet:bus_energy_meter:mybridge:CENTRAL_Tb:energyToday"} +Number:Energy iCENTRAL_Ta_month "Energy Month [%.1f]" { channel="openwebnet:bus_energy_meter:mybridge:CENTRAL_Ta:energyThisMonth"} +Number:Energy iCENTRAL_Tb_month "Energy Month [%.1f]" { channel="openwebnet:bus_energy_meter:mybridge:CENTRAL_Tb:energyThisMonth"} // 99 zones thermo central unit Group gCentralUnit "Thermo Central Unit" @@ -434,8 +441,12 @@ sitemap openwebnet label="OpenWebNet Binding Example Sitemap" Frame label="Energy Meters" icon="energy" { - Default item=iCENTRAL_Ta label="General" icon="energy" valuecolor=[>3000="red"] - Default item=iCENTRAL_Tb label="Ground Floor" icon="energy" valuecolor=[>3000="red"] + Default item=iCENTRAL_Ta label="General" icon="energy" valuecolor=[>3000="red"] + Default item=iCENTRAL_Tb label="Ground Floor" icon="energy" valuecolor=[>3000="red"] + Default item=CENTRAL_Ta_day label="General Energy Today" icon="energy" valuecolor=[>3000="blue"] + Default item=CENTRAL_Tb_day label="Ground Floor Energy Today" icon="energy" valuecolor=[>3000="blue"] + Default item=CENTRAL_Ta_month label="General Energy This Month" icon="energy" valuecolor=[>3000="yellow"] + Default item=CENTRAL_Tb_month label="Ground Floor Energy This Month" icon="energy" valuecolor=[>3000="yellow"] } Frame label="Living Room Thermo" diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java index 69e69633494a2..21328a4174925 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java @@ -157,6 +157,8 @@ public class OpenWebNetBindingConstants { public static final String CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL = "atLeastOneProbeManual"; // energy management public static final String CHANNEL_POWER = "power"; + public static final String CHANNEL_ENERGY_TOTALIZER_DAY = "energyToday"; + public static final String CHANNEL_ENERGY_TOTALIZER_MONTH = "energyThisMonth"; // scenario button channels public static final String CHANNEL_SCENARIO_BUTTON = "button#"; public static final String CHANNEL_TYPE_CEN_BUTTON_EVENT = "cenButtonEvent"; @@ -180,6 +182,7 @@ public class OpenWebNetBindingConstants { public static final String CONFIG_PROPERTY_SHUTTER_RUN = "shutterRun"; public static final String CONFIG_PROPERTY_SCENARIO_BUTTONS = "buttons"; public static final String CONFIG_PROPERTY_STANDALONE = "standAlone"; + public static final String CONFIG_PROPERTY_REFRESH_PERIOD = "energyRefreshPeriod"; // gw config properties public static final String CONFIG_PROPERTY_HOST = "host"; diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java index 8b7a03e4d9cda..cdb3a638f5c12 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.openwebnet.internal.handler; +import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_ENERGY_TOTALIZER_DAY; +import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_ENERGY_TOTALIZER_MONTH; import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_POWER; import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import javax.measure.quantity.Energy; import javax.measure.quantity.Power; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -49,7 +52,7 @@ * device. It extends the abstract {@link OpenWebNetThingHandler}. * * @author Massimo Valla - Initial contribution - * @author Andrea Conte - Energy management + * @author Andrea Conte, Giovanni Fabiani - Energy management */ @NonNullByDefault public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler { @@ -57,8 +60,11 @@ public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler { private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class); public static final Set SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES; - public static final int ENERGY_SUBSCRIPTION_PERIOD = 10; // minutes - private @Nullable ScheduledFuture notificationSchedule; + private static final int POWER_SUBSCRIPTION_PERIOD = 10; // MINUTES + private int energyRefreshPeriod; // MINUTES + + private @Nullable ScheduledFuture powerSchedule; + private @Nullable ScheduledFuture energySchedule; public OpenWebNetEnergyHandler(Thing thing) { super(thing); @@ -69,6 +75,14 @@ public OpenWebNetEnergyHandler(Thing thing) { @Override public void initialize() { super.initialize(); + try { + Object refreshPeriodConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_REFRESH_PERIOD); + energyRefreshPeriod = Integer.parseInt(refreshPeriodConfig.toString()); + } catch (NumberFormatException e) { + logger.debug("NumberFormatException caught while parsing OpenWebNetEnergyHandler configuration: {}", + e.getMessage()); + energyRefreshPeriod = 30; + } // In order to get data from the probe we must send a command over the bus, this could be done only when the // bridge is online. @@ -83,6 +97,7 @@ public void initialize() { if (gw != null && gw.isConnected()) { // bridge is online subscribeToActivePowerChanges(); + subscribeToEnergyTotalizer(); } } } @@ -94,26 +109,27 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { // subscribe the scheduler only after the bridge is online if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) { subscribeToActivePowerChanges(); + subscribeToEnergyTotalizer(); } } private void subscribeToActivePowerChanges() { - notificationSchedule = scheduler.scheduleWithFixedDelay(() -> { + powerSchedule = scheduler.scheduleWithFixedDelay(() -> { if (isFirstSchedulerLaunch) { logger.debug( "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min", - deviceWhere, ENERGY_SUBSCRIPTION_PERIOD); + deviceWhere, POWER_SUBSCRIPTION_PERIOD); } else { logger.debug( "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification", - ENERGY_SUBSCRIPTION_PERIOD, deviceWhere); + POWER_SUBSCRIPTION_PERIOD, deviceWhere); } Where w = deviceWhere; if (w == null) { logger.warn("subscribeToActivePowerChanges() WHERE=null. Skipping"); } else { try { - send(EnergyManagement.setActivePowerNotificationsTime(w.value(), ENERGY_SUBSCRIPTION_PERIOD)); + send(EnergyManagement.setActivePowerNotificationsTime(w.value(), POWER_SUBSCRIPTION_PERIOD)); isFirstSchedulerLaunch = false; } catch (Exception e) { if (isFirstSchedulerLaunch) { @@ -127,15 +143,40 @@ private void subscribeToActivePowerChanges() { } } } - }, 0, ENERGY_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES); + }, 0, POWER_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES); + } + + private void subscribeToEnergyTotalizer() { + Where w = deviceWhere; + if (w == null) { + logger.warn("subscribeToEnergyTotalizer() WHERE=null. Skipping"); + return; + } + energySchedule = scheduler.scheduleWithFixedDelay(() -> { + try { + send(EnergyManagement.requestCurrentDayTotalizer(w.value())); + send(EnergyManagement.requestCurrentMonthTotalizer(w.value())); + } catch (Exception e) { + logger.warn( + "subscribeToEnergyTotalizer() Could not subscribe to totalizers scheduler for WHERE={}. Exception={}", + w, e.getMessage()); + } + }, 0, energyRefreshPeriod, TimeUnit.MINUTES); } @Override public void dispose() { - if (notificationSchedule != null) { - ScheduledFuture ns = notificationSchedule; - ns.cancel(false); - logger.debug("dispose() scheduler stopped."); + ScheduledFuture sfp = powerSchedule; + if (sfp != null) { + sfp.cancel(false); + powerSchedule = null; + logger.debug("dispose() power scheduler stopped."); + } + ScheduledFuture sfe = energySchedule; + if (sfe != null) { + sfe.cancel(false); + energySchedule = null; + logger.debug("dispose() energy scheduler stopped."); } super.dispose(); } @@ -152,6 +193,8 @@ protected void requestChannelState(ChannelUID channel) { if (w != null) { try { send(EnergyManagement.requestActivePower(w.value())); + send(EnergyManagement.requestCurrentDayTotalizer(w.value())); + send(EnergyManagement.requestCurrentMonthTotalizer(w.value())); } catch (OWNException e) { logger.debug("Exception while requesting state for channel {}: {} ", channel, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -163,6 +206,8 @@ protected void requestChannelState(ChannelUID channel) { protected void refreshDevice(boolean refreshAll) { logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID()); requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_POWER)); + requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTALIZER_DAY)); + requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTALIZER_MONTH)); } @Override @@ -187,6 +232,10 @@ protected void handleMessage(BaseOpenMessage msg) { // fix: check for correct DIM (ActivePower / 113) if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.ACTIVE_POWER)) { updateActivePower(msg); + } else if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.PARTIAL_TOTALIZER_CURRENT_DAY)) { + updateCurrentDayTotalizer(msg); + } else if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.PARTIAL_TOTALIZER_CURRENT_MONTH)) { + updateCurrentMonthTotalizer(msg); } else { logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.", msg); @@ -198,7 +247,6 @@ protected void handleMessage(BaseOpenMessage msg) { * Updates energy power state based on an EnergyManagement message received from the OWN network * * @param msg the EnergyManagement message received - * @throws FrameException */ private void updateActivePower(BaseOpenMessage msg) { Integer activePower; @@ -208,6 +256,48 @@ private void updateActivePower(BaseOpenMessage msg) { } catch (FrameException e) { logger.warn("FrameException on frame {}: {}", msg, e.getMessage()); updateState(CHANNEL_POWER, UnDefType.UNDEF); + } catch (NumberFormatException e) { + logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage()); + updateState(CHANNEL_POWER, UnDefType.UNDEF); + } + } + + /** + * Updates current day totalizer + * + * @param msg the EnergyManagement message received + */ + private void updateCurrentDayTotalizer(BaseOpenMessage msg) { + Double currentDayEnergy; + try { + currentDayEnergy = Double.parseDouble(msg.getDimValues()[0]) / 1000d; + updateState(CHANNEL_ENERGY_TOTALIZER_DAY, new QuantityType(currentDayEnergy, Units.KILOWATT_HOUR)); + } catch (FrameException e) { + logger.warn("FrameException on frame {}: {}", msg, e.getMessage()); + updateState(CHANNEL_ENERGY_TOTALIZER_DAY, UnDefType.UNDEF); + } catch (NumberFormatException e) { + logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage()); + updateState(CHANNEL_ENERGY_TOTALIZER_DAY, UnDefType.UNDEF); + } + } + + /** + * Updates current month totalizer + * + * @param msg the EnergyManagement message received + */ + private void updateCurrentMonthTotalizer(BaseOpenMessage msg) { + Double currentMonthEnergy; + try { + currentMonthEnergy = Double.parseDouble(msg.getDimValues()[0]) / 1000d; + updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, + new QuantityType(currentMonthEnergy, Units.KILOWATT_HOUR)); + } catch (FrameException e) { + logger.warn("FrameException on frame {}: {}", msg, e.getMessage()); + updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, UnDefType.UNDEF); + } catch (NumberFormatException e) { + logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage()); + updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, UnDefType.UNDEF); } } } diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties index 54e20a79f3177..596ad75497d3d 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties @@ -72,6 +72,8 @@ thing-type.config.openwebnet.bus_dimmer.where.label = OpenWebNet Address (where) thing-type.config.openwebnet.bus_dimmer.where.description = Example: A/PL address: A=1 PL=3 --> where="13". On local bus: where="13#4#01" thing-type.config.openwebnet.bus_dry_contact_ir.where.label = OpenWebNet Address (where) thing-type.config.openwebnet.bus_dry_contact_ir.where.description = A "3" must be added before the address. Automation Dry Contacts 3N with N=[1-201]; example N=60 --> where="360". Alarm Dry Contacts and IR sensors 3ZN with Zone Z=[1-9], N=[1-9]; example Z=4, N=5 --> where="345" +thing-type.config.openwebnet.bus_energy_meter.energyRefreshPeriod.label = Energy Totalizers Refresh Period +thing-type.config.openwebnet.bus_energy_meter.energyRefreshPeriod.description = Sets the number of minutes between refreshes for energy totalizers (default: 30 minutes) thing-type.config.openwebnet.bus_energy_meter.where.label = OpenWebNet Address (where) thing-type.config.openwebnet.bus_energy_meter.where.description = A "5" must be added before the energy meter address: 5N with N=[1-255]. Example energy meter "42" --> where="542" thing-type.config.openwebnet.bus_gateway.dateTimeSynch.label = Date Time Synchronisation @@ -185,6 +187,10 @@ channel-type.openwebnet.conditioningValves.state.option.OFF_SPEED_2 = Off speed channel-type.openwebnet.conditioningValves.state.option.OFF_SPEED_3 = Off speed 3 channel-type.openwebnet.dryContactIR.label = Sensor channel-type.openwebnet.dryContactIR.description = Dry Contact Interface or IR Interface sensor movement (read only) +channel-type.openwebnet.energyThisMonth.label = Energy This Month +channel-type.openwebnet.energyThisMonth.description = Total energy measured for this month +channel-type.openwebnet.energyToday.label = Energy Today +channel-type.openwebnet.energyToday.description = Total energy measured for today channel-type.openwebnet.failureDiscovered.label = Failure Discovered channel-type.openwebnet.failureDiscovered.description = Central Unit Failure Discovered (read only) channel-type.openwebnet.function.label = Thermo Function diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusEnergyMeter.xml b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusEnergyMeter.xml index 4dbc8bc787b19..c6aab79e93d89 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusEnergyMeter.xml +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusEnergyMeter.xml @@ -15,22 +15,29 @@ + + BTicino/Legrand BTI-F52x 1830 + 1 ownId - A "5" must be added before the energy meter address: 5N with N=[1-255]. Example energy meter "42" --> where="542" + + + Sets the number of minutes between refreshes for energy totalizers (default: 30 minutes) + 30 + diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/channels.xml index d502160cb8181..1d0fba1a1ad9d 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/channels.xml @@ -343,6 +343,22 @@ + + Number:Energy + + Total energy measured for this month + Energy + + + + + Number:Energy + + Total energy measured for today + Energy + + + trigger diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/update/update.xml b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/update/update.xml index 900a8bd44c526..87a1a34d12d4c 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/update/update.xml +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/update/update.xml @@ -10,4 +10,14 @@ + + + + openwebnet:energyToday + + + openwebnet:energyThisMonth + + + From f25b0d0fb93c425baca74828e07338d3cc729cb2 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sun, 19 Nov 2023 09:06:54 -0700 Subject: [PATCH 102/146] [mqtt.homeassistant] Add support for Button component (#15892) * [mqtt.homeassistant] Add support for Button component * use a StringValue instead of an OnOffValue --------- Signed-off-by: Cody Cutrer --- .../internal/component/Button.java | 58 +++++++++++++++ .../internal/component/ComponentFactory.java | 2 + .../component/AbstractComponentTests.java | 9 +++ .../internal/component/ButtonTests.java | 72 +++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java new file mode 100644 index 0000000000000..97aa60f01b056 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.homeassistant.internal.component; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; + +import com.google.gson.annotations.SerializedName; + +/** + * An MQTT button, following the https://www.home-assistant.io/integrations/button.mqtt/ specification. + * + * @author Cody Cutrer - Initial contribution + */ +@NonNullByDefault +public class Button extends AbstractComponent { + public static final String BUTTON_CHANNEL_ID = "button"; + + /** + * Configuration class for MQTT component + */ + static class ChannelConfiguration extends AbstractChannelConfiguration { + ChannelConfiguration() { + super("MQTT Button"); + } + + protected @Nullable Boolean optimistic; + + @SerializedName("command_topic") + protected @Nullable String commandTopic; + + @SerializedName("payload_press") + protected String payloadPress = "PRESS"; + } + + public Button(ComponentFactory.ComponentConfiguration componentConfiguration) { + super(componentConfiguration, ChannelConfiguration.class); + + TextValue value = new TextValue(new String[] { channelConfiguration.payloadPress }); + + buildChannel(BUTTON_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) + .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), + channelConfiguration.getQos()) + .build(); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java index 9855eec9c49ce..6c95d5eeddecc 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java @@ -57,6 +57,8 @@ public static AbstractComponent createComponent(ThingUID thingUID, HaID haID, return new AlarmControlPanel(componentConfiguration); case "binary_sensor": return new BinarySensor(componentConfiguration); + case "button": + return new Button(componentConfiguration); case "camera": return new Camera(componentConfiguration); case "cover": diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index fc5d8c3cf1369..e2d88086db786 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -230,6 +230,15 @@ protected void assertNotPublished(String mqttTopic, String payload) { ArgumentMatchers.eq(payload.getBytes(StandardCharsets.UTF_8)), anyInt(), anyBoolean()); } + /** + * Assert that nothing was published on given topic. + * + * @param mqttTopic Mqtt topic + */ + protected void assertNothingPublished(String mqttTopic) { + verify(bridgeConnection, never()).publish(eq(mqttTopic), any(), anyInt(), anyBoolean()); + } + /** * Publish payload to all subscribers on specified topic. * diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java new file mode 100644 index 0000000000000..943a6c4b0f275 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.core.library.types.StringType; + +/** + * Tests for {@link Button} + * + * @author Cody Cutrer - Initial contribution + */ +@NonNullByDefault +public class ButtonTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "button/0x847127fffe11dd6a_auto_lock_zigbee2mqtt"; + + @SuppressWarnings("null") + @Test + public void testButton() { + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """ + { + "dev_cla":"restart", + "name":"Restart", + "entity_category":"config", + "cmd_t":"esphome/single-car-gdo/button/restart/command", + "avty_t":"esphome/single-car-gdo/status", + "uniq_id":"78e36d645710-button-ba0e8e32", + "dev":{ + "ids":"78e36d645710", + "name":"Single Car Garage Door Opener", + "sw":"esphome v2023.10.4 Nov 1 2023, 09:27:02", + "mdl":"esp32dev", + "mf":"espressif"} + } + """); + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("Restart")); + + assertChannel(component, Button.BUTTON_CHANNEL_ID, "", "esphome/single-car-gdo/button/restart/command", + "Restart", TextValue.class); + + assertThrows(IllegalArgumentException.class, + () -> component.getChannel(Button.BUTTON_CHANNEL_ID).getState().publishValue(new StringType("ON"))); + assertNothingPublished("esphome/single-car-gdo/button/restart/command"); + component.getChannel(Button.BUTTON_CHANNEL_ID).getState().publishValue(new StringType("PRESS")); + assertPublished("esphome/single-car-gdo/button/restart/command", "PRESS"); + } + + @Override + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} From 238a38cd3aaa51db3156f1b310793a11f87d5c81 Mon Sep 17 00:00:00 2001 From: M Valla <12682715+mvalla@users.noreply.github.com> Date: Sun, 19 Nov 2023 17:26:27 +0100 Subject: [PATCH 103/146] [openwebnet] fix CEN/CEN+ scenario control buttons param descriptions (#15924) Signed-off-by: Massimo Valla --- .../src/main/resources/OH-INF/i18n/openwebnet.properties | 4 ++-- .../main/resources/OH-INF/thing/BusCENPlusScenarioControl.xml | 2 +- .../src/main/resources/OH-INF/thing/BusCENScenarioControl.xml | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties index 596ad75497d3d..c73164cc12480 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties @@ -61,11 +61,11 @@ thing-type.config.openwebnet.bus_automation.where.description = Example: A/PL ad thing-type.config.openwebnet.bus_aux.where.label = OpenWebNet Address (where) thing-type.config.openwebnet.bus_aux.where.description = Example: AUX 1 --> where="1" thing-type.config.openwebnet.bus_cen_scenario_control.buttons.label = Configured Buttons -thing-type.config.openwebnet.bus_cen_scenario_control.buttons.description = List (comma separated) of buttons numbers [0-31] configured for this scenario device. Example: buttons=1,2,4 +thing-type.config.openwebnet.bus_cen_scenario_control.buttons.description = List (comma separated) of buttons numbers [0-31] configured for this scenario device. Example: buttons="1,2,4" thing-type.config.openwebnet.bus_cen_scenario_control.where.label = OpenWebNet Address (where) thing-type.config.openwebnet.bus_cen_scenario_control.where.description = Example: A/PL address: A=1 PL=3 --> where="13". On local bus: where="13#4#01" thing-type.config.openwebnet.bus_cenplus_scenario_control.buttons.label = Configured Buttons -thing-type.config.openwebnet.bus_cenplus_scenario_control.buttons.description = List (comma separated) of buttons numbers [0-31] configured for this scenario device, example: buttons=1,2,4 +thing-type.config.openwebnet.bus_cenplus_scenario_control.buttons.description = List (comma separated) of buttons numbers [0-31] configured for this scenario device, example: buttons="1,2,4" thing-type.config.openwebnet.bus_cenplus_scenario_control.where.label = OpenWebNet Address (where) thing-type.config.openwebnet.bus_cenplus_scenario_control.where.description = Use 2+N[0-2047]. Example: scenario control 5 --> where="25" thing-type.config.openwebnet.bus_dimmer.where.label = OpenWebNet Address (where) diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENPlusScenarioControl.xml b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENPlusScenarioControl.xml index bcebc86de1501..420b38208a519 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENPlusScenarioControl.xml +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENPlusScenarioControl.xml @@ -26,7 +26,7 @@ List (comma separated) of buttons numbers [0-31] configured for this scenario device, example: - buttons=1,2,4 + buttons="1,2,4" diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENScenarioControl.xml b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENScenarioControl.xml index 1c2d3274ee1d2..375baa1fd3c0c 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENScenarioControl.xml +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/thing/BusCENScenarioControl.xml @@ -27,7 +27,8 @@ List (comma separated) of buttons numbers [0-31] configured for this scenario device. Example: - buttons=1,2,4 + buttons="1,2,4" + From be994de81fd795edb17ee55eb668149fdae5b181 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sun, 19 Nov 2023 09:52:51 -0700 Subject: [PATCH 104/146] [mqtt.homeassistant] implement effect channel for light (#15914) Signed-off-by: Cody Cutrer --- .../component/DefaultSchemaLight.java | 5 +-- .../internal/component/JSONSchemaLight.java | 29 +++++++++++++++ .../internal/component/Light.java | 11 ++++-- .../component/DefaultSchemaLightTests.java | 36 +++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java index 8b90631d3a93a..676d6ddd2cbc7 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java @@ -98,8 +98,9 @@ protected void buildChannels() { .build(); } - if (channelConfiguration.effectStateTopic != null || channelConfiguration.effectCommandTopic != null) { - buildChannel(EFFECT_CHANNEL_ID, effectValue, "Lighting effect", this) + if (effectValue != null + && (channelConfiguration.effectStateTopic != null || channelConfiguration.effectCommandTopic != null)) { + buildChannel(EFFECT_CHANNEL_ID, Objects.requireNonNull(effectValue), "Lighting Effect", this) .stateTopic(channelConfiguration.effectStateTopic, channelConfiguration.effectValueTemplate) .commandTopic(channelConfiguration.effectCommandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java index 8a270d01e01c7..93dcd606a6958 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java @@ -114,6 +114,22 @@ protected void buildChannels() { onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, onOffValue, "On/Off State", this) .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build(); } + + if (effectValue != null) { + buildChannel(EFFECT_CHANNEL_ID, Objects.requireNonNull(effectValue), "Lighting Effect", this) + .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleEffectCommand(command)).build(); + + } + } + + private boolean handleEffectCommand(Command command) { + if (command instanceof StringType) { + JSONState json = new JSONState(); + json.state = "ON"; + json.effect = command.toString(); + publishState(json); + } + return false; } @Override @@ -151,6 +167,10 @@ protected void publishState(HSBType state) { } } + publishState(json); + } + + private void publishState(JSONState json) { String command = getGson().toJson(json); logger.debug("Publishing new state '{}' of light {} to MQTT.", command, getName()); rawChannel.getState().publishValue(new StringType(command)); @@ -224,6 +244,15 @@ public void updateChannelState(ChannelUID channel, State state) { return; } + if (effectValue != null) { + if (jsonState.effect != null) { + effectValue.update(new StringType(jsonState.effect)); + listener.updateChannelState(buildChannelUID(EFFECT_CHANNEL_ID), effectValue.getChannelState()); + } else { + listener.updateChannelState(buildChannelUID(EFFECT_CHANNEL_ID), UnDefType.NULL); + } + } + if (jsonState.state != null) { onOffValue.update(onOffValue.parseCommand(new StringType(jsonState.state))); if (brightnessValue.getChannelState() instanceof UnDefType) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java index 782268cdab047..c0c41ef34a20c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java @@ -47,7 +47,7 @@ * three different schemas. * * As of now, only on/off, brightness, and RGB are fully implemented and tested. - * HS and XY are implemented, but not tested. Color temp and effect are only + * HS and XY are implemented, but not tested. Color temp is only * implemented (but not tested) for the default schema. * * @author David Graeff - Initial contribution @@ -246,7 +246,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { protected OnOffValue onOffValue; protected PercentageValue brightnessValue; protected final NumberValue colorTempValue; - protected final TextValue effectValue = new TextValue(); + protected final @Nullable TextValue effectValue; protected final ColorValue colorValue = new ColorValue(ColorMode.HSB, null, null, 100); protected final List hiddenChannels = new ArrayList<>(); @@ -281,6 +281,13 @@ protected Light(ComponentFactory.ComponentConfiguration builder) { brightnessValue = new PercentageValue(null, new BigDecimal(channelConfiguration.brightnessScale), null, null, null); @Nullable + List effectList = channelConfiguration.effectList; + if (effectList != null) { + effectValue = new TextValue(effectList.toArray(new String[0])); + } else { + effectValue = null; + } + @Nullable BigDecimal min = null, max = null; if (channelConfiguration.minMireds != null) { min = new BigDecimal(channelConfiguration.minMireds); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLightTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLightTests.java index a5b092b01e779..150d9e4f15f15 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLightTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLightTests.java @@ -27,11 +27,13 @@ import org.openhab.binding.mqtt.generic.values.ColorValue; import org.openhab.binding.mqtt.generic.values.OnOffValue; import org.openhab.binding.mqtt.generic.values.PercentageValue; +import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StringType; /** * Tests for {@link Light} confirming to the default schema @@ -285,6 +287,40 @@ public void testOnOffOnly() throws InterruptedException { assertPublished("zigbee2mqtt/light/set/state", "OFF_"); } + @Test + public void testOnOffWithEffect() throws InterruptedException { + // @formatter:off + var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + "{ " + + " \"name\": \"light\", " + + " \"state_topic\": \"zigbee2mqtt/light/state\", " + + " \"command_topic\": \"zigbee2mqtt/light/set/state\", " + + " \"effect_command_topic\": \"zigbee2mqtt/light/set/effect\", " + + " \"state_value_template\": \"{{ value_json.power }}\", " + + " \"effect_list\": [\"party\", \"rainbow\"]," + + " \"effect_state_topic\": \"zigbee2mqtt/light/effect\"" + + "}"); + // @formatter:on + + assertThat(component.channels.size(), is(2)); + assertThat(component.getName(), is("light")); + + assertChannel(component, Light.ON_OFF_CHANNEL_ID, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", + "On/Off State", OnOffValue.class); + assertChannel(component, Light.EFFECT_CHANNEL_ID, "zigbee2mqtt/light/effect", "zigbee2mqtt/light/set/effect", + "Lighting Effect", TextValue.class); + + publishMessage("zigbee2mqtt/light/state", "{\"power\": \"ON\"}"); + assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON); + publishMessage("zigbee2mqtt/light/effect", "party"); + assertState(component, Light.EFFECT_CHANNEL_ID, new StringType("party")); + publishMessage("zigbee2mqtt/light/state", "{\"power\": \"OFF\"}"); + assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF); + + sendCommand(component, Light.EFFECT_CHANNEL_ID, new StringType("rainbow")); + assertPublished("zigbee2mqtt/light/set/effect", "rainbow"); + } + @Override protected Set getConfigTopics() { return Set.of(CONFIG_TOPIC); From 9f44bfb6311c066bc4cfba3c02430508c0ebe89e Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sun, 19 Nov 2023 09:54:33 -0700 Subject: [PATCH 105/146] [mqtt.homeassistant] Add support for Scene component (#15916) Signed-off-by: Cody Cutrer --- .../internal/component/ComponentFactory.java | 2 + .../internal/component/Scene.java | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java index 6c95d5eeddecc..3246c3789480c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java @@ -75,6 +75,8 @@ public static AbstractComponent createComponent(ThingUID thingUID, HaID haID, return new Lock(componentConfiguration); case "number": return new Number(componentConfiguration); + case "scene": + return new Scene(componentConfiguration); case "select": return new Select(componentConfiguration); case "sensor": diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java new file mode 100644 index 0000000000000..1d323c2defe15 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.homeassistant.internal.component; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; + +import com.google.gson.annotations.SerializedName; + +/** + * A MQTT scene, following the https://www.home-assistant.io/integrations/scene.mqtt/ specification. + * + * @author Cody Cutrer - Initial contribution + */ +@NonNullByDefault +public class Scene extends AbstractComponent { + public static final String SCENE_CHANNEL_ID = "scene"; + + /** + * Configuration class for MQTT component + */ + static class ChannelConfiguration extends AbstractChannelConfiguration { + ChannelConfiguration() { + super("MQTT Scene"); + } + + @SerializedName("command_topic") + protected @Nullable String commandTopic; + + @SerializedName("payload_on") + protected String payloadOn = "ON"; + } + + public Scene(ComponentFactory.ComponentConfiguration componentConfiguration) { + super(componentConfiguration, ChannelConfiguration.class); + + TextValue value = new TextValue(new String[] { channelConfiguration.payloadOn }); + + buildChannel(SCENE_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) + .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), + channelConfiguration.getQos()) + .build(); + } +} From 1e2e1d852f16339f0273032c8e14659ed8df332a Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Mon, 20 Nov 2023 12:30:32 +0100 Subject: [PATCH 106/146] New Crowdin updates (#15931) * New translations openwebnet.properties (Italian) * New translations jsscripting.properties (Danish) * New translations hdpowerview.properties (Danish) --- .../OH-INF/i18n/jsscripting_da.properties | 19 +++++++++++-------- .../OH-INF/i18n/hdpowerview_da.properties | 3 +-- .../OH-INF/i18n/openwebnet_it.properties | 10 ++++++++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.automation.jsscripting/src/main/resources/OH-INF/i18n/jsscripting_da.properties b/bundles/org.openhab.automation.jsscripting/src/main/resources/OH-INF/i18n/jsscripting_da.properties index d9089c4992453..ab0d30d15d9d1 100644 --- a/bundles/org.openhab.automation.jsscripting/src/main/resources/OH-INF/i18n/jsscripting_da.properties +++ b/bundles/org.openhab.automation.jsscripting/src/main/resources/OH-INF/i18n/jsscripting_da.properties @@ -1,12 +1,15 @@ +# add-on + +addon.jsscripting.name = JavaScript Scripting +addon.jsscripting.description = Dette tilføjer en JS (ECMAScript-2021) script-motor. + +# add-on + +automation.config.jsscripting.injectionCachingEnabled.label = Cache openHAB JavaScript-bibliotek injektion +automation.config.jsscripting.injectionCachingEnabled.description = Brug det medfølgende openHAB JavaScript-bibliotek for optimal ydeevne.
Deaktivér denne indstilling for at tillade indlæsning af biblioteket fra den lokale brugerkonfigurerede mappe "automation/js/node_modules". Anvendelse af et bibliotek leveret af brugeren, kan resultere i øgede indlæsningstider, specielt på mindre kraftfulde systemer. +automation.config.jsscripting.injectionCachingEnabled.option.true = Cache injektion af bibliotek +automation.config.jsscripting.injectionCachingEnabled.option.false = Cache ikke injektion af bibliotek automation.config.jsscripting.injectionEnabled.label = Brug indbyggede globale variabler automation.config.jsscripting.injectionEnabled.description = Importer alle variabler fra openHAB JavaScript-biblioteket ind i alle regler for fælles tjenester som items, things, actions, log, etc...
Hvis deaktiveret, kan openHAB JavaScript-biblioteket importeres manuelt med "require('openhab')" automation.config.jsscripting.injectionEnabled.option.true = Brug indbyggede variabler automation.config.jsscripting.injectionEnabled.option.false = Brug ikke indbyggede variabler -automation.config.jsscripting.useIncludedLibrary.label = Brug inkluderet openHAB JavaScript-bibliotek -automation.config.jsscripting.useIncludedLibrary.description = Brug det medfølgende openHAB JavaScript-bibliotek for optimal ydeevne.
Deaktivér denne indstilling for at tillade indlæsning af biblioteket fra den lokale brugerkonfigurerede mappe "automation/js/node_modules". Anvendelse af et bibliotek leveret af brugeren, kan resultere i øgede indlæsningstider, specielt på mindre kraftfulde systemer. -automation.config.jsscripting.useIncludedLibrary.option.true = Brug inkluderet bibliotek -automation.config.jsscripting.useIncludedLibrary.option.false = Brug ikke inkluderet bibliotek - -# service - -service.automation.jsscripting.label = JavaScript Scripting diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties index 0511e730800ca..ef816c7596e92 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties @@ -11,8 +11,7 @@ thing-type.hdpowerview.hub.label = PowerView Hub thing-type.hdpowerview.hub.description = Luxaflex PowerView Hub thing-type.hdpowerview.repeater.label = PowerView repeater thing-type.hdpowerview.repeater.description = Luxaflex PowerView repeater -thing-type.hdpowerview.repeater.channel.brightness.description = Styrer lysstyrken på LED-ringen -thing-type.hdpowerview.repeater.channel.color.description = Styrer farven på LED-ringen +thing-type.hdpowerview.repeater.channel.color.description = Styrer farven og lysstyrken på LED-ringen thing-type.hdpowerview.shade.label = PowerView gardin thing-type.hdpowerview.shade.description = Luxaflex PowerView Gen 1/2-gardin thing-type.hdpowerview.shade.channel.hubRssi.label = Hub RSSI diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_it.properties b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_it.properties index 9e862b1cae901..61cbca514641c 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_it.properties +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet_it.properties @@ -61,17 +61,19 @@ thing-type.config.openwebnet.bus_automation.where.description = Esempio\: indiri thing-type.config.openwebnet.bus_aux.where.label = Indirizzo OpenWebNet (where) thing-type.config.openwebnet.bus_aux.where.description = Esempio\: AUX 1 --> dove\="1" thing-type.config.openwebnet.bus_cen_scenario_control.buttons.label = Pulsanti Configurati -thing-type.config.openwebnet.bus_cen_scenario_control.buttons.description = Elenco (separato da virgola) dei pulsanti numerati [0-31] configurati per questo dispositivo di controllo scenari. Esempio\: buttons\=1,2,4 +thing-type.config.openwebnet.bus_cen_scenario_control.buttons.description = Elenco (separato da virgola) dei pulsanti numero [0-31] configurati per questo dispositivo di scenario. Esempio\: buttons\="1,2,4" thing-type.config.openwebnet.bus_cen_scenario_control.where.label = Indirizzo OpenWebNet (where) thing-type.config.openwebnet.bus_cen_scenario_control.where.description = Esempio\: indirizzo A/PL\: A\=1 PL\=3 --> dove\="13". Sul bus locale\: dove\="13\#4\#01" thing-type.config.openwebnet.bus_cenplus_scenario_control.buttons.label = Pulsanti Configurati -thing-type.config.openwebnet.bus_cenplus_scenario_control.buttons.description = Elenco (separato da virgola) dei numeri dei pulsanti [0-31] configurati per questo dispositivo di controllo scenari, esempio\: buttons\=1,2,4 +thing-type.config.openwebnet.bus_cenplus_scenario_control.buttons.description = Elenco (separato da virgola) dei numeri di pulsanti [0-31] configurati per questo dispositivo di scenario, esempio\: buttons\="1,2,4" thing-type.config.openwebnet.bus_cenplus_scenario_control.where.label = Indirizzo OpenWebNet (where) thing-type.config.openwebnet.bus_cenplus_scenario_control.where.description = Usa 2+N[0-2047]. Esempio\: controllo di scenario 5 --> dove\="25" thing-type.config.openwebnet.bus_dimmer.where.label = Indirizzo OpenWebNet (where) thing-type.config.openwebnet.bus_dimmer.where.description = Esempio\: indirizzo A/PL\: A\=1 PL\=3 --> dove\="13". Sul bus locale\: dove\="13\#4\#01" thing-type.config.openwebnet.bus_dry_contact_ir.where.label = Indirizzo OpenWebNet (where) thing-type.config.openwebnet.bus_dry_contact_ir.where.description = Un "3" deve essere aggiunto prima dell'indirizzo. Automazione a secco Contatti 3N con N\=[1-201]; esempio N\=60 --> dove\="360". Contatti asciutti di allarme e sensori IR 3ZN con Zona Z\=[1-9], N\=[1-9]; esempio Z\=4, N\=5 --> dove\="345" +thing-type.config.openwebnet.bus_energy_meter.energyRefreshPeriod.label = Periodo Aggiornamento Totalizzatori Energia +thing-type.config.openwebnet.bus_energy_meter.energyRefreshPeriod.description = Imposta il numero di minuti tra gli aggiornamenti per i totalizzatori di energia (predefinito\: 30 minuti) thing-type.config.openwebnet.bus_energy_meter.where.label = Indirizzo OpenWebNet (where) thing-type.config.openwebnet.bus_energy_meter.where.description = Un "5" deve essere aggiunto prima dell'indirizzo del contatore di energia\: 5N con N\=[1-255]. Esempio di misuratore di energia "42" --> dove\="542" thing-type.config.openwebnet.bus_gateway.dateTimeSynch.label = Sincronizzazione Data Ora @@ -185,6 +187,10 @@ channel-type.openwebnet.conditioningValves.state.option.OFF_SPEED_2 = Spento vel channel-type.openwebnet.conditioningValves.state.option.OFF_SPEED_3 = Spento velocità 3 channel-type.openwebnet.dryContactIR.label = Sensore channel-type.openwebnet.dryContactIR.description = Interfaccia per sensore a contatto pulito o interfaccia per sensore di movimento infrarosso (sola lettura) +channel-type.openwebnet.energyThisMonth.label = Energia Questo Mese +channel-type.openwebnet.energyThisMonth.description = Energia totale misurata per questo mese +channel-type.openwebnet.energyToday.label = Energia Oggi +channel-type.openwebnet.energyToday.description = Energia totale misurata per oggi channel-type.openwebnet.failureDiscovered.label = Guasto channel-type.openwebnet.failureDiscovered.description = Guasto rilevato per la Centrale (sola lettura) channel-type.openwebnet.function.label = Funzionamento From 5c8285c2ef9619ccf6ff8f61ed98ed8e64c54ab5 Mon Sep 17 00:00:00 2001 From: David Pace Date: Mon, 20 Nov 2023 20:59:52 +0100 Subject: [PATCH 107/146] [boschshc] Update active profile of intrusion detection system (#15899) * [boschshc] Update active profile of intrusion detection system Fixes an issue that caused the active configuration profile of the intrusion detection system not to be updated. closes #15848 * [boschshc] add channel to control state service registration Signed-off-by: David Pace --- .../internal/devices/intrusion/IntrusionDetectionHandler.java | 3 ++- .../devices/intrusion/IntrusionDetectionHandlerTest.java | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandler.java index 8941c8db18e15..a8c5a49c20533 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandler.java @@ -93,7 +93,7 @@ protected void initializeServices() throws BoschSHCException { CHANNEL_ACTIVE_CONFIGURATION_PROFILE), true); this.registerService(intrusionDetectionControlStateService, this::updateChannels, - List.of(CHANNEL_ARMING_STATE)); + List.of(CHANNEL_ARMING_STATE, CHANNEL_ACTIVE_CONFIGURATION_PROFILE)); this.registerService(surveillanceAlarmService, this::updateChannels, List.of(CHANNEL_ALARM_STATE)); this.registerStatelessService(armActionService); this.registerStatelessService(disarmActionService); @@ -110,6 +110,7 @@ private void updateChannels(IntrusionDetectionSystemState systemState) { private void updateChannels(IntrusionDetectionControlState controlState) { super.updateState(CHANNEL_ARMING_STATE, new StringType(controlState.value.toString())); + super.updateState(CHANNEL_ACTIVE_CONFIGURATION_PROFILE, new StringType(controlState.activeProfile)); } private void updateChannels(SurveillanceAlarmState surveillanceAlarmState) { diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandlerTest.java index 5f69b925ebf5a..1e318efc7c48b 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandlerTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandlerTest.java @@ -161,6 +161,9 @@ void testUpdateChannelsIntrusionDetectionControlState() { verify(getCallback()).stateUpdated( new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ARMING_STATE), new StringType("SYSTEM_ARMING")); + verify(getCallback()).stateUpdated( + new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACTIVE_CONFIGURATION_PROFILE), + new StringType("0")); } @Test From 10907e9ae2c17022a9ab8a66470937158391b9a4 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Mon, 20 Nov 2023 23:18:46 +0100 Subject: [PATCH 108/146] Rename price element to price component (#15935) Signed-off-by: Jacob Laursen --- .../README.md | 14 ++--- .../action/EnergiDataServiceActions.java | 52 +++++++++---------- .../OH-INF/i18n/energidataservice.properties | 4 +- .../action/EnergiDataServiceActionsTest.java | 4 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/bundles/org.openhab.binding.energidataservice/README.md b/bundles/org.openhab.binding.energidataservice/README.md index 8b717e19d9fc4..cf9972e47d6ef 100644 --- a/bundles/org.openhab.binding.energidataservice/README.md +++ b/bundles/org.openhab.binding.energidataservice/README.md @@ -40,7 +40,7 @@ To obtain the Global Location Number of your grid company: For customers using electricity for heating, a reduced electricity tax rate may apply after consuming the first 4000 kWh within a year. When you are entitled to reduced electricity tax, this option should be set. -This will ensure that thing action calculations use the reduced electricity tax rate when price elements are not explicitly provided. +This will ensure that thing action calculations use the reduced electricity tax rate when price components are not explicitly provided. It will not impact channels, see [Electricity Tax](#electricity-tax) for further information. ## Channels @@ -170,7 +170,7 @@ Historic prices older than 24 hours are removed from the JSON array each hour. ## Thing Actions Thing actions can be used to perform calculations as well as import prices directly into rules without deserializing JSON from the [hourly-prices](#hourly-prices) channel. -This is more convenient, much faster, and provides automatic summation of the price elements of interest. +This is more convenient, much faster, and provides automatic summation of the price components of interest. Actions use cached data for performing operations. Since data is only fetched when an item is linked to a channel, there might not be any cached data available. @@ -327,14 +327,14 @@ var price = actions.calculatePrice(now.toInstant(), now.plusHours(4).toInstant, | Parameter | Type | Description | |--------------------|-----------------------------|--------------------------------------------------------| -| priceElements | `String` | Comma-separated list of price elements to include | +| priceComponents | `String` | Comma-separated list of price components to include | **Result:** `Map` -The parameter `priceElements` is a case-insensitive comma-separated list of price elements to include in the returned hourly prices. -These elements can be requested: +The parameter `priceComponents` is a case-insensitive comma-separated list of price components to include in the returned hourly prices. +These components can be requested: -| Price element | Description | +| Price component | Description | |-----------------------|-------------------------| | SpotPrice | Spot price | | NetTariff | Net tariff | @@ -343,7 +343,7 @@ These elements can be requested: | ReducedElectricityTax | Reduced electricity tax | | TransmissionNetTariff | Transmission net tariff | -Using `null` as parameter returns the total prices including all price elements. +Using `null` as parameter returns the total prices including all price components. If **Reduced Electricity Tax** is set in Thing configuration, `ElectricityTax` will be excluded, otherwise `ReducedElectricityTax`. This logic ensures consistent and comparable results not affected by artifical changes in the rate for electricity tax two times per year. diff --git a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java index c35cbed206ddd..9e8b307597bb2 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java +++ b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java @@ -61,7 +61,7 @@ public class EnergiDataServiceActions implements ThingActions { private @Nullable EnergiDataServiceHandler handler; - private enum PriceElement { + private enum PriceComponent { SPOT_PRICE("spotprice", null), NET_TARIFF("nettariff", DatahubTariff.NET_TARIFF), SYSTEM_TARIFF("systemtariff", DatahubTariff.SYSTEM_TARIFF), @@ -69,13 +69,13 @@ private enum PriceElement { REDUCED_ELECTRICITY_TAX("reducedelectricitytax", DatahubTariff.REDUCED_ELECTRICITY_TAX), TRANSMISSION_NET_TARIFF("transmissionnettariff", DatahubTariff.TRANSMISSION_NET_TARIFF); - private static final Map NAME_MAP = Stream.of(values()) - .collect(Collectors.toMap(PriceElement::toString, Function.identity())); + private static final Map NAME_MAP = Stream.of(values()) + .collect(Collectors.toMap(PriceComponent::toString, Function.identity())); private String name; private @Nullable DatahubTariff datahubTariff; - private PriceElement(String name, @Nullable DatahubTariff datahubTariff) { + private PriceComponent(String name, @Nullable DatahubTariff datahubTariff) { this.name = name; this.datahubTariff = datahubTariff; } @@ -85,8 +85,8 @@ public String toString() { return name; } - public static PriceElement fromString(final String name) { - PriceElement myEnum = NAME_MAP.get(name.toLowerCase()); + public static PriceComponent fromString(final String name) { + PriceComponent myEnum = NAME_MAP.get(name.toLowerCase()); if (null == myEnum) { throw new IllegalArgumentException(String.format("'%s' has no corresponding value. Accepted values: %s", name, Arrays.asList(values()))); @@ -109,30 +109,30 @@ public static PriceElement fromString(final String name) { boolean isReducedElectricityTax = handler.isReducedElectricityTax(); - return getPrices(Arrays.stream(PriceElement.values()) - .filter(element -> element != (isReducedElectricityTax ? PriceElement.ELECTRICITY_TAX - : PriceElement.REDUCED_ELECTRICITY_TAX)) + return getPrices(Arrays.stream(PriceComponent.values()) + .filter(component -> component != (isReducedElectricityTax ? PriceComponent.ELECTRICITY_TAX + : PriceComponent.REDUCED_ELECTRICITY_TAX)) .collect(Collectors.toSet())); } @RuleAction(label = "@text/action.get-prices.label", description = "@text/action.get-prices.description") public @ActionOutput(name = "prices", type = "java.util.Map") Map getPrices( - @ActionInput(name = "priceElements", label = "@text/action.get-prices.priceElements.label", description = "@text/action.get-prices.priceElements.description") @Nullable String priceElements) { - if (priceElements == null) { - logger.warn("Argument 'priceElements' is null"); + @ActionInput(name = "priceComponents", label = "@text/action.get-prices.priceComponents.label", description = "@text/action.get-prices.priceComponents.description") @Nullable String priceComponents) { + if (priceComponents == null) { + logger.warn("Argument 'priceComponents' is null"); return Map.of(); } - Set priceElementsSet; + Set priceComponentsSet; try { - priceElementsSet = new HashSet( - Arrays.stream(priceElements.split(",")).map(PriceElement::fromString).toList()); + priceComponentsSet = new HashSet( + Arrays.stream(priceComponents.split(",")).map(PriceComponent::fromString).toList()); } catch (IllegalArgumentException e) { logger.warn("{}", e.getMessage()); return Map.of(); } - return getPrices(priceElementsSet); + return getPrices(priceComponentsSet); } @RuleAction(label = "@text/action.calculate-price.label", description = "@text/action.calculate-price.description") @@ -233,7 +233,7 @@ public static PriceElement fromString(final String name) { } } - private Map getPrices(Set priceElements) { + private Map getPrices(Set priceComponents) { EnergiDataServiceHandler handler = this.handler; if (handler == null) { logger.warn("EnergiDataServiceActions ThingHandler is null."); @@ -242,8 +242,8 @@ private Map getPrices(Set priceElements) { Map prices; boolean spotPricesRequired; - if (priceElements.contains(PriceElement.SPOT_PRICE)) { - if (priceElements.size() > 1 && !handler.getCurrency().equals(CURRENCY_DKK)) { + if (priceComponents.contains(PriceComponent.SPOT_PRICE)) { + if (priceComponents.size() > 1 && !handler.getCurrency().equals(CURRENCY_DKK)) { logger.warn("Cannot calculate sum when spot price currency is {}", handler.getCurrency()); return Map.of(); } @@ -254,13 +254,13 @@ private Map getPrices(Set priceElements) { prices = new HashMap<>(); } - for (PriceElement priceElement : PriceElement.values()) { - DatahubTariff datahubTariff = priceElement.getDatahubTariff(); + for (PriceComponent priceComponent : PriceComponent.values()) { + DatahubTariff datahubTariff = priceComponent.getDatahubTariff(); if (datahubTariff == null) { continue; } - if (priceElements.contains(priceElement)) { + if (priceComponents.contains(priceComponent)) { Map tariffMap = handler.getTariffs(datahubTariff); mergeMaps(prices, tariffMap, !spotPricesRequired); } @@ -287,13 +287,13 @@ private void mergeMaps(Map destinationMap, Map getPrices(@Nullable ThingActions actions, @Nullable String priceElements) { + public static Map getPrices(@Nullable ThingActions actions, @Nullable String priceComponents) { if (actions instanceof EnergiDataServiceActions serviceActions) { - if (priceElements != null && !priceElements.isBlank()) { - return serviceActions.getPrices(priceElements); + if (priceComponents != null && !priceComponents.isBlank()) { + return serviceActions.getPrices(priceComponents); } else { return serviceActions.getPrices(); } diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties index f63c3a834742d..b274c96e76d42 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties @@ -108,5 +108,5 @@ action.calculate-price.label = calculate price action.calculate-price.description = calculate price for power consumption in period excl. VAT action.get-prices.label = get prices action.get-prices.description = get hourly prices excl. VAT -action.get-prices.priceElements.label = price elements -action.get-prices.priceElements.description = comma-separated list of price elements to include in sums +action.get-prices.priceComponents.label = price components +action.get-prices.priceComponents.description = comma-separated list of price components to include in sums diff --git a/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java b/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java index 70ec701cb6123..c2dbfda85bba9 100644 --- a/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java +++ b/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java @@ -198,7 +198,7 @@ void getPricesTotalReducedElectricityTax() throws IOException { } @Test - void getPricesTotalAllElements() throws IOException { + void getPricesTotalAllComponents() throws IOException { mockCommonDatasets(actions); Map actual = actions @@ -210,7 +210,7 @@ void getPricesTotalAllElements() throws IOException { } @Test - void getPricesInvalidPriceElement() throws IOException { + void getPricesInvalidPriceComponent() throws IOException { mockCommonDatasets(actions); Map actual = actions.getPrices("spotprice,nettarif"); From dd8ee21042f431f142623951dbcd6771b6327afc Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Wed, 22 Nov 2023 07:00:48 +0100 Subject: [PATCH 109/146] New Crowdin updates (#15939) * New translations shelly.properties (German) * New translations energidataservice.properties (Danish) --- .../resources/OH-INF/i18n/energidataservice_da.properties | 4 ++-- .../src/main/resources/OH-INF/i18n/shelly_de.properties | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties index 71a9d3f8c0f76..a7c2a0543aec5 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties @@ -108,5 +108,5 @@ action.calculate-price.label = beregn pris action.calculate-price.description = beregn pris for elforbrug i perioden ekskl. moms action.get-prices.label = hent priser action.get-prices.description = hent timepriser ekskl. moms -action.get-prices.priceElements.label = priselementer -action.get-prices.priceElements.description = kommasepareret liste over priselementer der skal medregnes i summer +action.get-prices.priceComponents.label = priskomponenter +action.get-prices.priceComponents.description = kommasepareret liste over priskomponenter der skal medregnes i summer diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties index 0021fa688cc69..ac2bb777dcf4d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties @@ -34,7 +34,7 @@ message.versioncheck.update = INFO\: Neue Firmware verfügbar\: aktuelle Version message.versioncheck.autocoiot = INFO\: Die Firmware unterstützt die Anforderung, Auto-CoIoT wurde aktiviert. message.init.noipaddress = Es konnte keine lokale IP-Adresse ermittelt werden. Bitte sicherstellen, dass IPv4 aktiviert ist und das richtige Interface in der openHAB Netzwerk-Konfiguration ausgewählt ist. message.command.failed = FEHLER\: Der Befehl {0} für Kanal {1} kann nicht verarbeitet werden -message.command.init = Thing aktuell nicht initialisiert, der Befehl {0} führt zur Initialisierung +message.command.init = Gerät ist nicht initialisiert, der Befehl {0} führt zur Initialisierung message.status.unknown.initializing = Initialisierung oder Gerät im Schlafmodus. message.statusupdate.failed = Status konnte nicht aktualisiert werden message.status.managerstarted = Shelly Manager gestartet und erreichbar unter http(s)\://{0}\:{1}/shelly/manager @@ -42,7 +42,7 @@ message.event.triggered = Event erzeugt\: {0} message.event.filtered = Ereignis gefiltert\: {0} message.coap.init.failed = CoAP/CoIoT konnte nicht gestartet werden\: {0} message.discovery.disabled = Das Gerät ist als "nicht erkennen" markiert und wird nicht übernommen. -message.discovery.protected = Das Gerät mit der IP-Adresse {0} ist zugriffsgeschützt und keine Zugangsdaten konfiguriert. +message.discovery.protected = Gerät {0} ist geschützt und meldet ''Zugriff verweigert'', überprüfen Sie User Id und Passwort. message.discovery.failed = Erkennung des Gerätes mit der IP-Adresse {0} ist fehlgeschlagen\: {1} message.roller.calibrating = Das Gerät ist nicht kalibriert. Es ist eine Kalibrierung mit der Shelly App erforderlich. message.roller.favmissing = Positions-Favoriten werden von der installierten Firmwareversion nicht unterstützt (ab 1.9.2), oder sind nicht in der Shelly App konfiguriert. @@ -120,8 +120,9 @@ thing-type.shelly.shellyproem50.description = Shelly Pro EM-50 - 2xPower Meter + thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM mit 4 Relais und Strommesser # BLU devices -thing-type.shelly.shellypblubutton.description = Shelly BLU Button 1 +thing-type.shelly.shellyblubutton.description = Shelly BLU Button 1 thing-type.shelly.shellybludw.description = Shelly BLU Door/Window Sensor +thing-type.shelly.shellyblumotion.description = Shelly BLU Motion Sensor # Wall Displays From 374a2b10e37a6470a0091f243edd447d0496e748 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Wed, 22 Nov 2023 23:27:42 +0100 Subject: [PATCH 110/146] New translations energidataservice.properties (Italian) (#15947) --- .../resources/OH-INF/i18n/energidataservice_it.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_it.properties b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_it.properties index 3f1d1c79e2ea6..74f9f9b9acc1d 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_it.properties +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_it.properties @@ -108,5 +108,5 @@ action.calculate-price.label = calcola il prezzo action.calculate-price.description = calcolare il prezzo per il consumo di energia nel periodo IVA escl. action.get-prices.label = ottieni prezzi action.get-prices.description = ottieni prezzi orari IVA escl. -action.get-prices.priceElements.label = elementi di prezzo -action.get-prices.priceElements.description = elenco separato da virgole di elementi di prezzo da includere nelle somme +action.get-prices.priceComponents.label = componenti del prezzo +action.get-prices.priceComponents.description = elenco separato da virgole dei componenti del prezzo da includere nelle somme From aae908a0166fbfe6bbcc148bd4a04447ed500d15 Mon Sep 17 00:00:00 2001 From: Matthew Skinner Date: Thu, 23 Nov 2023 23:39:07 +1100 Subject: [PATCH 111/146] Fix Reloink alarms not working after reconnect. (#15943) Signed-off-by: Matthew Skinner --- .../binding/ipcamera/internal/handler/IpCameraHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java index cab6e79c895c4..e43c05f09aacc 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java @@ -1424,7 +1424,7 @@ void pollingCameraConnection() { } logger.debug("About to connect to the IP Camera using the ONVIF PORT at IP: {}:{}", cameraConfig.getIp(), cameraConfig.getOnvifPort()); - onvifCamera.connect(thing.getThingTypeUID().getId().equals(ONVIF_THING)); + onvifCamera.connect(supportsOnvifEvents()); return; } if ("ffmpeg".equals(snapshotUri)) { From 5665e21cb8835dae52d6c97f4a583fe5ff95dc09 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Thu, 23 Nov 2023 19:36:30 +0100 Subject: [PATCH 112/146] Rename channels net-tariff and transmission-net-tariff (#15938) Signed-off-by: Jacob Laursen --- .../README.md | 68 +++++++++---------- .../internal/DatahubTariff.java | 6 +- .../EnergiDataServiceBindingConstants.java | 14 ++-- .../action/EnergiDataServiceActions.java | 6 +- .../api/DatahubTariffFilterFactory.java | 12 ++-- .../handler/EnergiDataServiceHandler.java | 32 +++++---- .../OH-INF/i18n/energidataservice.properties | 8 +-- .../resources/OH-INF/thing/channel-groups.xml | 14 ++-- .../resources/OH-INF/thing/thing-service.xml | 2 +- .../resources/OH-INF/update/instructions.xml | 15 ++++ .../action/EnergiDataServiceActionsTest.java | 34 +++++----- .../{NetTariffs.json => GridTariffs.json} | 0 ...iffs.json => TransmissionGridTariffs.json} | 0 13 files changed, 114 insertions(+), 97 deletions(-) rename bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/{NetTariffs.json => GridTariffs.json} (100%) rename bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/{TransmissionNetTariffs.json => TransmissionGridTariffs.json} (100%) diff --git a/bundles/org.openhab.binding.energidataservice/README.md b/bundles/org.openhab.binding.energidataservice/README.md index cf9972e47d6ef..c28eb0524a008 100644 --- a/bundles/org.openhab.binding.energidataservice/README.md +++ b/bundles/org.openhab.binding.energidataservice/README.md @@ -47,15 +47,15 @@ It will not impact channels, see [Electricity Tax](#electricity-tax) for further ### Channel Group `electricity` -| Channel | Type | Description | Advanced | -|-------------------------|--------|---------------------------------------------------------------------------------------|----------| -| spot-price | Number | Current spot price in DKK or EUR per kWh | no | -| net-tariff | Number | Current net tariff in DKK per kWh. Only available when `gridCompanyGLN` is configured | no | -| system-tariff | Number | Current system tariff in DKK per kWh | no | -| electricity-tax | Number | Current electricity tax in DKK per kWh | no | -| reduced-electricity-tax | Number | Current reduced electricity tax in DKK per kWh. For electric heating customers only | no | -| transmission-net-tariff | Number | Current transmission net tariff in DKK per kWh | no | -| hourly-prices | String | JSON array with hourly prices from 24 hours ago and onward | yes | +| Channel | Type | Description | Advanced | +|--------------------------|--------|----------------------------------------------------------------------------------------|----------| +| spot-price | Number | Current spot price in DKK or EUR per kWh | no | +| grid-tariff | Number | Current grid tariff in DKK per kWh. Only available when `gridCompanyGLN` is configured | no | +| system-tariff | Number | Current system tariff in DKK per kWh | no | +| transmission-grid-tariff | Number | Current transmission grid tariff in DKK per kWh | no | +| electricity-tax | Number | Current electricity tax in DKK per kWh | no | +| reduced-electricity-tax | Number | Current reduced electricity tax in DKK per kWh. For electric heating customers only | no | +| hourly-prices | String | JSON array with hourly prices from 24 hours ago and onward | yes | _Please note:_ There is no channel providing the total price. Instead, create a group item with `SUM` as aggregate function and add the individual price items as children. @@ -82,13 +82,13 @@ The recommended persistence strategy is `forecast`, as it ensures a clean histor Prices from the past 24 hours and all forthcoming prices will be stored. Any changes that impact published prices (e.g. selecting or deselecting VAT Profile) will result in the replacement of persisted prices within this period. -#### Net Tariff +#### Grid Tariff -Discounts are automatically taken into account for channel `net-tariff` so that it represents the actual price. +Discounts are automatically taken into account for channel `grid-tariff` so that it represents the actual price. The tariffs are downloaded using pre-configured filters for the different [Grid Company GLN's](#global-location-number-of-the-grid-company). If your company is not in the list, or the filters are not working, they can be manually overridden. -To override filters, the channel `net-tariff` has the following configuration parameters: +To override filters, the channel `grid-tariff` has the following configuration parameters: | Name | Type | Description | Default | Required | Advanced | |-----------------|---------|----------------------------------------------------------------------------------------------------------------------------------|---------|----------|----------| @@ -145,21 +145,21 @@ The format of the `hourly-prices` JSON array is as follows: "hourStart": "2023-09-19T18:00:00Z", "spotPrice": 0.0, "spotPriceCurrency": "DKK", - "netTariff": 0.0, + "gridTariff": 0.0, "systemTariff": 0.054, + "transmissionGridTariff": 0.058, "electricityTax": 0.697, - "reducedElectricityTax": 0.008, - "transmissionNetTariff": 0.058 + "reducedElectricityTax": 0.008 }, { "hourStart": "2023-09-19T19:00:00Z", "spotPrice": -0.00052, "spotPriceCurrency": "DKK", - "netTariff": 0.0, + "gridTariff": 0.0, "systemTariff": 0.054, + "transmissionGridTariff": 0.058, "electricityTax": 0.697, - "reducedElectricityTax": 0.008, - "transmissionNetTariff": 0.058 + "reducedElectricityTax": 0.008 } ] ``` @@ -334,14 +334,14 @@ var price = actions.calculatePrice(now.toInstant(), now.plusHours(4).toInstant, The parameter `priceComponents` is a case-insensitive comma-separated list of price components to include in the returned hourly prices. These components can be requested: -| Price component | Description | -|-----------------------|-------------------------| -| SpotPrice | Spot price | -| NetTariff | Net tariff | -| SystemTariff | System tariff | -| ElectricityTax | Electricity tax | -| ReducedElectricityTax | Reduced electricity tax | -| TransmissionNetTariff | Transmission net tariff | +| Price component | Description | +|------------------------|-------------------------| +| SpotPrice | Spot price | +| GridTariff | Grid tariff | +| SystemTariff | System tariff | +| TransmissionGridTariff | Transmission grid tariff | +| ElectricityTax | Electricity tax | +| ReducedElectricityTax | Reduced electricity tax | Using `null` as parameter returns the total prices including all price components. If **Reduced Electricity Tax** is set in Thing configuration, `ElectricityTax` will be excluded, otherwise `ReducedElectricityTax`. @@ -350,7 +350,7 @@ This logic ensures consistent and comparable results not affected by artifical c Example: ```javascript -var priceMap = actions.getPrices("SpotPrice,NetTariff") +var priceMap = actions.getPrices("SpotPrice,GridTariff") ``` ## Full Example @@ -360,7 +360,7 @@ var priceMap = actions.getPrices("SpotPrice,NetTariff") ```java Thing energidataservice:service:energidataservice "Energi Data Service" [ priceArea="DK1", currencyCode="DKK", gridCompanyGLN="5790001089030" ] { Channels: - Number : electricity#net-tariff [ chargeTypeCodes="CD,CD R", start="StartOfYear" ] + Number : electricity#grid-tariff [ chargeTypeCodes="CD,CD R", start="StartOfYear" ] } ``` @@ -369,10 +369,10 @@ Thing energidataservice:service:energidataservice "Energi Data Service" [ priceA ```java Group:Number:SUM TotalPrice "Current Total Price" Number SpotPrice "Current Spot Price" (TotalPrice) { channel="energidataservice:service:energidataservice:electricity#spot-price" [profile="transform:VAT"] } -Number NetTariff "Current Net Tariff" (TotalPrice) { channel="energidataservice:service:energidataservice:electricity#net-tariff" [profile="transform:VAT"] } +Number GridTariff "Current Grid Tariff" (TotalPrice) { channel="energidataservice:service:energidataservice:electricity#grid-tariff" [profile="transform:VAT"] } Number SystemTariff "Current System Tariff" (TotalPrice) { channel="energidataservice:service:energidataservice:electricity#system-tariff" [profile="transform:VAT"] } +Number TransmissionGridTariff "Current Transmission Grid Tariff" (TotalPrice) { channel="energidataservice:service:energidataservice:electricity#transmission-grid-tariff" [profile="transform:VAT"] } Number ElectricityTax "Current Electricity Tax" (TotalPrice) { channel="energidataservice:service:energidataservice:electricity#electricity-tax" [profile="transform:VAT"] } -Number TransmissionNetTariff "Current Transmission Tariff" (TotalPrice) { channel="energidataservice:service:energidataservice:electricity#transmission-net-tariff" [profile="transform:VAT"] } String HourlyPrices "Hourly Prices" { channel="energidataservice:service:energidataservice:electricity#hourly-prices" } ``` @@ -394,8 +394,8 @@ var priceMap = actions.getPrices(null) var hourStart = now.toInstant().truncatedTo(ChronoUnit.HOURS) logInfo("Current total price excl. VAT", priceMap.get(hourStart).toString) -var priceMap = actions.getPrices("SpotPrice,NetTariff"); -logInfo("Current spot price + net tariff excl. VAT", priceMap.get(hourStart).toString) +var priceMap = actions.getPrices("SpotPrice,GridTariff"); +logInfo("Current spot price + grid tariff excl. VAT", priceMap.get(hourStart).toString) var price = actions.calculatePrice(Instant.now, now.plusHours(1).toInstant, 150 | W) logInfo("Total price for using 150 W for the next hour", price.toString) @@ -457,10 +457,10 @@ utils.javaMapToJsMap(edsActions.getPrices()).forEach((value, key) => { var hourStart = time.Instant.now().truncatedTo(time.ChronoUnit.HOURS); console.log("Current total price excl. VAT: " + priceMap.get(hourStart.toString())); -utils.javaMapToJsMap(edsActions.getPrices("SpotPrice,NetTariff")).forEach((value, key) => { +utils.javaMapToJsMap(edsActions.getPrices("SpotPrice,GridTariff")).forEach((value, key) => { priceMap.set(key.toString(), value); }); -console.log("Current spot price + net tariff excl. VAT: " + priceMap.get(hourStart.toString())); +console.log("Current spot price + grid tariff excl. VAT: " + priceMap.get(hourStart.toString())); var price = edsActions.calculatePrice(time.Instant.now(), time.Instant.now().plusSeconds(3600), Quantity("150 W")); console.log("Total price for using 150 W for the next hour: " + price.toString()); diff --git a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/DatahubTariff.java b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/DatahubTariff.java index b1e360f53c648..f04dc6146a936 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/DatahubTariff.java +++ b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/DatahubTariff.java @@ -23,11 +23,11 @@ */ @NonNullByDefault public enum DatahubTariff { - NET_TARIFF(CHANNEL_NET_TARIFF), + GRID_TARIFF(CHANNEL_GRID_TARIFF), SYSTEM_TARIFF(CHANNEL_SYSTEM_TARIFF), + TRANSMISSION_GRID_TARIFF(CHANNEL_TRANSMISSION_GRID_TARIFF), ELECTRICITY_TAX(CHANNEL_ELECTRICITY_TAX), - REDUCED_ELECTRICITY_TAX(CHANNEL_REDUCED_ELECTRICITY_TAX), - TRANSMISSION_NET_TARIFF(CHANNEL_TRANSMISSION_NET_TARIFF); + REDUCED_ELECTRICITY_TAX(CHANNEL_REDUCED_ELECTRICITY_TAX); String channelId; diff --git a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/EnergiDataServiceBindingConstants.java b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/EnergiDataServiceBindingConstants.java index 210b133adb074..ccc81cb2d93d5 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/EnergiDataServiceBindingConstants.java +++ b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/EnergiDataServiceBindingConstants.java @@ -42,22 +42,22 @@ public class EnergiDataServiceBindingConstants { // List of all Channel ids public static final String CHANNEL_SPOT_PRICE = CHANNEL_GROUP_ELECTRICITY + ChannelUID.CHANNEL_GROUP_SEPARATOR + "spot-price"; - public static final String CHANNEL_NET_TARIFF = CHANNEL_GROUP_ELECTRICITY + ChannelUID.CHANNEL_GROUP_SEPARATOR - + "net-tariff"; + public static final String CHANNEL_GRID_TARIFF = CHANNEL_GROUP_ELECTRICITY + ChannelUID.CHANNEL_GROUP_SEPARATOR + + "grid-tariff"; public static final String CHANNEL_SYSTEM_TARIFF = CHANNEL_GROUP_ELECTRICITY + ChannelUID.CHANNEL_GROUP_SEPARATOR + "system-tariff"; public static final String CHANNEL_ELECTRICITY_TAX = CHANNEL_GROUP_ELECTRICITY + ChannelUID.CHANNEL_GROUP_SEPARATOR + "electricity-tax"; public static final String CHANNEL_REDUCED_ELECTRICITY_TAX = CHANNEL_GROUP_ELECTRICITY + ChannelUID.CHANNEL_GROUP_SEPARATOR + "reduced-electricity-tax"; - public static final String CHANNEL_TRANSMISSION_NET_TARIFF = CHANNEL_GROUP_ELECTRICITY - + ChannelUID.CHANNEL_GROUP_SEPARATOR + "transmission-net-tariff"; + public static final String CHANNEL_TRANSMISSION_GRID_TARIFF = CHANNEL_GROUP_ELECTRICITY + + ChannelUID.CHANNEL_GROUP_SEPARATOR + "transmission-grid-tariff"; public static final String CHANNEL_HOURLY_PRICES = CHANNEL_GROUP_ELECTRICITY + ChannelUID.CHANNEL_GROUP_SEPARATOR + "hourly-prices"; - public static final Set ELECTRICITY_CHANNELS = Set.of(CHANNEL_SPOT_PRICE, CHANNEL_NET_TARIFF, - CHANNEL_SYSTEM_TARIFF, CHANNEL_ELECTRICITY_TAX, CHANNEL_REDUCED_ELECTRICITY_TAX, - CHANNEL_TRANSMISSION_NET_TARIFF, CHANNEL_HOURLY_PRICES); + public static final Set ELECTRICITY_CHANNELS = Set.of(CHANNEL_SPOT_PRICE, CHANNEL_GRID_TARIFF, + CHANNEL_SYSTEM_TARIFF, CHANNEL_TRANSMISSION_GRID_TARIFF, CHANNEL_ELECTRICITY_TAX, + CHANNEL_REDUCED_ELECTRICITY_TAX, CHANNEL_HOURLY_PRICES); // List of all properties public static final String PROPERTY_REMAINING_CALLS = "remainingCalls"; diff --git a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java index 9e8b307597bb2..de4f8385a2232 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java +++ b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActions.java @@ -63,11 +63,11 @@ public class EnergiDataServiceActions implements ThingActions { private enum PriceComponent { SPOT_PRICE("spotprice", null), - NET_TARIFF("nettariff", DatahubTariff.NET_TARIFF), + GRID_TARIFF("gridtariff", DatahubTariff.GRID_TARIFF), SYSTEM_TARIFF("systemtariff", DatahubTariff.SYSTEM_TARIFF), + TRANSMISSION_GRID_TARIFF("transmissiongridtariff", DatahubTariff.TRANSMISSION_GRID_TARIFF), ELECTRICITY_TAX("electricitytax", DatahubTariff.ELECTRICITY_TAX), - REDUCED_ELECTRICITY_TAX("reducedelectricitytax", DatahubTariff.REDUCED_ELECTRICITY_TAX), - TRANSMISSION_NET_TARIFF("transmissionnettariff", DatahubTariff.TRANSMISSION_NET_TARIFF); + REDUCED_ELECTRICITY_TAX("reducedelectricitytax", DatahubTariff.REDUCED_ELECTRICITY_TAX); private static final Map NAME_MAP = Stream.of(values()) .collect(Collectors.toMap(PriceComponent::toString, Function.identity())); diff --git a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/api/DatahubTariffFilterFactory.java b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/api/DatahubTariffFilterFactory.java index 07a248939d758..63de9d63802c4 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/api/DatahubTariffFilterFactory.java +++ b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/api/DatahubTariffFilterFactory.java @@ -71,7 +71,7 @@ public class DatahubTariffFilterFactory { public static final LocalDate RADIUS_CUTOFF_DATE = LocalDate.of(2023, 1, 1); public static final LocalDate KONSTANT_CUTOFF_DATE = LocalDate.of(2023, 2, 1); - public static DatahubTariffFilter getNetTariffByGLN(String globalLocationNumber) { + public static DatahubTariffFilter getGridTariffByGLN(String globalLocationNumber) { switch (globalLocationNumber) { case GLN_AAL_ELNET: return new DatahubTariffFilter(Set.of(ChargeTypeCode.of("AAL-NT-05"), ChargeTypeCode.of("AAL-NTR05")), @@ -167,6 +167,11 @@ public static DatahubTariffFilter getSystemTariff() { DateQueryParameter.of(ENERGINET_CUTOFF_DATE)); } + public static DatahubTariffFilter getTransmissionGridTariff() { + return new DatahubTariffFilter(Set.of(), Set.of(NOTE_TRANSMISSION_NET_TARIFF), + DateQueryParameter.of(ENERGINET_CUTOFF_DATE)); + } + public static DatahubTariffFilter getElectricityTax() { return new DatahubTariffFilter(Set.of(), Set.of(NOTE_ELECTRICITY_TAX), DateQueryParameter.of(ENERGINET_CUTOFF_DATE)); @@ -176,9 +181,4 @@ public static DatahubTariffFilter getReducedElectricityTax() { return new DatahubTariffFilter(Set.of(), Set.of(NOTE_REDUCED_ELECTRICITY_TAX), DateQueryParameter.of(LocalDate.of(2021, 2, 1))); } - - public static DatahubTariffFilter getTransmissionNetTariff() { - return new DatahubTariffFilter(Set.of(), Set.of(NOTE_TRANSMISSION_NET_TARIFF), - DateQueryParameter.of(ENERGINET_CUTOFF_DATE)); - } } diff --git a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java index 2a71212972c21..ca686821fb6b0 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java +++ b/bundles/org.openhab.binding.energidataservice/src/main/java/org/openhab/binding/energidataservice/internal/handler/EnergiDataServiceHandler.java @@ -95,8 +95,9 @@ public class EnergiDataServiceHandler extends BaseThingHandler { private @Nullable ScheduledFuture priceUpdateFuture; private record Price(String hourStart, BigDecimal spotPrice, String spotPriceCurrency, - @Nullable BigDecimal netTariff, @Nullable BigDecimal systemTariff, @Nullable BigDecimal electricityTax, - @Nullable BigDecimal reducedElectricityTax, @Nullable BigDecimal transmissionNetTariff) { + @Nullable BigDecimal gridTariff, @Nullable BigDecimal systemTariff, + @Nullable BigDecimal transmissionGridTariff, @Nullable BigDecimal electricityTax, + @Nullable BigDecimal reducedElectricityTax) { } public EnergiDataServiceHandler(Thing thing, HttpClient httpClient, TimeZoneProvider timeZoneProvider) { @@ -236,7 +237,7 @@ private void downloadSpotPrices() throws InterruptedException, DataServiceExcept private void downloadTariffs(DatahubTariff datahubTariff) throws InterruptedException, DataServiceException { GlobalLocationNumber globalLocationNumber = switch (datahubTariff) { - case NET_TARIFF -> config.getGridCompanyGLN(); + case GRID_TARIFF -> config.getGridCompanyGLN(); default -> config.getEnerginetGLN(); }; if (globalLocationNumber.isEmpty()) { @@ -247,11 +248,11 @@ private void downloadTariffs(DatahubTariff datahubTariff) throws InterruptedExce cacheManager.updateTariffs(datahubTariff); } else { DatahubTariffFilter filter = switch (datahubTariff) { - case NET_TARIFF -> getNetTariffFilter(); + case GRID_TARIFF -> getGridTariffFilter(); case SYSTEM_TARIFF -> DatahubTariffFilterFactory.getSystemTariff(); + case TRANSMISSION_GRID_TARIFF -> DatahubTariffFilterFactory.getTransmissionGridTariff(); case ELECTRICITY_TAX -> DatahubTariffFilterFactory.getElectricityTax(); case REDUCED_ELECTRICITY_TAX -> DatahubTariffFilterFactory.getReducedElectricityTax(); - case TRANSMISSION_NET_TARIFF -> DatahubTariffFilterFactory.getTransmissionNetTariff(); }; cacheManager.putTariffs(datahubTariff, downloadPriceLists(globalLocationNumber, filter)); } @@ -267,24 +268,24 @@ private Collection downloadPriceLists(GlobalLocationNumb return records; } - private DatahubTariffFilter getNetTariffFilter() { - Channel channel = getThing().getChannel(CHANNEL_NET_TARIFF); + private DatahubTariffFilter getGridTariffFilter() { + Channel channel = getThing().getChannel(CHANNEL_GRID_TARIFF); if (channel == null) { - return DatahubTariffFilterFactory.getNetTariffByGLN(config.gridCompanyGLN); + return DatahubTariffFilterFactory.getGridTariffByGLN(config.gridCompanyGLN); } DatahubPriceConfiguration datahubPriceConfiguration = channel.getConfiguration() .as(DatahubPriceConfiguration.class); if (!datahubPriceConfiguration.hasAnyFilterOverrides()) { - return DatahubTariffFilterFactory.getNetTariffByGLN(config.gridCompanyGLN); + return DatahubTariffFilterFactory.getGridTariffByGLN(config.gridCompanyGLN); } DateQueryParameter start = datahubPriceConfiguration.getStart(); if (start == null) { logger.warn("Invalid channel configuration parameter 'start' or 'offset': {} (offset: {})", datahubPriceConfiguration.start, datahubPriceConfiguration.offset); - return DatahubTariffFilterFactory.getNetTariffByGLN(config.gridCompanyGLN); + return DatahubTariffFilterFactory.getGridTariffByGLN(config.gridCompanyGLN); } Set chargeTypeCodes = datahubPriceConfiguration.getChargeTypeCodes(); @@ -295,7 +296,7 @@ private DatahubTariffFilter getNetTariffFilter() { filter = new DatahubTariffFilter(chargeTypeCodes, notes, start); } else { // Only override start date in pre-configured filter. - filter = new DatahubTariffFilter(DatahubTariffFilterFactory.getNetTariffByGLN(config.gridCompanyGLN), + filter = new DatahubTariffFilter(DatahubTariffFilterFactory.getGridTariffByGLN(config.gridCompanyGLN), start); } @@ -341,13 +342,14 @@ private void updateHourlyPrices() { int i = 0; for (Entry sourcePrice : sourcePrices) { Instant hourStart = sourcePrice.getKey(); - BigDecimal netTariff = cacheManager.getTariff(DatahubTariff.NET_TARIFF, hourStart); + BigDecimal gridTariff = cacheManager.getTariff(DatahubTariff.GRID_TARIFF, hourStart); BigDecimal systemTariff = cacheManager.getTariff(DatahubTariff.SYSTEM_TARIFF, hourStart); + BigDecimal transmissionGridTariff = cacheManager.getTariff(DatahubTariff.TRANSMISSION_GRID_TARIFF, + hourStart); BigDecimal electricityTax = cacheManager.getTariff(DatahubTariff.ELECTRICITY_TAX, hourStart); BigDecimal reducedElectricityTax = cacheManager.getTariff(DatahubTariff.REDUCED_ELECTRICITY_TAX, hourStart); - BigDecimal transmissionNetTariff = cacheManager.getTariff(DatahubTariff.TRANSMISSION_NET_TARIFF, hourStart); - targetPrices[i++] = new Price(hourStart.toString(), sourcePrice.getValue(), config.currencyCode, netTariff, - systemTariff, electricityTax, reducedElectricityTax, transmissionNetTariff); + targetPrices[i++] = new Price(hourStart.toString(), sourcePrice.getValue(), config.currencyCode, gridTariff, + systemTariff, electricityTax, reducedElectricityTax, transmissionGridTariff); } updateState(CHANNEL_HOURLY_PRICES, new StringType(gson.toJson(targetPrices))); } diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties index b274c96e76d42..ee8aa84ac78c1 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice.properties @@ -60,16 +60,16 @@ channel-group-type.energidataservice.electricity.label = Electricity channel-group-type.energidataservice.electricity.description = Channels related to electricity channel-group-type.energidataservice.electricity.channel.electricity-tax.label = Electricity Tax channel-group-type.energidataservice.electricity.channel.electricity-tax.description = Current electricity tax in DKK per kWh. -channel-group-type.energidataservice.electricity.channel.net-tariff.label = Net Tariff -channel-group-type.energidataservice.electricity.channel.net-tariff.description = Current net tariff in DKK per kWh. +channel-group-type.energidataservice.electricity.channel.grid-tariff.label = Grid Tariff +channel-group-type.energidataservice.electricity.channel.grid-tariff.description = Current grid tariff in DKK per kWh. channel-group-type.energidataservice.electricity.channel.reduced-electricity-tax.label = Reduced Electricity Tax channel-group-type.energidataservice.electricity.channel.reduced-electricity-tax.description = Current reduced electricity tax in DKK per kWh. For electric heating customers only. channel-group-type.energidataservice.electricity.channel.spot-price.label = Spot Price channel-group-type.energidataservice.electricity.channel.spot-price.description = Current spot price in DKK or EUR per kWh. channel-group-type.energidataservice.electricity.channel.system-tariff.label = System Tariff channel-group-type.energidataservice.electricity.channel.system-tariff.description = Current system tariff in DKK per kWh. -channel-group-type.energidataservice.electricity.channel.transmission-net-tariff.label = Transmission Net Tariff -channel-group-type.energidataservice.electricity.channel.transmission-net-tariff.description = Current transmission net tariff in DKK per kWh. +channel-group-type.energidataservice.electricity.channel.transmission-grid-tariff.label = Transmission Grid Tariff +channel-group-type.energidataservice.electricity.channel.transmission-grid-tariff.description = Current transmission grid tariff in DKK per kWh. # channel types diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/channel-groups.xml index c978b03667b3d..83587c1e46a67 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/channel-groups.xml +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/channel-groups.xml @@ -12,14 +12,18 @@ Current spot price in DKK or EUR per kWh.
- - - Current net tariff in DKK per kWh. + + + Current grid tariff in DKK per kWh. Current system tariff in DKK per kWh. + + + Current transmission grid tariff in DKK per kWh. + Current electricity tax in DKK per kWh. @@ -28,10 +32,6 @@ Current reduced electricity tax in DKK per kWh. For electric heating customers only. - - - Current transmission net tariff in DKK per kWh. - diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/thing-service.xml b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/thing-service.xml index b69ef51aa8e13..805510a68eab8 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/thing-service.xml +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/thing/thing-service.xml @@ -14,7 +14,7 @@ - 1 + 2 diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/update/instructions.xml index 62e288807ab46..ef4dd6bd99184 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/update/instructions.xml +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/update/instructions.xml @@ -13,6 +13,21 @@ + + + energidataservice:datahub-price + + Current grid tariff in DKK per kWh. + + + energidataservice:datahub-price + + Current transmission grid tariff in DKK per kWh. + + + + + diff --git a/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java b/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java index c2dbfda85bba9..d1ed9ae9d2828 100644 --- a/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java +++ b/bundles/org.openhab.binding.energidataservice/src/test/java/org/openhab/binding/energidataservice/internal/action/EnergiDataServiceActionsTest.java @@ -109,10 +109,10 @@ void getPricesSpotPrice() throws IOException { } @Test - void getPricesNetTariff() throws IOException { + void getPricesGridTariff() throws IOException { mockCommonDatasets(actions); - Map actual = actions.getPrices("NetTariff"); + Map actual = actions.getPrices("GridTariff"); assertThat(actual.size(), is(60)); assertThat(actual.get(Instant.parse("2023-02-04T12:00:00Z")), is(equalTo(new BigDecimal("0.432225")))); assertThat(actual.get(Instant.parse("2023-02-04T16:00:00Z")), is(equalTo(new BigDecimal("1.05619")))); @@ -139,30 +139,30 @@ void getPricesElectricityTax() throws IOException { } @Test - void getPricesTransmissionNetTariff() throws IOException { + void getPricesTransmissionGridTariff() throws IOException { mockCommonDatasets(actions); - Map actual = actions.getPrices("TransmissionNetTariff"); + Map actual = actions.getPrices("TransmissionGridTariff"); assertThat(actual.size(), is(60)); assertThat(actual.get(Instant.parse("2023-02-04T12:00:00Z")), is(equalTo(new BigDecimal("0.058")))); assertThat(actual.get(Instant.parse("2023-02-04T16:00:00Z")), is(equalTo(new BigDecimal("0.058")))); } @Test - void getPricesSpotPriceNetTariff() throws IOException { + void getPricesSpotPriceGridTariff() throws IOException { mockCommonDatasets(actions); - Map actual = actions.getPrices("SpotPrice,NetTariff"); + Map actual = actions.getPrices("SpotPrice,GridTariff"); assertThat(actual.size(), is(35)); assertThat(actual.get(Instant.parse("2023-02-04T12:00:00Z")), is(equalTo(new BigDecimal("1.425065027")))); assertThat(actual.get(Instant.parse("2023-02-04T16:00:00Z")), is(equalTo(new BigDecimal("2.323870054")))); } @Test - void getPricesSpotPriceNetTariffElectricityTax() throws IOException { + void getPricesSpotPriceGridTariffElectricityTax() throws IOException { mockCommonDatasets(actions); - Map actual = actions.getPrices("SpotPrice,NetTariff,ElectricityTax"); + Map actual = actions.getPrices("SpotPrice,GridTariff,ElectricityTax"); assertThat(actual.size(), is(35)); assertThat(actual.get(Instant.parse("2023-02-04T12:00:00Z")), is(equalTo(new BigDecimal("1.433065027")))); assertThat(actual.get(Instant.parse("2023-02-04T16:00:00Z")), is(equalTo(new BigDecimal("2.331870054")))); @@ -202,7 +202,7 @@ void getPricesTotalAllComponents() throws IOException { mockCommonDatasets(actions); Map actual = actions - .getPrices("spotprice,nettariff,systemtariff,electricitytax,transmissionnettariff"); + .getPrices("spotprice,gridtariff,systemtariff,electricitytax,transmissiongridtariff"); assertThat(actual.size(), is(35)); assertThat(actual.get(Instant.parse("2023-02-04T12:00:00Z")), is(equalTo(new BigDecimal("1.545065027")))); assertThat(actual.get(Instant.parse("2023-02-04T15:00:00Z")), is(equalTo(new BigDecimal("1.708765039")))); @@ -213,7 +213,7 @@ void getPricesTotalAllComponents() throws IOException { void getPricesInvalidPriceComponent() throws IOException { mockCommonDatasets(actions); - Map actual = actions.getPrices("spotprice,nettarif"); + Map actual = actions.getPrices("spotprice,gridtarif"); assertThat(actual.size(), is(0)); } @@ -222,7 +222,7 @@ void getPricesMixedCurrencies() throws IOException { mockCommonDatasets(actions); when(handler.getCurrency()).thenReturn(EnergiDataServiceBindingConstants.CURRENCY_EUR); - Map actual = actions.getPrices("spotprice,nettariff"); + Map actual = actions.getPrices("spotprice,gridtariff"); assertThat(actual.size(), is(0)); } @@ -404,8 +404,8 @@ private void mockCommonDatasets(EnergiDataServiceActions actions, String spotPri PriceListParser priceListParser = new PriceListParser( Clock.fixed(spotPriceRecords[0].hourStart, EnergiDataServiceBindingConstants.DATAHUB_TIMEZONE)); - DatahubPricelistRecords datahubRecords = getObjectFromJson("NetTariffs.json", DatahubPricelistRecords.class); - Map netTariffs = priceListParser + DatahubPricelistRecords datahubRecords = getObjectFromJson("GridTariffs.json", DatahubPricelistRecords.class); + Map gridTariffs = priceListParser .toHourly(Arrays.stream(datahubRecords.records()).toList()); datahubRecords = getObjectFromJson("SystemTariffs.json", DatahubPricelistRecords.class); Map systemTariffs = priceListParser @@ -416,16 +416,16 @@ private void mockCommonDatasets(EnergiDataServiceActions actions, String spotPri datahubRecords = getObjectFromJson("ReducedElectricityTaxes.json", DatahubPricelistRecords.class); Map reducedElectricityTaxes = priceListParser .toHourly(Arrays.stream(datahubRecords.records()).toList()); - datahubRecords = getObjectFromJson("TransmissionNetTariffs.json", DatahubPricelistRecords.class); - Map transmissionNetTariffs = priceListParser + datahubRecords = getObjectFromJson("TransmissionGridTariffs.json", DatahubPricelistRecords.class); + Map transmissionGridTariffs = priceListParser .toHourly(Arrays.stream(datahubRecords.records()).toList()); when(handler.getSpotPrices()).thenReturn(spotPrices); - when(handler.getTariffs(DatahubTariff.NET_TARIFF)).thenReturn(netTariffs); + when(handler.getTariffs(DatahubTariff.GRID_TARIFF)).thenReturn(gridTariffs); when(handler.getTariffs(DatahubTariff.SYSTEM_TARIFF)).thenReturn(systemTariffs); + when(handler.getTariffs(DatahubTariff.TRANSMISSION_GRID_TARIFF)).thenReturn(transmissionGridTariffs); when(handler.getTariffs(DatahubTariff.ELECTRICITY_TAX)).thenReturn(electricityTaxes); when(handler.getTariffs(DatahubTariff.REDUCED_ELECTRICITY_TAX)).thenReturn(reducedElectricityTaxes); - when(handler.getTariffs(DatahubTariff.TRANSMISSION_NET_TARIFF)).thenReturn(transmissionNetTariffs); when(handler.getCurrency()).thenReturn(EnergiDataServiceBindingConstants.CURRENCY_DKK); when(handler.isReducedElectricityTax()).thenReturn(isReducedElectricityTax); actions.setThingHandler(handler); diff --git a/bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/NetTariffs.json b/bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/GridTariffs.json similarity index 100% rename from bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/NetTariffs.json rename to bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/GridTariffs.json diff --git a/bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/TransmissionNetTariffs.json b/bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/TransmissionGridTariffs.json similarity index 100% rename from bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/TransmissionNetTariffs.json rename to bundles/org.openhab.binding.energidataservice/src/test/resources/org/openhab/binding/energidataservice/internal/action/TransmissionGridTariffs.json From 9f8af20c10d7f404ad037c20722a7656fe1c0db3 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Thu, 23 Nov 2023 20:54:56 +0100 Subject: [PATCH 113/146] New translations energidataservice.properties (Danish) (#15948) --- .../resources/OH-INF/i18n/energidataservice_da.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties index a7c2a0543aec5..9c85d20f2c6fe 100644 --- a/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties +++ b/bundles/org.openhab.binding.energidataservice/src/main/resources/OH-INF/i18n/energidataservice_da.properties @@ -60,16 +60,16 @@ channel-group-type.energidataservice.electricity.label = Elektricitet channel-group-type.energidataservice.electricity.description = Kanaler relateret til elektricitet channel-group-type.energidataservice.electricity.channel.electricity-tax.label = Elafgift channel-group-type.energidataservice.electricity.channel.electricity-tax.description = Nuværende elafgift i DKK pr. kWh. -channel-group-type.energidataservice.electricity.channel.net-tariff.label = Nettarif -channel-group-type.energidataservice.electricity.channel.net-tariff.description = Nuværende nettarif i DKK pr. kWh. +channel-group-type.energidataservice.electricity.channel.grid-tariff.label = Nettarif +channel-group-type.energidataservice.electricity.channel.grid-tariff.description = Nuværende nettarif i DKK pr. kWh. channel-group-type.energidataservice.electricity.channel.reduced-electricity-tax.label = Reduceret elafgift channel-group-type.energidataservice.electricity.channel.reduced-electricity-tax.description = Nuværende reduceret elafgift i DKK pr. kWh. Kun for elvarmekunder. channel-group-type.energidataservice.electricity.channel.spot-price.label = Spotpris channel-group-type.energidataservice.electricity.channel.spot-price.description = Nuværende spotpris i DKK eller EUR pr. kWh. channel-group-type.energidataservice.electricity.channel.system-tariff.label = Systemtarif channel-group-type.energidataservice.electricity.channel.system-tariff.description = Nuværende systemtarif i DKK pr. kWh. -channel-group-type.energidataservice.electricity.channel.transmission-net-tariff.label = Transmissionsnettarif -channel-group-type.energidataservice.electricity.channel.transmission-net-tariff.description = Nuværende transmissionsnettarif i DKK pr. kWh. +channel-group-type.energidataservice.electricity.channel.transmission-grid-tariff.label = Transmissionsnettarif +channel-group-type.energidataservice.electricity.channel.transmission-grid-tariff.description = Nuværende transmissionsnettarif i DKK pr. kWh. # channel types From 0b86647da2b45338b665d9287de1f0469d5acd49 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Sat, 25 Nov 2023 09:46:22 +0100 Subject: [PATCH 114/146] [shelly] Misc changes (small fixes, log improvements, hardened leak prevention on (#15922) * Misc changes (same fixes, log improvements, hardened leak prevention on exceptions) --------- Signed-off-by: Markus Michels --- bundles/org.openhab.binding.shelly/README.md | 4 +- bundles/org.openhab.binding.shelly/pom.xml | 9 -- .../internal/ShellyBindingConstants.java | 1 + .../shelly/internal/ShellyHandlerFactory.java | 2 +- .../internal/api/ShellyDeviceProfile.java | 15 +++ .../shelly/internal/api/ShellyHttpClient.java | 11 +- .../internal/api1/Shelly1CoapHandler.java | 12 +- .../internal/api2/Shelly2ApiClient.java | 2 +- .../shelly/internal/api2/Shelly2ApiRpc.java | 18 +-- .../internal/api2/Shelly2RpcSocket.java | 10 +- .../shelly/internal/api2/ShellyBluApi.java | 31 ++--- .../discovery/ShellyDiscoveryParticipant.java | 4 + .../internal/handler/ShellyBaseHandler.java | 113 +++++++----------- .../handler/ShellyBluSensorHandler.java | 6 +- .../internal/handler/ShellyComponents.java | 2 +- .../manager/ShellyManagerOverviewPage.java | 2 +- .../provider/ShellyChannelDefinitions.java | 2 +- .../resources/OH-INF/i18n/shelly.properties | 6 +- 18 files changed, 124 insertions(+), 126 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index 406cffc19b72b..fde35de2e69ee 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -1451,8 +1451,6 @@ See notes on discovery of Shelly BLU devices above. | | lowBattery | Switch | yes | Low battery alert (< 20%) | | device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet | - - ### Shelly BLU Door/Window Sensor (thing-type: shellybludw) See notes on discovery of Shelly BLU devices above. @@ -1467,7 +1465,7 @@ See notes on discovery of Shelly BLU devices above. | | lowBattery | Switch | yes | Low battery alert (< 20%) | | device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet | -## Shelly BLU Motion Sensor (thing-type: shellyblumotion) +### Shelly BLU Motion Sensor (thing-type: shellyblumotion) See notes on discovery of Shelly BLU devices above. diff --git a/bundles/org.openhab.binding.shelly/pom.xml b/bundles/org.openhab.binding.shelly/pom.xml index 61c8dbf6cf88a..429dc71bd521d 100644 --- a/bundles/org.openhab.binding.shelly/pom.xml +++ b/bundles/org.openhab.binding.shelly/pom.xml @@ -14,13 +14,4 @@ org.openhab.binding.shelly openHAB Add-ons :: Bundles :: Shelly Binding Gen1+2 - - - org.eclipse.jetty.websocket - websocket-server - 9.4.46.v20220331 - compile - - - diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index 34957a2b44cd6..1a8732b0ac386 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -120,6 +120,7 @@ public class ShellyBindingConstants { public static final String PROPERTY_DEV_TYPE = "deviceType"; public static final String PROPERTY_DEV_MODE = "deviceMode"; public static final String PROPERTY_DEV_GEN = "deviceGeneration"; + public static final String PROPERTY_DEV_AUTH = "deviceAuth"; public static final String PROPERTY_GW_DEVICE = "gatewayDevice"; public static final String PROPERTY_HWREV = "deviceHwRev"; public static final String PROPERTY_HWBATCH = "deviceHwBatch"; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java index f0cf33be8bb1d..111f60e4af099 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java @@ -119,7 +119,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ShellyBaseHandler handler = null; if (thingType.equals(THING_TYPE_SHELLYPROTECTED_STR)) { - logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(), + logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(), thingTypeUID.toString()); handler = new ShellyProtectedHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient); } else if (thingType.equals(THING_TYPE_SHELLYBULB_STR) || thingType.equals(THING_TYPE_SHELLYDUO_STR) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 23cce5cff1a93..e82314ae99109 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -145,6 +145,7 @@ public ShellyDeviceProfile initialize(String thingType, String jsonIn, @Nullable device.hostname = device.mac.length() >= 12 ? "shelly-" + device.mac.toUpperCase().substring(6, 11) : "unknown"; } + device.mode = getString(settings.mode).toLowerCase(); name = getString(settings.name); hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : ""; hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : ""; @@ -418,4 +419,18 @@ public boolean coiotEnabled() { // If device is not yet intialized or the enabled property is missing we assume that CoIoT is enabled return true; } + + public static String buildBluServiceName(String name, String mac) throws IllegalArgumentException { + String model = name.contains("-") ? substringBefore(name, "-") : name; // e.g. SBBT-02C or just SBDW + switch (model) { + case SHELLYDT_BLUBUTTON: + return (THING_TYPE_SHELLYBLUBUTTON_STR + "-" + mac).toLowerCase(); + case SHELLYDT_BLUDW: + return (THING_TYPE_SHELLYBLUDW_STR + "-" + mac).toLowerCase(); + case SHELLYDT_BLUMOTION: + return (THING_TYPE_SHELLYBLUMOTION_STR + "-" + mac).toLowerCase(); + default: + throw new IllegalArgumentException("Unsupported BLU device model " + model); + } + } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java index 7ed4363450bc8..41fb0181342eb 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java @@ -120,6 +120,12 @@ protected String httpRequest(String uri) throws ShellyApiException { } return apiResult.response; // successful } catch (ShellyApiException e) { + if (e.isHttpAccessUnauthorized() && !profile.isGen2 && !basicAuth && !config.password.isEmpty()) { + logger.debug("{}: Access is unauthorized, auto-activate basic auth", thingName); + basicAuth = true; + apiResult = innerRequest(HttpMethod.GET, uri, null, ""); + } + if (e.isConnectionError() || (!e.isTimeout() && !apiResult.isHttpServerError()) && !apiResult.isNotFound() || profile.hasBattery || (retries == 0)) { @@ -129,9 +135,10 @@ protected String httpRequest(String uri) throws ShellyApiException { timeout = true; timeoutErrors++; // count the retries - logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); - retries--; + if (profile.alwaysOn) { + logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); + } } } throw new ShellyApiException("API Timeout or inconsistent result"); // successful diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java index 3de692e224034..48eab5fb5c913 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java @@ -49,6 +49,7 @@ import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.types.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -241,7 +242,7 @@ public void processResponse(@Nullable Response response) { } if (!coiotBound) { thingHandler.updateProperties(PROPERTY_COAP_VERSION, sVersion); - logger.debug("{}: CoIoT Version {} detected", thingName, iVersion); + logger.debug("{}: CoIoT Version {} detected", thingName, iVersion); if (iVersion == COIOT_VERSION_1) { coiot = new Shelly1CoIoTVersion1(thingName, thingHandler, blkMap, sensorMap); } else if (iVersion == COIOT_VERSION_2) { @@ -265,6 +266,13 @@ public void processResponse(@Nullable Response response) { } } + // Don't change state to online when thing is in status config error + // (e.g. auth failed, but device sends COAP packets via multicast) + if (thingHandler.getThingStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR) { + logger.debug("{}: The device is not configuired correctly, skip Coap packet", thingName); + return; + } + // If we received a CoAP message successful the thing must be online thingHandler.setThingOnline(); @@ -441,7 +449,7 @@ private void handleStatusUpdate(String devId, String payload, int serial) throws List sensorUpdates = list.generic; Map updates = new TreeMap(); - logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size()); + logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size()); int failed = 0; ShellyColorUtils col = new ShellyColorUtils(); for (int i = 0; i < sensorUpdates.size(); i++) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java index 7ff8a699218e7..0e687307b74d8 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java @@ -507,7 +507,7 @@ private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly rs.isValid = sm.isValid = emeter.isValid = true; if (cs.state != null) { if (!getString(rs.state).equals(cs.state)) { - logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state, + logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state, mapValue(MAP_ROLLER_STATE, cs.state), updateChannels); } rs.state = mapValue(MAP_ROLLER_STATE, cs.state); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java index 17efc1d84ebfa..c37a9c854c28b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java @@ -131,14 +131,13 @@ public Shelly2ApiRpc(String thingName, ShellyThingConfiguration config, HttpClie @Override public void initialize() throws ShellyApiException { - if (!initialized) { - rpcSocket = new Shelly2RpcSocket(thingName, thingTable, config.deviceIp); - rpcSocket.addMessageHandler(this); - initialized = true; - } else { + if (initialized) { logger.debug("{}: Disconnect Rpc Socket on initialize", thingName); disconnect(); } + rpcSocket = new Shelly2RpcSocket(thingName, thingTable, config.deviceIp); + rpcSocket.addMessageHandler(this); + initialized = true; } @Override @@ -1211,6 +1210,9 @@ private void reconnect() throws ShellyApiException { } private void disconnect() { + if (rpcSocket.isConnected()) { + logger.debug("{}: Disconnect Rpc Socket", thingName); + } rpcSocket.disconnect(); } @@ -1220,8 +1222,10 @@ public Shelly2RpctInterface getRpcHandler() { @Override public void close() { - logger.debug("{}: Closing Rpc API (socket is {}, discovery={})", thingName, - rpcSocket.isConnected() ? "connected" : "disconnected", discovery); + if (initialized || rpcSocket.isConnected()) { + logger.debug("{}: Closing Rpc API (socket is {}, discovery={})", thingName, + rpcSocket.isConnected() ? "connected" : "disconnected", discovery); + } disconnect(); initialized = false; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java index faff2738ee27e..308ab06ed67ce 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java @@ -206,18 +206,24 @@ public void disconnect() { if (s.isOpen()) { logger.debug("{}: Disconnecting WebSocket ({} -> {})", thingName, s.getLocalAddress(), s.getRemoteAddress()); - s.disconnect(); } + s.disconnect(); s.close(StatusCode.NORMAL, "Socket closed"); session = null; } - client.stop(); } catch (Exception e) { if (e.getCause() instanceof InterruptedException) { logger.debug("{}: Unable to close socket - interrupted", thingName); // e.g. device was rebooted } else { logger.debug("{}: Unable to close socket", thingName, e); } + } finally { + // make sure client is stopped / thread terminates / socket resource is free up + try { + client.stop(); + } catch (Exception e) { + logger.debug("{}: Unable to close Web Socket", thingName, e); + } } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java index 7869b6fd79a37..5dbb81609aad5 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java @@ -15,7 +15,6 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*; -import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*; import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import java.util.ArrayList; @@ -112,11 +111,11 @@ public void setConfig(String thingName, ShellyThingConfiguration config) { public ShellySettingsDevice getDeviceInfo() throws ShellyApiException { ShellySettingsDevice info = new ShellySettingsDevice(); info.hostname = !config.serviceName.isEmpty() ? config.serviceName : ""; - info.fw = "1234"; - info.type = "SBBT"; + info.fw = ""; + info.type = "BLU"; info.mac = config.deviceAddress; info.auth = false; - info.gen = 99; + info.gen = 2; return info; } @@ -136,13 +135,13 @@ public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySe profile.gateway = getThing().getProperty(PROPERTY_GW_DEVICE); } - ShellySettingsDevice device = getDeviceInfo(); + profile.device = getDeviceInfo(); if (config.serviceName.isEmpty()) { config.serviceName = getString(profile.device.hostname); } - profile.fwDate = substringBefore(device.fw, "/"); - profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-"); - profile.status.update.oldVersion = profile.fwVersion; + + // for now we have no API to get this information + profile.fwDate = profile.fwVersion = profile.status.update.oldVersion = ""; profile.status.hasUpdate = profile.status.update.hasUpdate = false; if (profile.hasBattery) { @@ -239,7 +238,7 @@ public void onNotifyEvent(Shelly2RpcNotifyEvent message) { } logger.debug("{}: BLU Device discovered", thingName); if (e.data.name != null) { - profile.settings.name = buildBluServiceName(e.data.name, e.data.addr); + profile.settings.name = ShellyDeviceProfile.buildBluServiceName(e.data.name, e.data.addr); } break; case SHELLY2_EVENT_BLUDATA: @@ -317,18 +316,4 @@ public void onNotifyEvent(Shelly2RpcNotifyEvent message) { if (updated) { } } - - public static String buildBluServiceName(String name, String mac) throws IllegalArgumentException { - String model = name.contains("-") ? substringBefore(name, "-") : name; // e.g. SBBT-02C or just SBDW - switch (model) { - case SHELLYDT_BLUBUTTON: - return (THING_TYPE_SHELLYBLUBUTTON_STR + "-" + mac).toLowerCase(); - case SHELLYDT_BLUDW: - return (THING_TYPE_SHELLYBLUDW_STR + "-" + mac).toLowerCase(); - case SHELLYDT_BLUMOTION: - return (THING_TYPE_SHELLYBLUMOTION_STR + "-" + mac).toLowerCase(); - default: - throw new IllegalArgumentException("Unsupported BLU device model " + model); - } - } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java index 793b710f7db9e..db5da2e1e5e3f 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java @@ -145,15 +145,18 @@ public DiscoveryResult createResult(final ServiceInfo service) { boolean gen2 = "2".equals(service.getPropertyString("gen")); ShellyApiInterface api = null; + boolean auth = false; ShellySettingsDevice devInfo; try { api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) : new Shelly1HttpApi(name, config, httpClient); api.initialize(); devInfo = api.getDeviceInfo(); model = devInfo.type; + auth = devInfo.auth; if (devInfo.name != null) { deviceName = devInfo.name; } + profile = api.getDeviceProfile(thingType, devInfo); api.close(); logger.debug("{}: Shelly settings : {}", name, profile.settingsJson); @@ -191,6 +194,7 @@ public DiscoveryResult createResult(final ServiceInfo service) { addProperty(properties, PROPERTY_DEV_TYPE, thingType); addProperty(properties, PROPERTY_DEV_GEN, gen2 ? "2" : "1"); addProperty(properties, PROPERTY_DEV_MODE, mode); + addProperty(properties, PROPERTY_DEV_AUTH, auth ? "yes" : "no"); logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString()); String thingLabel = deviceName.isEmpty() ? name + " - " + address diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index 2871943edabb9..0daed7b48a0cd 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -187,21 +187,7 @@ public void initialize() { config.eventsSensorReport, config.eventsCoIoT, bindingConfig.autoCoIoT); start = initializeThing(); } catch (ShellyApiException e) { - ShellyApiResult res = e.getApiResult(); - String mid = ""; - if (e.isJsonError()) { // invalid JSON format - mid = "offline.status-error-unexpected-error"; - start = false; - } else if (isAuthorizationFailed(res)) { - mid = "offline.conf-error-access-denied"; - start = false; - } else if (profile.alwaysOn && e.isConnectionError()) { - mid = "offline.status-error-connect"; - } - if (!mid.isEmpty()) { - setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, mid, e.toString()); - } - logger.debug("{}: Unable to initialize: {}, retrying later", thingName, e.toString()); + start = handleApiException(e); } catch (IllegalArgumentException e) { logger.debug("{}: Unable to initialize, retrying later", thingName, e); } finally { @@ -215,6 +201,43 @@ public void initialize() { }, 2, TimeUnit.SECONDS); } + private boolean handleApiException(ShellyApiException e) { + ShellyApiResult res = e.getApiResult(); + ThingStatusDetail errorCode = ThingStatusDetail.COMMUNICATION_ERROR; + String status = ""; + boolean retry = true; + if (e.isJsonError()) { // invalid JSON format + logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e); + status = "offline.status-error-unexpected-error"; + errorCode = ThingStatusDetail.CONFIGURATION_ERROR; + retry = false; + } else if (res.isHttpAccessUnauthorized()) { + status = "offline.conf-error-access-denied"; + errorCode = ThingStatusDetail.CONFIGURATION_ERROR; + retry = false; + } else if (isWatchdogExpired()) { + status = "offline.status-error-watchdog"; + } else if (res.httpCode >= 400) { + logger.debug("{}: Unexpected API result: {}/{}", thingName, res.httpCode, res.httpReason, e); + status = "offline.status-error-unexpected-api-result"; + retry = false; + } else if (profile.alwaysOn && (e.isConnectionError() || res.isHttpTimeout())) { + status = "offline.status-error-connect"; + } + + if (!status.isEmpty()) { + setThingOffline(errorCode, status, e.toString()); + } else { + logger.debug("{}: Unable to initialize: {}, retrying later", thingName, e.toString()); + } + + if (!retry) { + api.close(); + } + + return retry; + } + @Override public ShellyThingConfiguration getThingConfig() { return config; @@ -464,10 +487,11 @@ public void handleCommand(ChannelUID channelUID, Command command) { requestUpdates(1, false); } } catch (ShellyApiException e) { - ShellyApiResult res = e.getApiResult(); - if (isAuthorizationFailed(res)) { + if (!handleApiException(e)) { return; } + + ShellyApiResult res = e.getApiResult(); if (res.isNotCalibrtated()) { logger.warn("{}: {}", thingName, messages.get("roller.calibrating")); } else { @@ -554,35 +578,7 @@ protected void refreshStatus() { } catch (ShellyApiException e) { // http call failed: go offline except for battery devices, which might be in // sleep mode. Once the next update is successful the device goes back online - String status = ""; - ShellyApiResult res = e.getApiResult(); - if (profile.alwaysOn && e.isConnectionError()) { - status = "offline.status-error-connect"; - } else if (res.isHttpAccessUnauthorized()) { - status = "offline.conf-error-access-denied"; - } else if (isWatchdogStarted()) { - if (!isWatchdogExpired()) { - logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); - if (profile.alwaysOn) { // suppress for battery powered sensors - logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); - } - } - } else if (e.isJSONException()) { - status = "offline.status-error-unexpected-api-result"; - logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e); - } else if (res.isHttpTimeout()) { - // Watchdog not started, e.g. device in sleep mode - if (isThingOnline()) { // ignore when already offline - status = "offline.status-error-watchdog"; - } - } else { - status = "offline.status-error-unexpected-api-result"; - logger.debug("{}: Unexpected API result: {}", thingName, res.response, e); - } - - if (!status.isEmpty()) { - setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, status); - } + handleApiException(e); } catch (NullPointerException | IllegalArgumentException e) { logger.debug("{}: Unable to refresh status: {}", thingName, messages.get("statusupdate.failed"), e); } finally { @@ -631,7 +627,7 @@ private void addStateOptions(ShellyDeviceProfile prf) { } if (prf.isRoller && prf.settings.favorites != null) { String channelId = mkChannelId(CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_FAV); - logger.debug("{}: Adding {} roler favorite(s) to channel description", thingName, + logger.debug("{}: Adding {} roler favorite(s) to channel description", thingName, prf.settings.favorites.size()); channelDefinitions.clearStateOptions(channelId); int fid = 1; @@ -1057,7 +1053,7 @@ private void checkVersion(ShellyDeviceProfile prf, ShellySettingsStatus status) String minVersion = !gen2 ? SHELLY_API_MIN_FWVERSION : SHELLY2_API_MIN_FWVERSION; if (version.compare(prf.fwVersion, minVersion) < 0) { logger.warn("{}: {}", prf.device.hostname, - messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); + messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate, minVersion)); } } if (!gen2 && bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0) @@ -1120,23 +1116,6 @@ public void startCoap(ShellyThingConfiguration config, ShellyDeviceProfile profi } } - /** - * Checks the http response for authorization error. - * If the authorization failed the binding can't access the device settings and determine the thing type. In this - * case the thing type shelly-unknown is set. - * - * @param result exception details including the http respone - * @return true if the authorization failed - */ - protected boolean isAuthorizationFailed(ShellyApiResult result) { - if (result.isHttpAccessUnauthorized()) { - // If the device is password protected the API doesn't provide settings to the device settings - setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-access-denied"); - return true; - } - return false; - } - /** * Change type of this thing. * @@ -1363,11 +1342,11 @@ public void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus s properties.put(PROPERTY_SERVICE_NAME, config.serviceName); String deviceName = getString(profile.settings.name); properties.put(PROPERTY_SERVICE_NAME, config.serviceName); - properties.put(PROPERTY_DEV_GEN, "1"); + properties.put(PROPERTY_DEV_GEN, !profile.isGen2 ? "1" : "2"); + properties.put(PROPERTY_DEV_AUTH, getBool(profile.device.auth) ? "yes" : "no"); if (!deviceName.isEmpty()) { properties.put(PROPERTY_DEV_NAME, deviceName); } - properties.put(PROPERTY_DEV_GEN, !profile.isGen2 ? "1" : "2"); // add status properties if (status.wifiSta != null) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java index 0c392678dbc00..8e097bf761959 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java @@ -13,7 +13,6 @@ package org.openhab.binding.shelly.internal.handler; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; -import static org.openhab.binding.shelly.internal.api2.ShellyBluApi.buildBluServiceName; import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*; import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; @@ -23,6 +22,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent; import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration; @@ -54,7 +54,7 @@ public void initialize() { public static void addBluThing(String gateway, Shelly2NotifyEvent e, ShellyThingTable thingTable) { String model = substringBefore(getString(e.data.name), "-").toUpperCase(); - String mac = e.data.addr.replace(":", ""); + String mac = e.data.addr.replaceAll(":", ""); String ttype = ""; logger.debug("{}: Create thing for new BLU device {}: {} / {}", gateway, e.data.name, model, mac); ThingTypeUID tuid; @@ -75,7 +75,7 @@ public static void addBluThing(String gateway, Shelly2NotifyEvent e, ShellyThing logger.debug("{}: Unsupported BLU device model {}, MAC={}", gateway, model, mac); return; } - String serviceName = buildBluServiceName(model, mac); + String serviceName = ShellyDeviceProfile.buildBluServiceName(getString(e.data.name), mac); Map properties = new TreeMap<>(); addProperty(properties, PROPERTY_MODEL_ID, model); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java index cbac83ffc2aa1..fc648d2eb7dd5 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java @@ -78,7 +78,7 @@ public static boolean updateDeviceStatus(ShellyThingInterface thingHandler, Shel if (status.tmp != null && getBool(status.tmp.isValid) && !thingHandler.getProfile().isSensor && status.tmp.tC != SHELLY_API_INVTEMP) { thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, - toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS)); + toQuantityType(getDouble(status.tmp.tC), DIGITS_TEMP, SIUnits.CELSIUS)); } else if (status.temperature != null && status.temperature != SHELLY_API_INVTEMP) { thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS)); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java index ff43076997254..93c8bd1ee0720 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java @@ -64,7 +64,7 @@ public ShellyMgrResponse generateContent(String path, Map para String action = getUrlParm(parameters, URLPARM_ACTION).toLowerCase(); String uidParm = getUrlParm(parameters, URLPARM_UID).toLowerCase(); - logger.debug("Generating overview for {} devices", getThingHandlers().size()); + logger.debug("Generating overview for {} devices", getThingHandlers().size()); String html = ""; Map properties = new HashMap<>(); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java index 6f03bfc79ba7a..7ecaa6232df2b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java @@ -315,7 +315,7 @@ public static Map createDeviceChannels(final Thing thing, final addChannel(thing, add, profile.settings.sleepTime != null, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME); // If device has more than 1 meter the channel accumulatedWatts receives the accumulated value - boolean accuChannel = profile.numMeters > 1 && !profile.isRoller && !profile.isRGBW2; + boolean accuChannel = profile.hasRelays && profile.numMeters > 1 && !profile.isRoller && !profile.isRGBW2; addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS); addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL); addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED); diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties index caa4b42438ff0..8ec3b279b7baf 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties @@ -34,7 +34,7 @@ message.versioncheck.update = INFO: New firmware available: current version: {0} message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration. message.command.failed = ERROR: Unable to process command {0} for channel {1} -message.command.init = Thing not yet initialized, command {0}�triggered initialization +message.command.init = Thing not yet initialized, command {0} triggered initialization message.status.unknown.initializing = Initializing or device in sleep mode. message.statusupdate.failed = Unable to update status message.status.managerstarted = Shelly Manager started at http(s)://{0}:{1}/shelly/manager @@ -120,7 +120,7 @@ thing-type.shelly.shellyproem50.description = Shelly Pro EM-50 - 2xPower Meter + thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch with Power Meter # BLU devices -thing-type.shelly.shellyblubutton.description = Shelly BLU Button +thing-type.shelly.shellyblubutton.description = Shelly BLU Button 1 thing-type.shelly.shellybludw.description = Shelly BLU Door/Window Sensor thing-type.shelly.shellyblumotion.description = Shelly BLU Motion Sensor @@ -247,7 +247,7 @@ channel-type.shelly.temperature4.description = Temperature of external Sensor #4 channel-type.shelly.temperature5.label = Temperature 5 channel-type.shelly.temperature6.description = Temperature of external Sensor #5 channel-type.shelly.targetTemp.label = Target Temperature -channel-type.shelly.targetTemp.description = Target Temperature in �C to be reached in auto-temperature mode +channel-type.shelly.targetTemp.description = Target Temperature in �C to be reached in auto-temperature mode channel-type.shelly.humidity.label = Humidity channel-type.shelly.humidity.description = Relative humidity (0..100%) channel-type.shelly.rollerShutter.label = Roller Control (0=open, 100=closed) From b98376700326c08a413c0822d62b3af99eb93a7f Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sat, 25 Nov 2023 04:27:05 -0700 Subject: [PATCH 115/146] [mqtt.homeassistant] VETO updates to read only channels (#15937) I.e. Button, Scene, and Binary Sensors. Also ensure we set up the CommandDescription, since some value types mights use it. Signed-off-by: Cody Cutrer --- .../internal/ComponentChannel.java | 27 +++++++++++++------ .../internal/component/BinarySensor.java | 4 ++- .../internal/component/Button.java | 3 ++- .../internal/component/Scene.java | 3 ++- .../internal/component/ButtonTests.java | 4 +++ 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java index d63a304d5efc7..d73a2e4c622c2 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java @@ -33,12 +33,14 @@ import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.type.AutoUpdatePolicy; import org.openhab.core.thing.type.ChannelDefinition; import org.openhab.core.thing.type.ChannelDefinitionBuilder; import org.openhab.core.thing.type.ChannelType; import org.openhab.core.thing.type.ChannelTypeBuilder; import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; +import org.openhab.core.types.CommandDescription; import org.openhab.core.types.StateDescriptionFragment; /** @@ -130,6 +132,7 @@ public static class Builder { private boolean retain; private boolean trigger; private boolean isAdvanced; + private @Nullable AutoUpdatePolicy autoUpdatePolicy; private @Nullable Integer qos; private @Nullable Predicate commandFilter; @@ -203,6 +206,11 @@ public Builder isAdvanced(boolean advanced) { return this; } + public Builder withAutoUpdatePolicy(@Nullable AutoUpdatePolicy autoUpdatePolicy) { + this.autoUpdatePolicy = autoUpdatePolicy; + return this; + } + public Builder commandFilter(@Nullable Predicate commandFilter) { this.commandFilter = commandFilter; return this; @@ -237,23 +245,26 @@ public ComponentChannel build(boolean addToComponent) { isAdvanced = true; } + ChannelTypeBuilder typeBuilder; if (this.trigger) { - type = ChannelTypeBuilder.trigger(channelTypeUID, label) - .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL)) - .isAdvanced(isAdvanced).build(); + typeBuilder = ChannelTypeBuilder.trigger(channelTypeUID, label); } else { - StateDescriptionFragment description = valueState.createStateDescription(commandTopic == null).build(); - type = ChannelTypeBuilder.state(channelTypeUID, label, channelState.getItemType()) - .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL)) - .withStateDescriptionFragment(description).isAdvanced(isAdvanced).build(); + StateDescriptionFragment stateDescription = valueState.createStateDescription(commandTopic == null) + .build(); + CommandDescription commandDescription = valueState.createCommandDescription().build(); + typeBuilder = ChannelTypeBuilder.state(channelTypeUID, label, channelState.getItemType()) + .withStateDescriptionFragment(stateDescription).withCommandDescription(commandDescription); } + type = typeBuilder.withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL)) + .isAdvanced(isAdvanced).build(); Configuration configuration = new Configuration(); configuration.put("config", component.getChannelConfigurationJson()); component.getHaID().toConfig(configuration); channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID) - .withKind(type.getKind()).withLabel(label).withConfiguration(configuration).build(); + .withKind(type.getKind()).withLabel(label).withConfiguration(configuration) + .withAutoUpdatePolicy(autoUpdatePolicy).build(); ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, type, channelTypeUID, channelStateUpdateListener); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java index d5e98c04a66b1..85b575a08341d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java @@ -22,6 +22,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener; import org.openhab.binding.mqtt.homeassistant.internal.listener.OffDelayUpdateStateListener; +import org.openhab.core.thing.type.AutoUpdatePolicy; import com.google.gson.annotations.SerializedName; @@ -72,7 +73,8 @@ public BinarySensor(ComponentFactory.ComponentConfiguration componentConfigurati OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff); buildChannel(SENSOR_CHANNEL_ID, value, "value", getListener(componentConfiguration, value)) - .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()).build(); + .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) + .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); } private ChannelStateUpdateListener getListener(ComponentFactory.ComponentConfiguration componentConfiguration, diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java index 97aa60f01b056..ad56f3f4e01a1 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java @@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; +import org.openhab.core.thing.type.AutoUpdatePolicy; import com.google.gson.annotations.SerializedName; @@ -53,6 +54,6 @@ public Button(ComponentFactory.ComponentConfiguration componentConfiguration) { buildChannel(BUTTON_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) - .build(); + .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java index 1d323c2defe15..3ad3b1521c514 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java @@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; +import org.openhab.core.thing.type.AutoUpdatePolicy; import com.google.gson.annotations.SerializedName; @@ -51,6 +52,6 @@ public Scene(ComponentFactory.ComponentConfiguration componentConfiguration) { buildChannel(SCENE_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) - .build(); + .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java index 943a6c4b0f275..9fdce64d12a16 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ButtonTests.java @@ -16,12 +16,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.type.AutoUpdatePolicy; /** * Tests for {@link Button} @@ -57,6 +59,8 @@ public void testButton() { assertChannel(component, Button.BUTTON_CHANNEL_ID, "", "esphome/single-car-gdo/button/restart/command", "Restart", TextValue.class); + assertThat(Objects.requireNonNull(component.getChannel(Button.BUTTON_CHANNEL_ID)).getChannel() + .getAutoUpdatePolicy(), is(AutoUpdatePolicy.VETO)); assertThrows(IllegalArgumentException.class, () -> component.getChannel(Button.BUTTON_CHANNEL_ID).getState().publishValue(new StringType("ON"))); From a02f0482de456eaf32423710ff9e191865c3e749 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 25 Nov 2023 12:33:59 +0100 Subject: [PATCH 116/146] Reorganize API classes (#15934) Resolves #15731 Signed-off-by: Jacob Laursen --- .../{dto => api/dto/clip1}/ApiVersion.java | 2 +- .../dto/clip1}/ApiVersionUtils.java | 2 +- .../dto/clip1}/BridgeConfigUpdate.java | 2 +- .../{dto => api/dto/clip1}/Capabilities.java | 2 +- .../dto/clip1}/ColorTemperature.java | 2 +- .../{dto => api/dto/clip1}/Command.java | 2 +- .../{dto => api/dto/clip1}/Config.java | 2 +- .../{dto => api/dto/clip1}/ConfigUpdate.java | 2 +- .../{dto => api/dto/clip1}/Control.java | 2 +- .../dto/clip1}/CreateUserRequest.java | 2 +- .../{dto => api/dto/clip1}/ErrorResponse.java | 2 +- .../{dto => api/dto/clip1}/FullConfig.java | 2 +- .../{dto => api/dto/clip1}/FullGroup.java | 2 +- .../{dto => api/dto/clip1}/FullHueObject.java | 2 +- .../{dto => api/dto/clip1}/FullLight.java | 2 +- .../{dto => api/dto/clip1}/FullSensor.java | 2 +- .../{dto => api/dto/clip1}/Group.java | 2 +- .../{dto => api/dto/clip1}/HueObject.java | 2 +- .../dto/clip1}/LightLevelConfigUpdate.java | 4 +- .../dto/clip1}/NewLightsResponse.java | 2 +- .../dto/clip1}/PresenceConfigUpdate.java | 4 +- .../{dto => api/dto/clip1}/Scene.java | 2 +- .../{dto => api/dto/clip1}/Schedule.java | 2 +- .../dto/clip1}/ScheduleUpdate.java | 2 +- .../dto/clip1}/SearchForLightsRequest.java | 2 +- .../dto/clip1}/SensorConfigUpdate.java | 4 +- .../dto/clip1}/SetAttributesRequest.java | 2 +- .../dto/clip1}/SoftwareUpdate.java | 2 +- .../{dto => api/dto/clip1}/State.java | 2 +- .../{dto => api/dto/clip1}/StateUpdate.java | 6 +- .../dto/clip1}/SuccessResponse.java | 2 +- .../dto/clip1}/TemperatureConfigUpdate.java | 4 +- .../internal/{dto => api/dto/clip1}/User.java | 2 +- .../internal/{dto => api/dto/clip1}/Util.java | 2 +- .../{ => api}/dto/clip2/ActionEntry.java | 2 +- .../internal/{ => api}/dto/clip2/Alerts.java | 4 +- .../{ => api}/dto/clip2/BridgeConfig.java | 2 +- .../internal/{ => api}/dto/clip2/Button.java | 4 +- .../{ => api}/dto/clip2/ButtonReport.java | 4 +- .../{ => api}/dto/clip2/ColorTemperature.java | 2 +- .../internal/{ => api}/dto/clip2/ColorXy.java | 2 +- .../{ => api}/dto/clip2/ContactReport.java | 4 +- .../internal/{ => api}/dto/clip2/Dimming.java | 2 +- .../{ => api}/dto/clip2/Dynamics.java | 2 +- .../internal/{ => api}/dto/clip2/Effects.java | 4 +- .../internal/{ => api}/dto/clip2/Error.java | 2 +- .../internal/{ => api}/dto/clip2/Event.java | 2 +- .../internal/{ => api}/dto/clip2/Gamut2.java | 2 +- .../{ => api}/dto/clip2/LightLevel.java | 2 +- .../{ => api}/dto/clip2/LightLevelReport.java | 2 +- .../{ => api}/dto/clip2/MetaData.java | 4 +- .../{ => api}/dto/clip2/MirekSchema.java | 2 +- .../internal/{ => api}/dto/clip2/Motion.java | 2 +- .../{ => api}/dto/clip2/MotionReport.java | 2 +- .../internal/{ => api}/dto/clip2/OnState.java | 2 +- .../internal/{ => api}/dto/clip2/PairXy.java | 2 +- .../internal/{ => api}/dto/clip2/Power.java | 4 +- .../{ => api}/dto/clip2/ProductData.java | 4 +- .../internal/{ => api}/dto/clip2/Recall.java | 6 +- .../{ => api}/dto/clip2/RelativeRotary.java | 2 +- .../{ => api}/dto/clip2/Resource.java | 22 +++---- .../dto/clip2/ResourceReference.java | 4 +- .../{ => api}/dto/clip2/Resources.java | 2 +- .../{ => api}/dto/clip2/RotaryReport.java | 4 +- .../{ => api}/dto/clip2/Rotation.java | 4 +- .../{ => api}/dto/clip2/RotationEvent.java | 4 +- .../{ => api}/dto/clip2/TamperReport.java | 4 +- .../{ => api}/dto/clip2/Temperature.java | 2 +- .../dto/clip2/TemperatureReport.java | 2 +- .../{ => api}/dto/clip2/TimedEffects.java | 2 +- .../{ => api}/dto/clip2/enums/ActionType.java | 2 +- .../{ => api}/dto/clip2/enums/Archetype.java | 2 +- .../dto/clip2/enums/BatteryStateType.java | 2 +- .../dto/clip2/enums/ButtonEventType.java | 2 +- .../dto/clip2/enums/ContactStateType.java | 2 +- .../dto/clip2/enums/DirectionType.java | 2 +- .../{ => api}/dto/clip2/enums/EffectType.java | 2 +- .../dto/clip2/enums/ResourceType.java | 2 +- .../dto/clip2/enums/RotationEventType.java | 2 +- .../dto/clip2/enums/SceneRecallAction.java | 2 +- .../clip2/enums/SmartSceneRecallAction.java | 2 +- .../dto/clip2/enums/SmartSceneState.java | 2 +- .../dto/clip2/enums/TamperStateType.java | 2 +- .../dto/clip2/enums/ZigbeeStatus.java | 2 +- .../{ => api}/dto/clip2/helper/Setters.java | 26 ++++---- .../serialization/InstantDeserializer.java | 2 +- .../hue/internal/connection/Clip2Bridge.java | 18 ++--- .../hue/internal/connection/HueBridge.java | 44 ++++++------- .../internal/console/HueCommandExtension.java | 10 +-- .../discovery/Clip2ThingDiscoveryService.java | 10 +-- .../discovery/HueDeviceDiscoveryService.java | 8 +-- .../internal/handler/Clip2BridgeHandler.java | 14 ++-- .../internal/handler/Clip2ThingHandler.java | 38 +++++------ .../internal/handler/GroupStatusListener.java | 4 +- .../internal/handler/HueBridgeHandler.java | 20 +++--- .../hue/internal/handler/HueClient.java | 10 +-- .../hue/internal/handler/HueGroupHandler.java | 10 +-- .../hue/internal/handler/HueLightHandler.java | 10 +-- .../internal/handler/HueSensorHandler.java | 8 +-- .../internal/handler/LightStateConverter.java | 12 ++-- .../internal/handler/LightStatusListener.java | 2 +- .../handler/SensorStatusListener.java | 2 +- .../internal/handler/sensors/ClipHandler.java | 4 +- .../handler/sensors/DimmerSwitchHandler.java | 4 +- .../sensors/GeofencePresenceHandler.java | 6 +- .../handler/sensors/LightLevelHandler.java | 8 +-- .../handler/sensors/PresenceHandler.java | 8 +-- .../handler/sensors/TapSwitchHandler.java | 4 +- .../handler/sensors/TemperatureHandler.java | 8 +-- .../binding/hue/internal/ApiVersionTest.java | 2 +- .../binding/hue/internal/HueBridgeTest.java | 2 +- .../hue/internal/LightStateConverterTest.java | 8 +-- .../binding/hue/internal/SceneTest.java | 6 +- .../hue/internal/clip2/Clip2DtoTest.java | 66 +++++++++---------- .../internal/handler/HueLightHandlerTest.java | 8 +-- .../hue/internal/handler/HueLightState.java | 2 +- .../HueDeviceDiscoveryServiceOSGiTest.java | 2 +- 117 files changed, 308 insertions(+), 308 deletions(-) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/ApiVersion.java (98%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/ApiVersionUtils.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/BridgeConfigUpdate.java (98%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Capabilities.java (90%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/ColorTemperature.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Command.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Config.java (98%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/ConfigUpdate.java (96%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Control.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/CreateUserRequest.java (96%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/ErrorResponse.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/FullConfig.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/FullGroup.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/FullHueObject.java (98%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/FullLight.java (96%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/FullSensor.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Group.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/HueObject.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/LightLevelConfigUpdate.java (89%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/NewLightsResponse.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/PresenceConfigUpdate.java (87%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Scene.java (98%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Schedule.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/ScheduleUpdate.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/SearchForLightsRequest.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/SensorConfigUpdate.java (83%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/SetAttributesRequest.java (96%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/SoftwareUpdate.java (96%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/State.java (99%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/StateUpdate.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/SuccessResponse.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/TemperatureConfigUpdate.java (82%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/User.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{dto => api/dto/clip1}/Util.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/ActionEntry.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Alerts.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/BridgeConfig.java (92%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Button.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/ButtonReport.java (90%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/ColorTemperature.java (98%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/ColorXy.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/ContactReport.java (89%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Dimming.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Dynamics.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Effects.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Error.java (92%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Event.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Gamut2.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/LightLevel.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/LightLevelReport.java (96%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/MetaData.java (89%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/MirekSchema.java (96%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Motion.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/MotionReport.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/OnState.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/PairXy.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Power.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/ProductData.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Recall.java (85%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/RelativeRotary.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Resource.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/ResourceReference.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Resources.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/RotaryReport.java (90%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Rotation.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/RotationEvent.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/TamperReport.java (89%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/Temperature.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/TemperatureReport.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/TimedEffects.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/ActionType.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/Archetype.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/BatteryStateType.java (90%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/ButtonEventType.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/ContactStateType.java (90%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/DirectionType.java (91%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/EffectType.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/ResourceType.java (97%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/RotationEventType.java (90%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/SceneRecallAction.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/SmartSceneRecallAction.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/SmartSceneState.java (93%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/TamperStateType.java (90%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/enums/ZigbeeStatus.java (95%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/dto/clip2/helper/Setters.java (94%) rename bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/{ => api}/serialization/InstantDeserializer.java (95%) diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ApiVersion.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ApiVersion.java index e15dbd4ddc018..0180aff0a84a6 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ApiVersion.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ApiVersionUtils.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ApiVersionUtils.java index 2e4cbb6c7b65b..f3f91cad77903 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ApiVersionUtils.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/BridgeConfigUpdate.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/BridgeConfigUpdate.java index 6e30ceeacf662..9a8fac8f1289e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/BridgeConfigUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; /** * Collection of updates to the bridge configuration. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Capabilities.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Capabilities.java similarity index 90% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Capabilities.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Capabilities.java index 80c82ad8194cd..e8ac93edb75cf 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Capabilities.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Capabilities.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; /** * Collection of capabilities for lights. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ColorTemperature.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ColorTemperature.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ColorTemperature.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ColorTemperature.java index 4dce4b6c24e18..a57043b052489 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ColorTemperature.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ColorTemperature.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; /** * Collection of color temperature capabilities to control lights. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Command.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Command.java index 520f24e90fabb..b943bf30ead6e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Command.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import com.google.gson.Gson; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Config.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Config.java index 1e67ff79b17bc..600294c0e26b2 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Config.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.ArrayList; import java.util.Date; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ConfigUpdate.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ConfigUpdate.java index 00c0afb87a3f7..6e32fbdc880d9 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ConfigUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import static java.util.stream.Collectors.joining; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Control.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Control.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Control.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Control.java index 36ae345977df5..077dda488fe59 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Control.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Control.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/CreateUserRequest.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/CreateUserRequest.java index 2e29b90508261..9e8c431210d7b 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/CreateUserRequest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; /** * diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ErrorResponse.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ErrorResponse.java index 86e910b6e7a4c..4a99bc6f7c119 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ErrorResponse.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.util.List; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullConfig.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullConfig.java index 7e23c63322478..2dadab5f71bfc 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullConfig.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.ArrayList; import java.util.List; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullGroup.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullGroup.java index 2bdd1df35a455..b7d89c9dd96d1 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullGroup.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.util.ArrayList; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullHueObject.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullHueObject.java index 8189fb6950cde..6483a72c0deb8 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullHueObject.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import static org.openhab.binding.hue.internal.HueBindingConstants.NORMALIZE_ID_REGEX; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullLight.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullLight.java index 4e339e421e1b2..a679ddde52008 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullLight.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.time.Duration; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullSensor.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullSensor.java index cd78f43a2f6ec..f0ae7f0e9578b 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/FullSensor.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.util.Map; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Group.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Group.java index 62a5d581810de..a725be8578f69 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Group.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; /** * Basic group information. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/HueObject.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/HueObject.java index 9dabe99359b55..6dc94bb70be9a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/HueObject.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.util.Map; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/LightLevelConfigUpdate.java similarity index 89% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/LightLevelConfigUpdate.java index 4fac25c8137c9..de51dc303d9b6 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/LightLevelConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; -import static org.openhab.binding.hue.internal.dto.FullSensor.*; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.*; /** * Updates the configuration of a light level sensor diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/NewLightsResponse.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/NewLightsResponse.java index 50a74aaefae30..05f438d98f0de 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/NewLightsResponse.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; /** * @author Q42 - Initial contribution diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/PresenceConfigUpdate.java similarity index 87% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/PresenceConfigUpdate.java index eeaae93fdaa06..baad51a5854d0 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/PresenceConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; -import static org.openhab.binding.hue.internal.dto.FullSensor.*; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.*; /** * Updates the configuration of a presence sensor diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Scene.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Scene.java index ee13d71ac6222..03d179006f345 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Scene.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.util.ArrayList; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Schedule.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Schedule.java index 6892b3ad0d220..73353c7ed4a70 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Schedule.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.util.Map; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ScheduleUpdate.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ScheduleUpdate.java index 7fea44816fa0b..4c64837f9f2de 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/ScheduleUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.Date; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SearchForLightsRequest.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SearchForLightsRequest.java index 91174e678a37d..5c3e1abd28406 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SearchForLightsRequest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.List; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SensorConfigUpdate.java similarity index 83% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SensorConfigUpdate.java index 50452c71af67c..f2a5c4a916525 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SensorConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; -import static org.openhab.binding.hue.internal.dto.FullSensor.CONFIG_ON; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.CONFIG_ON; /** * Collection of updates to the sensor configuration. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SetAttributesRequest.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SetAttributesRequest.java index 09ae2c155ccef..f3f6f4b694c53 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SetAttributesRequest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.List; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SoftwareUpdate.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SoftwareUpdate.java index 461b8589b3b36..de5ffa82d4441 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SoftwareUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; /** * Details of a bridge firmware update. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/State.java similarity index 99% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/State.java index 8bb646be256bf..2879c9d6f72e0 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/State.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.Arrays; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/StateUpdate.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/StateUpdate.java index 53bd9d48a35d6..8844d1c5b6a88 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/StateUpdate.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; -import org.openhab.binding.hue.internal.dto.State.AlertMode; -import org.openhab.binding.hue.internal.dto.State.Effect; +import org.openhab.binding.hue.internal.api.dto.clip1.State.AlertMode; +import org.openhab.binding.hue.internal.api.dto.clip1.State.Effect; /** * Collection of updates to the state of a light. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SuccessResponse.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SuccessResponse.java index f0eb43d4455c5..d6c53d44cf851 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/SuccessResponse.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.lang.reflect.Type; import java.util.List; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/TemperatureConfigUpdate.java similarity index 82% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/TemperatureConfigUpdate.java index ff66933adfd20..38960594f646e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/TemperatureConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; -import static org.openhab.binding.hue.internal.dto.FullSensor.CONFIG_LED_INDICATION; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.CONFIG_LED_INDICATION; /** * Updates the configuration of a temperature sensor diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/User.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/User.java index 375734d92fedd..17c729239faec 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/User.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.util.Date; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Util.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Util.java index 9b4dbf4001205..5459813dc0c29 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip1/Util.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto; +package org.openhab.binding.hue.internal.api.dto.clip1; import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ActionEntry.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ActionEntry.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ActionEntry.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ActionEntry.java index 835d4918b653f..aa9c9deeb72e8 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ActionEntry.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ActionEntry.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Alerts.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Alerts.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Alerts.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Alerts.java index 6d8b86bd9b279..1020a99dd2b6e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Alerts.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Alerts.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.List; import java.util.Objects; @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType; import com.google.gson.annotations.SerializedName; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/BridgeConfig.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/BridgeConfig.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/BridgeConfig.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/BridgeConfig.java index 66d4250ec39dd..c013078d26fb6 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/BridgeConfig.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/BridgeConfig.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Button.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Button.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Button.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Button.java index c4f75f6730432..f3135dc7690a9 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Button.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Button.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType; import com.google.gson.annotations.SerializedName; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ButtonReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ButtonReport.java similarity index 90% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ButtonReport.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ButtonReport.java index 947c028258f56..a334d21ebc41c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ButtonReport.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ButtonReport.java @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Instant; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType; /** * DTO for CLIP 2 button report. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ColorTemperature.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ColorTemperature.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ColorTemperature.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ColorTemperature.java index 4b5fd5a5eb5dc..c41720be2c48c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ColorTemperature.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ColorTemperature.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ColorXy.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ColorXy.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ColorXy.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ColorXy.java index 9b909c51bc4d3..7c19f21d3b751 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ColorXy.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ColorXy.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ContactReport.java similarity index 89% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ContactReport.java index 99c8c2a835c0b..7e1743ebed84e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ContactReport.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Instant; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.clip2.enums.ContactStateType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContactStateType; /** * DTO for CLIP 2 home security alarm contact. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Dimming.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Dimming.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Dimming.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Dimming.java index c51c80c51e222..b6b847d59964f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Dimming.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Dimming.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Dynamics.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Dynamics.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Dynamics.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Dynamics.java index fd898e6739c21..7b912f5d5a8ca 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Dynamics.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Dynamics.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Duration; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Effects.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Effects.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Effects.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Effects.java index d73020a5e0283..b5050ef4fa6df 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Effects.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Effects.java @@ -10,14 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.List; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType; import com.google.gson.annotations.SerializedName; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Error.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Error.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Error.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Error.java index 24437682965dc..90ae7ab0e6787 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Error.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Error.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Event.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Event.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Event.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Event.java index 04d397df97331..c7e9fe2db9081 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Event.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Event.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.lang.reflect.Type; import java.util.ArrayList; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Gamut2.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Gamut2.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Gamut2.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Gamut2.java index b218e57fb732b..bd0179f2779f2 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Gamut2.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Gamut2.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/LightLevel.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/LightLevel.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/LightLevel.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/LightLevel.java index 9921fba9790e7..673e32533b522 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/LightLevel.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/LightLevel.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/LightLevelReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/LightLevelReport.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/LightLevelReport.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/LightLevelReport.java index 189c64f4d1bd6..7c14d62410d56 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/LightLevelReport.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/LightLevelReport.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Instant; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MetaData.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MetaData.java similarity index 89% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MetaData.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MetaData.java index b416619468d33..1c21cd6fa1769 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MetaData.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MetaData.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype; import com.google.gson.annotations.SerializedName; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MirekSchema.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MirekSchema.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MirekSchema.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MirekSchema.java index 3a3dee205012a..940c4a0399fd9 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MirekSchema.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MirekSchema.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Motion.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Motion.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Motion.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Motion.java index 3b0ce60c89f06..9b6de00ecd666 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Motion.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Motion.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MotionReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MotionReport.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MotionReport.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MotionReport.java index 8146e524ea6ea..c6078eba8ee56 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/MotionReport.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/MotionReport.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Instant; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/OnState.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/OnState.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/OnState.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/OnState.java index 7ab06791af3cd..d0ba4056fad0c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/OnState.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/OnState.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/PairXy.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/PairXy.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/PairXy.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/PairXy.java index fa608129e8f8a..bf70fc73e2373 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/PairXy.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/PairXy.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; /** * DTO that contains an x and y pair of doubles. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Power.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Power.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Power.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Power.java index 3becfcdd736d0..caa2c21a92e6d 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Power.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Power.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.BatteryStateType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.types.State; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ProductData.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ProductData.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ProductData.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ProductData.java index 357d272af9055..1d009e0a9594e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ProductData.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ProductData.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype; import com.google.gson.annotations.SerializedName; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Recall.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Recall.java similarity index 85% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Recall.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Recall.java index f489e1cdae48b..35af29a2fd769 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Recall.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Recall.java @@ -10,14 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Duration; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction; -import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.SceneRecallAction; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.SmartSceneRecallAction; /** * DTO for scene and smart scene recall. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RelativeRotary.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RelativeRotary.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RelativeRotary.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RelativeRotary.java index 3de553a83fbf4..e56e0024639b6 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RelativeRotary.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RelativeRotary.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java index 2791bbeab535c..04e92905ba6cc 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.math.BigDecimal; import java.math.MathContext; @@ -26,16 +26,16 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ContactStateType; -import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; -import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction; -import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction; -import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneState; -import org.openhab.binding.hue.internal.dto.clip2.enums.TamperStateType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContactStateType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.SceneRecallAction; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.SmartSceneRecallAction; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.SmartSceneState; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.TamperStateType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ZigbeeStatus; import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ResourceReference.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ResourceReference.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ResourceReference.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ResourceReference.java index f8baf1c0f1a9d..47298a816f907 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ResourceReference.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/ResourceReference.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; /** * DTO that contains an API reference element. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resources.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resources.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resources.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resources.java index ce554136df111..85f01d64f3694 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resources.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resources.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.ArrayList; import java.util.List; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RotaryReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RotaryReport.java similarity index 90% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RotaryReport.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RotaryReport.java index 3d2159afb46d1..9b7c1fa6e0006 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RotaryReport.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RotaryReport.java @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Instant; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.RotationEventType; /** * DTO for CLIP 2 relative rotary report. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Rotation.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Rotation.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Rotation.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Rotation.java index e6e6f18d45186..fa2364bc951d2 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Rotation.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Rotation.java @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.DirectionType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RotationEvent.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RotationEvent.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RotationEvent.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RotationEvent.java index 1712c79878552..8c0e4679bf06e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/RotationEvent.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/RotationEvent.java @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.RotationEventType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TamperReport.java similarity index 89% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TamperReport.java index 50d250898adf4..50b30ef1dcb4e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TamperReport.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Instant; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.clip2.enums.TamperStateType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.TamperStateType; /** * DTO for CLIP 2 home security tamper switch. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Temperature.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Temperature.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Temperature.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Temperature.java index 401476a70a798..f86ea8fa17451 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Temperature.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Temperature.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TemperatureReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TemperatureReport.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TemperatureReport.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TemperatureReport.java index 1833daca65065..83088abaeab61 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TemperatureReport.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TemperatureReport.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Instant; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TimedEffects.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TimedEffects.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TimedEffects.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TimedEffects.java index e59dc68f470de..60730bdcb49da 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TimedEffects.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/TimedEffects.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2; +package org.openhab.binding.hue.internal.api.dto.clip2; import java.time.Duration; import java.util.Objects; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ActionType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ActionType.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ActionType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ActionType.java index 93484e6d2e69c..e11a7e8ebe094 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ActionType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ActionType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/Archetype.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/Archetype.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/Archetype.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/Archetype.java index 30c0b1c6eaea1..80f0d97914df3 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/Archetype.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/Archetype.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/BatteryStateType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/BatteryStateType.java similarity index 90% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/BatteryStateType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/BatteryStateType.java index 959409c46d356..624598cc8e709 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/BatteryStateType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/BatteryStateType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ButtonEventType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ButtonEventType.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ButtonEventType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ButtonEventType.java index ad2b7f8228f23..6a1287ffebbee 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ButtonEventType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ButtonEventType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ContactStateType.java similarity index 90% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ContactStateType.java index 2892e77ca18fb..c50ab0d3cf41a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ContactStateType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/DirectionType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/DirectionType.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/DirectionType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/DirectionType.java index 1af54749985f9..4ed4e918eab1c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/DirectionType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/DirectionType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/EffectType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/EffectType.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/EffectType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/EffectType.java index 09663a145971c..8709122498204 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/EffectType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/EffectType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import java.util.Set; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ResourceType.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ResourceType.java index 56c2f0061be3e..2562de2b43657 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ResourceType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import java.util.EnumSet; import java.util.Set; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/RotationEventType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/RotationEventType.java similarity index 90% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/RotationEventType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/RotationEventType.java index 38e1ccd689b65..359dd6235897f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/RotationEventType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/RotationEventType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SceneRecallAction.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SceneRecallAction.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SceneRecallAction.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SceneRecallAction.java index 36987c96025b9..c7057d0b734a8 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SceneRecallAction.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SceneRecallAction.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SmartSceneRecallAction.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SmartSceneRecallAction.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SmartSceneRecallAction.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SmartSceneRecallAction.java index b12c45282fdb5..f2be1d9431af3 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SmartSceneRecallAction.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SmartSceneRecallAction.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SmartSceneState.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SmartSceneState.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SmartSceneState.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SmartSceneState.java index 470f9fc7dc64c..e54a1f823760a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/SmartSceneState.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/SmartSceneState.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/TamperStateType.java similarity index 90% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/TamperStateType.java index 23199b1af50d3..afa632dfb8927 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/TamperStateType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ZigbeeStatus.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ZigbeeStatus.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ZigbeeStatus.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ZigbeeStatus.java index f9c33d1fe515d..b97e812ccb06e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ZigbeeStatus.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/enums/ZigbeeStatus.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.enums; +package org.openhab.binding.hue.internal.api.dto.clip2.enums; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/helper/Setters.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/helper/Setters.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/helper/Setters.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/helper/Setters.java index df400725a3165..641a8025718c6 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/helper/Setters.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/helper/Setters.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.dto.clip2.helper; +package org.openhab.binding.hue.internal.api.dto.clip2.helper; import java.math.BigDecimal; import java.time.Duration; @@ -21,18 +21,18 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.Alerts; -import org.openhab.binding.hue.internal.dto.clip2.ColorTemperature; -import org.openhab.binding.hue.internal.dto.clip2.ColorXy; -import org.openhab.binding.hue.internal.dto.clip2.Dimming; -import org.openhab.binding.hue.internal.dto.clip2.Effects; -import org.openhab.binding.hue.internal.dto.clip2.MetaData; -import org.openhab.binding.hue.internal.dto.clip2.MirekSchema; -import org.openhab.binding.hue.internal.dto.clip2.OnState; -import org.openhab.binding.hue.internal.dto.clip2.Resource; -import org.openhab.binding.hue.internal.dto.clip2.TimedEffects; -import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType; -import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType; +import org.openhab.binding.hue.internal.api.dto.clip2.Alerts; +import org.openhab.binding.hue.internal.api.dto.clip2.ColorTemperature; +import org.openhab.binding.hue.internal.api.dto.clip2.ColorXy; +import org.openhab.binding.hue.internal.api.dto.clip2.Dimming; +import org.openhab.binding.hue.internal.api.dto.clip2.Effects; +import org.openhab.binding.hue.internal.api.dto.clip2.MetaData; +import org.openhab.binding.hue.internal.api.dto.clip2.MirekSchema; +import org.openhab.binding.hue.internal.api.dto.clip2.OnState; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.TimedEffects; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.PercentType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/serialization/InstantDeserializer.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/serialization/InstantDeserializer.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/serialization/InstantDeserializer.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/serialization/InstantDeserializer.java index 1843c3c0417eb..510c94418f145 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/serialization/InstantDeserializer.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/serialization/InstantDeserializer.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal.serialization; +package org.openhab.binding.hue.internal.api.serialization; import java.lang.reflect.Type; import java.time.Instant; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java index 9a8abb1cce9b7..da156b655107e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/Clip2Bridge.java @@ -70,18 +70,18 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise.Completable; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.openhab.binding.hue.internal.dto.CreateUserRequest; -import org.openhab.binding.hue.internal.dto.SuccessResponse; -import org.openhab.binding.hue.internal.dto.clip2.BridgeConfig; -import org.openhab.binding.hue.internal.dto.clip2.Event; -import org.openhab.binding.hue.internal.dto.clip2.Resource; -import org.openhab.binding.hue.internal.dto.clip2.ResourceReference; -import org.openhab.binding.hue.internal.dto.clip2.Resources; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip1.CreateUserRequest; +import org.openhab.binding.hue.internal.api.dto.clip1.SuccessResponse; +import org.openhab.binding.hue.internal.api.dto.clip2.BridgeConfig; +import org.openhab.binding.hue.internal.api.dto.clip2.Event; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference; +import org.openhab.binding.hue.internal.api.dto.clip2.Resources; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.serialization.InstantDeserializer; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.HttpUnauthorizedException; import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler; -import org.openhab.binding.hue.internal.serialization.InstantDeserializer; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.HttpUtil; import org.slf4j.Logger; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java index 650af07aa89e9..7efdee40dd67c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java @@ -48,28 +48,28 @@ import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.openhab.binding.hue.internal.dto.ApiVersion; -import org.openhab.binding.hue.internal.dto.ApiVersionUtils; -import org.openhab.binding.hue.internal.dto.Config; -import org.openhab.binding.hue.internal.dto.ConfigUpdate; -import org.openhab.binding.hue.internal.dto.CreateUserRequest; -import org.openhab.binding.hue.internal.dto.ErrorResponse; -import org.openhab.binding.hue.internal.dto.FullConfig; -import org.openhab.binding.hue.internal.dto.FullGroup; -import org.openhab.binding.hue.internal.dto.FullHueObject; -import org.openhab.binding.hue.internal.dto.FullLight; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.Group; -import org.openhab.binding.hue.internal.dto.HueObject; -import org.openhab.binding.hue.internal.dto.NewLightsResponse; -import org.openhab.binding.hue.internal.dto.Scene; -import org.openhab.binding.hue.internal.dto.Schedule; -import org.openhab.binding.hue.internal.dto.ScheduleUpdate; -import org.openhab.binding.hue.internal.dto.SearchForLightsRequest; -import org.openhab.binding.hue.internal.dto.SetAttributesRequest; -import org.openhab.binding.hue.internal.dto.StateUpdate; -import org.openhab.binding.hue.internal.dto.SuccessResponse; -import org.openhab.binding.hue.internal.dto.Util; +import org.openhab.binding.hue.internal.api.dto.clip1.ApiVersion; +import org.openhab.binding.hue.internal.api.dto.clip1.ApiVersionUtils; +import org.openhab.binding.hue.internal.api.dto.clip1.Config; +import org.openhab.binding.hue.internal.api.dto.clip1.ConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.CreateUserRequest; +import org.openhab.binding.hue.internal.api.dto.clip1.ErrorResponse; +import org.openhab.binding.hue.internal.api.dto.clip1.FullConfig; +import org.openhab.binding.hue.internal.api.dto.clip1.FullGroup; +import org.openhab.binding.hue.internal.api.dto.clip1.FullHueObject; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.Group; +import org.openhab.binding.hue.internal.api.dto.clip1.HueObject; +import org.openhab.binding.hue.internal.api.dto.clip1.NewLightsResponse; +import org.openhab.binding.hue.internal.api.dto.clip1.Scene; +import org.openhab.binding.hue.internal.api.dto.clip1.Schedule; +import org.openhab.binding.hue.internal.api.dto.clip1.ScheduleUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.SearchForLightsRequest; +import org.openhab.binding.hue.internal.api.dto.clip1.SetAttributesRequest; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.SuccessResponse; +import org.openhab.binding.hue.internal.api.dto.clip1.Util; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.DeviceOffException; import org.openhab.binding.hue.internal.exceptions.EmptyResponseException; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java index b299cf5693ff7..c75ecb0d44fce 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java @@ -25,11 +25,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.MetaData; -import org.openhab.binding.hue.internal.dto.clip2.Resource; -import org.openhab.binding.hue.internal.dto.clip2.ResourceReference; -import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.MetaData; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException; import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/Clip2ThingDiscoveryService.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/Clip2ThingDiscoveryService.java index 829476e36ebcb..1f1f9cb42e675 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/Clip2ThingDiscoveryService.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/Clip2ThingDiscoveryService.java @@ -25,11 +25,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.clip2.MetaData; -import org.openhab.binding.hue.internal.dto.clip2.Resource; -import org.openhab.binding.hue.internal.dto.clip2.ResourceReference; -import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.MetaData; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException; import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java index d05a10943372d..ec6ea4ca56d17 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java @@ -25,10 +25,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.FullGroup; -import org.openhab.binding.hue.internal.dto.FullHueObject; -import org.openhab.binding.hue.internal.dto.FullLight; -import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.FullGroup; +import org.openhab.binding.hue.internal.api.dto.clip1.FullHueObject; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; import org.openhab.binding.hue.internal.handler.HueBridgeHandler; import org.openhab.binding.hue.internal.handler.HueGroupHandler; import org.openhab.binding.hue.internal.handler.HueLightHandler; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java index 2891cf9e4ea7b..c94d39ef85199 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java @@ -30,17 +30,17 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hue.internal.api.dto.clip2.MetaData; +import org.openhab.binding.hue.internal.api.dto.clip2.ProductData; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference; +import org.openhab.binding.hue.internal.api.dto.clip2.Resources; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; import org.openhab.binding.hue.internal.config.Clip2BridgeConfig; import org.openhab.binding.hue.internal.connection.Clip2Bridge; import org.openhab.binding.hue.internal.connection.HueTlsTrustManagerProvider; import org.openhab.binding.hue.internal.discovery.Clip2ThingDiscoveryService; -import org.openhab.binding.hue.internal.dto.clip2.MetaData; -import org.openhab.binding.hue.internal.dto.clip2.ProductData; -import org.openhab.binding.hue.internal.dto.clip2.Resource; -import org.openhab.binding.hue.internal.dto.clip2.ResourceReference; -import org.openhab.binding.hue.internal.dto.clip2.Resources; -import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException; import org.openhab.binding.hue.internal.exceptions.HttpUnauthorizedException; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java index a436f7f889a4f..aef0ff5bf27ae 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java @@ -35,26 +35,26 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.hue.internal.action.DynamicsActions; +import org.openhab.binding.hue.internal.api.dto.clip2.Alerts; +import org.openhab.binding.hue.internal.api.dto.clip2.ColorXy; +import org.openhab.binding.hue.internal.api.dto.clip2.Dimming; +import org.openhab.binding.hue.internal.api.dto.clip2.Effects; +import org.openhab.binding.hue.internal.api.dto.clip2.Gamut2; +import org.openhab.binding.hue.internal.api.dto.clip2.MetaData; +import org.openhab.binding.hue.internal.api.dto.clip2.MirekSchema; +import org.openhab.binding.hue.internal.api.dto.clip2.ProductData; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference; +import org.openhab.binding.hue.internal.api.dto.clip2.Resources; +import org.openhab.binding.hue.internal.api.dto.clip2.TimedEffects; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.SceneRecallAction; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.SmartSceneRecallAction; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ZigbeeStatus; +import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters; import org.openhab.binding.hue.internal.config.Clip2ThingConfig; -import org.openhab.binding.hue.internal.dto.clip2.Alerts; -import org.openhab.binding.hue.internal.dto.clip2.ColorXy; -import org.openhab.binding.hue.internal.dto.clip2.Dimming; -import org.openhab.binding.hue.internal.dto.clip2.Effects; -import org.openhab.binding.hue.internal.dto.clip2.Gamut2; -import org.openhab.binding.hue.internal.dto.clip2.MetaData; -import org.openhab.binding.hue.internal.dto.clip2.MirekSchema; -import org.openhab.binding.hue.internal.dto.clip2.ProductData; -import org.openhab.binding.hue.internal.dto.clip2.Resource; -import org.openhab.binding.hue.internal.dto.clip2.ResourceReference; -import org.openhab.binding.hue.internal.dto.clip2.Resources; -import org.openhab.binding.hue.internal.dto.clip2.TimedEffects; -import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType; -import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; -import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction; -import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction; -import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus; -import org.openhab.binding.hue.internal.dto.clip2.helper.Setters; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException; import org.openhab.core.i18n.TimeZoneProvider; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java index 27705e8710fa0..b8cdad3b9fe0d 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java @@ -15,8 +15,8 @@ import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullGroup; -import org.openhab.binding.hue.internal.dto.Scene; +import org.openhab.binding.hue.internal.api.dto.clip1.FullGroup; +import org.openhab.binding.hue.internal.api.dto.clip1.Scene; /** * The {@link GroupStatusListener} is notified when a group status has changed or a group has been removed or added. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java index 30991dd198e08..5a1f61ef9f0c1 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java @@ -35,20 +35,20 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.hue.internal.api.dto.clip1.ApiVersionUtils; +import org.openhab.binding.hue.internal.api.dto.clip1.Config; +import org.openhab.binding.hue.internal.api.dto.clip1.ConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullConfig; +import org.openhab.binding.hue.internal.api.dto.clip1.FullGroup; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.Scene; +import org.openhab.binding.hue.internal.api.dto.clip1.State; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.binding.hue.internal.config.HueBridgeConfig; import org.openhab.binding.hue.internal.connection.HueBridge; import org.openhab.binding.hue.internal.connection.HueTlsTrustManagerProvider; import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService; -import org.openhab.binding.hue.internal.dto.ApiVersionUtils; -import org.openhab.binding.hue.internal.dto.Config; -import org.openhab.binding.hue.internal.dto.ConfigUpdate; -import org.openhab.binding.hue.internal.dto.FullConfig; -import org.openhab.binding.hue.internal.dto.FullGroup; -import org.openhab.binding.hue.internal.dto.FullLight; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.Scene; -import org.openhab.binding.hue.internal.dto.State; -import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.DeviceOffException; import org.openhab.binding.hue.internal.exceptions.EmptyResponseException; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java index ea9bca0f5e4a5..fe79b34b1974a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java @@ -14,12 +14,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hue.internal.api.dto.clip1.ConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullGroup; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService; -import org.openhab.binding.hue.internal.dto.ConfigUpdate; -import org.openhab.binding.hue.internal.dto.FullGroup; -import org.openhab.binding.hue.internal.dto.FullLight; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.StateUpdate; /** * Access to the Hue system for light handlers. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java index 118edaef33353..8eb767f7ddbbc 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java @@ -25,11 +25,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.ColorTemperature; -import org.openhab.binding.hue.internal.dto.FullGroup; -import org.openhab.binding.hue.internal.dto.Scene; -import org.openhab.binding.hue.internal.dto.State; -import org.openhab.binding.hue.internal.dto.StateUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.ColorTemperature; +import org.openhab.binding.hue.internal.api.dto.clip1.FullGroup; +import org.openhab.binding.hue.internal.api.dto.clip1.Scene; +import org.openhab.binding.hue.internal.api.dto.clip1.State; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.IncreaseDecreaseType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java index a574f8f0900ce..9dda779406d26 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java @@ -24,11 +24,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.Capabilities; -import org.openhab.binding.hue.internal.dto.ColorTemperature; -import org.openhab.binding.hue.internal.dto.FullLight; -import org.openhab.binding.hue.internal.dto.State; -import org.openhab.binding.hue.internal.dto.StateUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.Capabilities; +import org.openhab.binding.hue.internal.api.dto.clip1.ColorTemperature; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; +import org.openhab.binding.hue.internal.api.dto.clip1.State; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.IncreaseDecreaseType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java index 8d3210a57ed0c..9f274be669d01 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java @@ -13,7 +13,7 @@ package org.openhab.binding.hue.internal.handler; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.binding.hue.internal.dto.FullSensor.*; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.*; import static org.openhab.core.thing.Thing.*; import java.time.LocalDateTime; @@ -27,9 +27,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; -import org.openhab.binding.hue.internal.dto.StateUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java index 525d1f4520bca..cefb960886426 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java @@ -16,12 +16,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.ColorTemperature; -import org.openhab.binding.hue.internal.dto.State; -import org.openhab.binding.hue.internal.dto.State.AlertMode; -import org.openhab.binding.hue.internal.dto.State.ColorMode; -import org.openhab.binding.hue.internal.dto.State.Effect; -import org.openhab.binding.hue.internal.dto.StateUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.ColorTemperature; +import org.openhab.binding.hue.internal.api.dto.clip1.State; +import org.openhab.binding.hue.internal.api.dto.clip1.State.AlertMode; +import org.openhab.binding.hue.internal.api.dto.clip1.State.ColorMode; +import org.openhab.binding.hue.internal.api.dto.clip1.State.Effect; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.IncreaseDecreaseType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java index bcdaad913f9bb..7c8be4d58e935 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java @@ -13,7 +13,7 @@ package org.openhab.binding.hue.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; /** * The {@link LightStatusListener} is notified when a light status has changed or a light has been removed or added. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java index 6a113c1311ff5..c572e5a43cdfe 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java @@ -13,7 +13,7 @@ package org.openhab.binding.hue.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; /** * The {@link SensorStatusListener} is notified when a sensor status has changed or a sensor has been removed or added. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java index 89a5c88e3f622..f98329b90dc38 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java @@ -20,8 +20,8 @@ import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.thing.Thing; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java index 8adc3ed736a11..7225aa65e7469 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java @@ -25,8 +25,8 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java index ac992a7dc507a..5ac1407f75ef9 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java @@ -13,14 +13,14 @@ package org.openhab.binding.hue.internal.handler.sensors; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.binding.hue.internal.dto.FullSensor.STATE_PRESENCE; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.STATE_PRESENCE; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.OnOffType; 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 933fb05c604ec..b6c0156f5d15b 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 @@ -13,16 +13,16 @@ package org.openhab.binding.hue.internal.handler.sensors; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.binding.hue.internal.dto.FullSensor.*; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.*; import java.math.BigDecimal; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.LightLevelConfigUpdate; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.LightLevelConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java index be50280afedd8..39caa20297866 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java @@ -13,15 +13,15 @@ package org.openhab.binding.hue.internal.handler.sensors; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.binding.hue.internal.dto.FullSensor.*; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.*; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.PresenceConfigUpdate; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.PresenceConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueClient; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java index 217301cc364d7..81053688bc484 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java @@ -25,8 +25,8 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; 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 3a664bff22e87..c2464aafa2321 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 @@ -13,16 +13,16 @@ package org.openhab.binding.hue.internal.handler.sensors; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.binding.hue.internal.dto.FullSensor.*; +import static org.openhab.binding.hue.internal.api.dto.clip1.FullSensor.*; import java.math.BigDecimal; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.FullSensor; -import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; -import org.openhab.binding.hue.internal.dto.TemperatureConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullSensor; +import org.openhab.binding.hue.internal.api.dto.clip1.SensorConfigUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.TemperatureConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.QuantityType; diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java index 7cb58b7a21a3c..53817f81d637b 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.hue.internal.dto.ApiVersion; +import org.openhab.binding.hue.internal.api.dto.clip1.ApiVersion; /** * @author Samuel Leisering - Initial contribution diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java index a207a3c5c8ccf..6b606b4705c76 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java @@ -25,9 +25,9 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpStatus; import org.junit.jupiter.api.Test; +import org.openhab.binding.hue.internal.api.dto.clip1.Scene; import org.openhab.binding.hue.internal.config.HueBridgeConfig; import org.openhab.binding.hue.internal.connection.HueBridge; -import org.openhab.binding.hue.internal.dto.Scene; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.core.i18n.CommunicationException; import org.openhab.core.i18n.ConfigurationException; diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java index 3640bd996a15f..87b5d4192f7cc 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java @@ -19,10 +19,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.hue.internal.dto.ColorTemperature; -import org.openhab.binding.hue.internal.dto.State; -import org.openhab.binding.hue.internal.dto.State.ColorMode; -import org.openhab.binding.hue.internal.dto.StateUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.ColorTemperature; +import org.openhab.binding.hue.internal.api.dto.clip1.State; +import org.openhab.binding.hue.internal.api.dto.clip1.State.ColorMode; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.binding.hue.internal.handler.LightStateConverter; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java index 3498c857025b5..2ea2bd57fd7db 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java @@ -19,9 +19,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.hue.internal.dto.FullGroup; -import org.openhab.binding.hue.internal.dto.Scene; -import org.openhab.binding.hue.internal.dto.State; +import org.openhab.binding.hue.internal.api.dto.clip1.FullGroup; +import org.openhab.binding.hue.internal.api.dto.clip1.Scene; +import org.openhab.binding.hue.internal.api.dto.clip1.State; /** * @author HJiang - initial contribution diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java index 79b32776d512d..a42a26b9a6302 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java @@ -29,39 +29,39 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.hue.internal.dto.clip2.ActionEntry; -import org.openhab.binding.hue.internal.dto.clip2.Alerts; -import org.openhab.binding.hue.internal.dto.clip2.Button; -import org.openhab.binding.hue.internal.dto.clip2.ContactReport; -import org.openhab.binding.hue.internal.dto.clip2.Dimming; -import org.openhab.binding.hue.internal.dto.clip2.Effects; -import org.openhab.binding.hue.internal.dto.clip2.Event; -import org.openhab.binding.hue.internal.dto.clip2.LightLevel; -import org.openhab.binding.hue.internal.dto.clip2.MetaData; -import org.openhab.binding.hue.internal.dto.clip2.MirekSchema; -import org.openhab.binding.hue.internal.dto.clip2.Motion; -import org.openhab.binding.hue.internal.dto.clip2.Power; -import org.openhab.binding.hue.internal.dto.clip2.ProductData; -import org.openhab.binding.hue.internal.dto.clip2.RelativeRotary; -import org.openhab.binding.hue.internal.dto.clip2.Resource; -import org.openhab.binding.hue.internal.dto.clip2.ResourceReference; -import org.openhab.binding.hue.internal.dto.clip2.Resources; -import org.openhab.binding.hue.internal.dto.clip2.Rotation; -import org.openhab.binding.hue.internal.dto.clip2.RotationEvent; -import org.openhab.binding.hue.internal.dto.clip2.TamperReport; -import org.openhab.binding.hue.internal.dto.clip2.Temperature; -import org.openhab.binding.hue.internal.dto.clip2.TimedEffects; -import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType; -import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype; -import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType; -import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType; -import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType; -import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType; -import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus; -import org.openhab.binding.hue.internal.dto.clip2.helper.Setters; -import org.openhab.binding.hue.internal.serialization.InstantDeserializer; +import org.openhab.binding.hue.internal.api.dto.clip2.ActionEntry; +import org.openhab.binding.hue.internal.api.dto.clip2.Alerts; +import org.openhab.binding.hue.internal.api.dto.clip2.Button; +import org.openhab.binding.hue.internal.api.dto.clip2.ContactReport; +import org.openhab.binding.hue.internal.api.dto.clip2.Dimming; +import org.openhab.binding.hue.internal.api.dto.clip2.Effects; +import org.openhab.binding.hue.internal.api.dto.clip2.Event; +import org.openhab.binding.hue.internal.api.dto.clip2.LightLevel; +import org.openhab.binding.hue.internal.api.dto.clip2.MetaData; +import org.openhab.binding.hue.internal.api.dto.clip2.MirekSchema; +import org.openhab.binding.hue.internal.api.dto.clip2.Motion; +import org.openhab.binding.hue.internal.api.dto.clip2.Power; +import org.openhab.binding.hue.internal.api.dto.clip2.ProductData; +import org.openhab.binding.hue.internal.api.dto.clip2.RelativeRotary; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference; +import org.openhab.binding.hue.internal.api.dto.clip2.Resources; +import org.openhab.binding.hue.internal.api.dto.clip2.Rotation; +import org.openhab.binding.hue.internal.api.dto.clip2.RotationEvent; +import org.openhab.binding.hue.internal.api.dto.clip2.TamperReport; +import org.openhab.binding.hue.internal.api.dto.clip2.Temperature; +import org.openhab.binding.hue.internal.api.dto.clip2.TimedEffects; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.BatteryStateType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.DirectionType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.RotationEventType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ZigbeeStatus; +import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters; +import org.openhab.binding.hue.internal.api.serialization.InstantDeserializer; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java index 5f47d7c43cf52..7ba9acd98a170 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java @@ -23,10 +23,10 @@ import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.openhab.binding.hue.internal.dto.FullConfig; -import org.openhab.binding.hue.internal.dto.FullLight; -import org.openhab.binding.hue.internal.dto.State.ColorMode; -import org.openhab.binding.hue.internal.dto.StateUpdate; +import org.openhab.binding.hue.internal.api.dto.clip1.FullConfig; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; +import org.openhab.binding.hue.internal.api.dto.clip1.State.ColorMode; +import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java index c0e8b14e2dfe8..c12fc8c10cc1e 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java @@ -13,7 +13,7 @@ package org.openhab.binding.hue.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.dto.State.ColorMode; +import org.openhab.binding.hue.internal.api.dto.clip1.State.ColorMode; /** * Builder for the current state of a hue light. diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java index 6be21f452cc4d..b1cac633dbcca 100644 --- a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java +++ b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java @@ -32,10 +32,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openhab.binding.hue.internal.api.dto.clip1.FullLight; import org.openhab.binding.hue.internal.config.HueBridgeConfig; import org.openhab.binding.hue.internal.connection.HueBridge; import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService; -import org.openhab.binding.hue.internal.dto.FullLight; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.handler.HueBridgeHandler; import org.openhab.core.config.core.Configuration; From ad0f44c07dd8f89d3348d8472bcca6ac7a9a8f89 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sat, 25 Nov 2023 04:47:00 -0700 Subject: [PATCH 117/146] [mqtt.homeassistant] interpret a dimmable light as OFF properly from zigbee2mqtt (#15925) * [mqtt.homeassistant] interpret a dimmable light as OFF properly from zigbee2mqtt zigbee2mqtt can send a brightness of say 99, with a state of OFF, when a bulb is off. make sure if state is sent, it overrides all other inferences * handle brightness but not color bulbs --------- Signed-off-by: Cody Cutrer --- .../internal/component/JSONSchemaLight.java | 31 ++++++++++++------- .../component/JSONSchemaLightTests.java | 3 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java index 93dcd606a6958..a86f6488de5ba 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java @@ -253,26 +253,38 @@ public void updateChannelState(ChannelUID channel, State state) { } } + boolean off = false; if (jsonState.state != null) { onOffValue.update(onOffValue.parseCommand(new StringType(jsonState.state))); + off = onOffValue.getChannelState().equals(OnOffType.OFF); if (brightnessValue.getChannelState() instanceof UnDefType) { - brightnessValue.update(brightnessValue.parseCommand((OnOffType) onOffValue.getChannelState())); + brightnessValue.update(off ? PercentType.ZERO : PercentType.HUNDRED); } if (colorValue.getChannelState() instanceof UnDefType) { - colorValue.update(colorValue.parseCommand((OnOffType) onOffValue.getChannelState())); + colorValue.update(off ? HSBType.BLACK : HSBType.WHITE); } } + PercentType brightness; + if (off) { + brightness = PercentType.ZERO; + } else if (brightnessValue.getChannelState() instanceof PercentType percentValue) { + brightness = percentValue; + } else { + brightness = PercentType.HUNDRED; + } + if (jsonState.brightness != null) { - brightnessValue.update( - brightnessValue.parseCommand(new DecimalType(Objects.requireNonNull(jsonState.brightness)))); + if (!off) { + brightness = (PercentType) brightnessValue + .parseMessage(new DecimalType(Objects.requireNonNull(jsonState.brightness))); + } + brightnessValue.update(brightness); if (colorValue.getChannelState() instanceof HSBType) { HSBType color = (HSBType) colorValue.getChannelState(); - colorValue.update(new HSBType(color.getHue(), color.getSaturation(), - (PercentType) brightnessValue.getChannelState())); + colorValue.update(new HSBType(color.getHue(), color.getSaturation(), brightness)); } else { - colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO, - (PercentType) brightnessValue.getChannelState())); + colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO, brightness)); } } @@ -284,9 +296,6 @@ public void updateChannelState(ChannelUID channel, State state) { } if (jsonState.color != null) { - PercentType brightness = brightnessValue.getChannelState() instanceof PercentType - ? (PercentType) brightnessValue.getChannelState() - : PercentType.HUNDRED; // This corresponds to "deprecated" color mode handling, since we're not checking which color // mode is currently active. // HS is highest priority, then XY, then RGB diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLightTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLightTests.java index e1c50de28992f..604836dfc4eef 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLightTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLightTests.java @@ -119,6 +119,9 @@ public void testBrightnessAndOnOff() throws InterruptedException { assertState(component, Light.BRIGHTNESS_CHANNEL_ID, new PercentType(new BigDecimal(128 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128))); + publishMessage("zigbee2mqtt/light/state", "{ \"state\": \"OFF\", \"brightness\": 128 }"); + assertState(component, Light.BRIGHTNESS_CHANNEL_ID, PercentType.ZERO); + sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, PercentType.HUNDRED); assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"ON\",\"brightness\":255}"); From a51114273a3b3fc8b0ac800470c600c9b891054f Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sat, 25 Nov 2023 14:36:20 +0100 Subject: [PATCH 118/146] [nanoleaf] Fix typos (#15956) Signed-off-by: mueller-ma --- bundles/org.openhab.binding.nanoleaf/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.nanoleaf/README.md b/bundles/org.openhab.binding.nanoleaf/README.md index 5eef9761961cd..8ce55a9bf9bca 100644 --- a/bundles/org.openhab.binding.nanoleaf/README.md +++ b/bundles/org.openhab.binding.nanoleaf/README.md @@ -172,7 +172,7 @@ The controller bridge has the following channels: | rhythmActive | Switch | Activity state of the rhythm module | Yes | | rhythmMode | Number | Sound source for the rhythm module. 0=Microphone, 1=Aux cable | No | | state | Image | Shows the current state of your panels with colors. | Yes | -| swipe | Trigger | [Canvas / Shapes Only] Detects Swipes over the panel.LEFT, RIGHT, UP, DOWN events are supported. | Yes | +| swipe | Trigger | [Canvas / Shapes Only] Detects Swipes over the panel. LEFT, RIGHT, UP, DOWN events are supported. | Yes | A lightpanel thing has the following channels: From 846ab71ac159668b1b14e109351c9b179a4efd91 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sat, 25 Nov 2023 14:36:52 +0100 Subject: [PATCH 119/146] Add SAT warning when jSerialComm, PureJavaComm imports are used (#15952) Add-ons should use the OH serial transport and not their own serial communication libraries. Related to #7573 Signed-off-by: Wouter Born --- tools/static-code-analysis/checkstyle/ruleset.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/static-code-analysis/checkstyle/ruleset.properties b/tools/static-code-analysis/checkstyle/ruleset.properties index acac1eb1ac612..491f7c9101753 100644 --- a/tools/static-code-analysis/checkstyle/ruleset.properties +++ b/tools/static-code-analysis/checkstyle/ruleset.properties @@ -1,6 +1,6 @@ checkstyle.headerCheck.content=^/\\*\\*$\\n^ \\* Copyright \\(c\\) {0}-{1} Contributors to the openHAB project$\\n^ \\*$\\n^ \\* See the NOTICE file\\(s\\) distributed with this work for additional$\\n^ \\* information.$\\n^ \\*$\\n^ \\* This program and the accompanying materials are made available under the$\\n^ \\* terms of the Eclipse Public License 2\\.0 which is available at$\\n^ \\* http://www.eclipse.org/legal/epl\\-2\\.0$\\n^ \\*$\\n^ \\* SPDX-License-Identifier: EPL-2.0$ checkstyle.headerCheck.values=2010,2023 -checkstyle.forbiddenPackageUsageCheck.forbiddenPackages=com.google.common,gnu.io,javax.comm,org.apache.commons,org.joda.time,org.junit.Assert,org.junit.Test,si.uom,tech.units +checkstyle.forbiddenPackageUsageCheck.forbiddenPackages=com.fazecast.jSerialComm,com.google.common,gnu.io,javax.comm,org.apache.commons,org.joda.time,org.junit.Assert,org.junit.Test,purejavacomm,si.uom,tech.units checkstyle.forbiddenPackageUsageCheck.exceptions= checkstyle.requiredFilesCheck.files=pom.xml checkstyle.karafAddonFeatureCheck.featureNameMappings=-transform-:-transformation-,-io-:-misc- From 83e1decb67d87db5fd9aef343838574afe84675d Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sat, 25 Nov 2023 22:56:04 +0100 Subject: [PATCH 120/146] [lifx] Add support for new LIFX products (#15957) Allows for discovering all the new LIFX products and their features: * LIFX Neon * LIFX String See: * https://github.com/LIFX/products/blob/master/products.json * https://www.lifx.com.au/products/neon-flex-2m-indoor * https://www.lifx.com.au/products/lifx-string-light Signed-off-by: Wouter Born --- bundles/org.openhab.binding.lifx/README.md | 2 ++ .../java/org/openhab/binding/lifx/internal/LifxProduct.java | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.lifx/README.md b/bundles/org.openhab.binding.lifx/README.md index 105ddb8d8071a..ba9438a48ced9 100644 --- a/bundles/org.openhab.binding.lifx/README.md +++ b/bundles/org.openhab.binding.lifx/README.md @@ -28,6 +28,8 @@ The following table lists the thing types of the supported LIFX devices: | LIFX+ BR30 | colorirlight | | | | | LIFX Beam | colormzlight | +| LIFX Neon | colormzlight | +| LIFX String | colormzlight | | LIFX Z | colormzlight | | | | | LIFX Tile | tilelight | 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 d60e03e5e6502..76890e7485cf5 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 @@ -141,7 +141,11 @@ public enum LifxProduct { 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)); + PRODUCT_138(138, "LIFX Candle", new Features(TR_1500_9000, COLOR, MATRIX)), + PRODUCT_141(141, "LIFX Neon", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)), + PRODUCT_142(142, "LIFX Neon", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)), + PRODUCT_143(143, "LIFX String", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)), + PRODUCT_144(144, "LIFX String", new Features(TR_1500_9000, COLOR, EXTENDED_MULTIZONE, MULTIZONE)); /** * Enumerates the product features. From aea6f2c170106a713e530b991b25b501d31ee6f8 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Sun, 26 Nov 2023 00:27:03 +0100 Subject: [PATCH 121/146] [shelly] Support for Plus Dimmer 10v (#15950) * Implements #15949: Plus Dimmer 10v added * category DimmableLight does not exist; README updated --------- Signed-off-by: Markus Michels --- bundles/org.openhab.binding.shelly/README.md | 27 +++++++++++++++++++ .../discovery/ShellyThingCreator.java | 9 +++++++ .../resources/OH-INF/i18n/shelly.properties | 1 + .../OH-INF/thing/shellyGen1_lights.xml | 2 +- .../OH-INF/thing/shellyGen1_relay.xml | 4 +-- .../OH-INF/thing/shellyGen2_relay.xml | 14 +++++++++- 6 files changed, 52 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index fde35de2e69ee..d007aecebb813 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -1178,6 +1178,33 @@ Refer to [Smartify Roller Shutters with openHAB and Shelly](doc/UseCaseSmartRoll | | totalKWH | Number | yes | Total energy consumption in kwh since the device powered up (resets on restart) | | | lastUpdate | DateTime | yes | Timestamp of the last measurement | +### Shelly Plus Dimmer 10v (thing-type: shellyplus10v) + +| Group | Channel | Type | read-only | Description | +| ----- | ------------ | -------- | --------- | --------------------------------------------------------------------------------- | +| relay | brightness | Dimmer | r/w | Currently selected brightness. | +| | outputName | String | yes | Logical name of this relay output as configured in the Shelly App | +| | input1 | Switch | yes | ON: Input/Button for input 1 is powered, see general notes on channels | +| | button1 | Trigger | yes | Event trigger, see section Button Events | +| | lastEvent1 | String | yes | Last event type (S/SS/SSS/L) for input 1 | +| | eventCount1 | Number | yes | Counter gets incremented every time the device issues a button event. | +| | input2 | Switch | yes | ON: Input/Button for channel 2 is powered, see general notes on channels | +| | button2 | Trigger | yes | Event trigger, see section Button Events | +| | lastEvent2 | String | yes | Last event type (S/SS/SSS/L) for input 2 | +| | eventCount2 | Number | yes | Counter gets incremented every time the device issues a button event. | +| | autoOn | Number | r/w | Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds | +| | autoOff | Number | r/w | Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds | +| | timerActive | Switch | yes | Relay #1: ON: An auto-on/off timer is active | +| meter | currentWatts | Number | yes | Current power consumption in Watts | +| | lastPower1 | Number | yes | Energy consumption for a round minute, 1 minute ago | +| | totalKWH | Number | yes | Total energy consumption in kwh since the device powered up (resets on restart) | +| | lastUpdate | DateTime | yes | Timestamp of the last measurement | + +`Note: The Dimmer should be calibrated using the device Web UI or Shelly App.` + +Using the Thing configuration option `brightnessAutoOn` you could decide if the light is turned on when a brightness > 0 is set. +`true`: Brightness will be set and device output is powered = light turns on with the new brightness +`false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off. ### Shelly Plus i4, i4DC (thing-types: shellyplusi4, shellyplusi4dc) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java index 9417205a5f9eb..3804516327469 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java @@ -80,6 +80,7 @@ public class ShellyThingCreator { public static final String SHELLYDT_PLUSHT = "SNSN-0013A"; public static final String SHELLYDT_PLUSSMOKE = "SNSN-0031Z"; public static final String SHELLYDT_PLUSDIMMERUS = "SNDM-0013US"; + public static final String SHELLYDT_PLUSDIMMER10V = "SNGW-0A11WW010"; public static final String SHELLYDT_PLUSWALLDISPLAY = "SAWD-0A1XX10EU1"; // Shelly Pro Series @@ -168,6 +169,7 @@ public class ShellyThingCreator { public static final String THING_TYPE_SHELLYPLUSPLUGS_STR = "shellyplusplug"; public static final String THING_TYPE_SHELLYPLUSPLUGUS_STR = "shellyplusplugus"; public static final String THING_TYPE_SHELLYPLUSDIMMERUS_STR = "shellypluswdus"; + public static final String THING_TYPE_SHELLYPLUSDIMMER10V_STR = "shellyplus10v"; // Shelly Wall Display public static final String THING_TYPE_SHELLYPLUSWALLDISPLAY_STR = "shellywalldisplay"; @@ -276,6 +278,10 @@ public class ShellyThingCreator { THING_TYPE_SHELLYPLUSPLUGUS_STR); public static final ThingTypeUID THING_TYPE_SHELLYPLUSDIMMERUS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUSDIMMERUS_STR); + public static final ThingTypeUID THING_TYPE_SHELLYPLUSDIMMER10V = new ThingTypeUID(BINDING_ID, + THING_TYPE_SHELLYPLUSDIMMER10V_STR); + + // Shelly Wall Display public static final ThingTypeUID THING_TYPE_SHELLYPLUSWALLDISPLAY = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUSWALLDISPLAY_STR); @@ -356,6 +362,7 @@ public class ShellyThingCreator { THING_TYPE_MAPPING.put(SHELLYDT_PLUSHT, THING_TYPE_SHELLYPLUSHT_STR); THING_TYPE_MAPPING.put(SHELLYDT_PLUSSMOKE, THING_TYPE_SHELLYPLUSSMOKE_STR); THING_TYPE_MAPPING.put(SHELLYDT_PLUSDIMMERUS, THING_TYPE_SHELLYPLUSDIMMERUS_STR); + THING_TYPE_MAPPING.put(SHELLYDT_PLUSDIMMER10V, THING_TYPE_SHELLYPLUSDIMMER10V_STR); // Plus Mini Series THING_TYPE_MAPPING.put(SHELLYDT_MINI1, THING_TYPE_SHELLYMINI1_STR); @@ -435,6 +442,8 @@ public class ShellyThingCreator { THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSHT_STR, THING_TYPE_SHELLYPLUSHT_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSSMOKE_STR, THING_TYPE_SHELLYPLUSSMOKE_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSDIMMERUS_STR, THING_TYPE_SHELLYPLUSDIMMERUS_STR); + THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSDIMMER10V_STR, THING_TYPE_SHELLYPLUSDIMMER10V_STR); + THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSWALLDISPLAY_STR, THING_TYPE_SHELLYPLUSWALLDISPLAY_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYMINI1_STR, THING_TYPE_SHELLYMINI1_STR); diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties index 8ec3b279b7baf..2c2801bfcbc34 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties @@ -99,6 +99,7 @@ thing-type.shelly.shellyplusi4dc.description = Shelly Plus i4DC - 4xDC Input Dev thing-type.shelly.shellyplusht.description = Shelly Plus HT - Humidity and Temperature sensor with display thing-type.shelly.shellyplussmoke.description = Shelly Plus Smoke - Smoke Detector with Alarm thing-type.shelly.shellypluswdus.description = Shelly Wall Dimmer US Device +thing-type.shelly.shellyplus10v.description = Shelly Plus Dimmer 10V # Wall displays thing-type.shelly.shellywalldisplay.description = Shelly Wall Display with sensors and input/output diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_lights.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_lights.xml index 2269fa96c43d3..92313c5c503ea 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_lights.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_lights.xml @@ -237,7 +237,7 @@ Dimmer @text/channel-type.shelly.whiteBrightness.description - DimmableLight + Light diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_relay.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_relay.xml index c2ef67421c9a1..0a252956acb5b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_relay.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen1_relay.xml @@ -255,7 +255,6 @@ @text/thing-type.shelly.shellydimmer.description - DimmableLight @@ -269,7 +268,6 @@ @text/thing-type.shelly.shellydimmer2.description - DimmableLight @@ -469,7 +467,7 @@ Dimmer @text/channel-type.shelly.dimmerBrightness.description - DimmableLight + Light diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml index b66b8137fc01e..1a0d7bb605152 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml @@ -357,7 +357,19 @@ @text/thing-type.shelly.shellypluswdus.description - DimmableLight + + + + + + + serviceName + + + + + + @text/thing-type.shelly.shellyplus10v.description From c710780f351ab0378adc26422cb16e999bf7f4ad Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 26 Nov 2023 17:56:02 +0000 Subject: [PATCH 122/146] [growatt] rule actions to setup (dis-) charging programs Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 37 ++ .../internal/action/GrowattActions.java | 92 ++++ .../growatt/internal/cloud/ApiException.java | 34 ++ .../growatt/internal/cloud/GrowattCloud.java | 504 ++++++++++++++++++ .../config/GrowattInverterConfiguration.java | 14 + .../factory/GrowattHandlerFactory.java | 9 +- .../handler/GrowattInverterHandler.java | 64 ++- .../resources/OH-INF/i18n/growatt.properties | 18 + .../resources/OH-INF/thing/thing-types.xml | 17 + .../binding/growatt/test/GrowattTest.java | 110 ++++ 10 files changed, 895 insertions(+), 4 deletions(-) create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/ApiException.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index ecc33c1f4a1f4..902e758142034 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -30,6 +30,8 @@ The `inverter` thing requires configuration of its serial number resp. `deviceId | Name | Type | Description | Required | |-----------|---------|------------------------------------------------------------------------------------------|----------| | deviceId | text | Device serial number or id as configured in the Growatt cloud and the Grott application. | yes | +| userName | text | User name for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | +| password | text | Password for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | ## Channels @@ -131,6 +133,41 @@ The list of all possible channels is as follows: | erac-today | Number:Dimensionless | ERAC count today. | yes | | erac-total | Number:Dimensionless | Total ERAC count. | yes | +## Rule Actions + +This binding includes rule actions, which allow you to setup programs for battery charging and discharging. +Each inverter thing has a separate actions instance, which can be retrieved as follows. + +```php +val growattActions = getActions("growatt", "growatt:inverter:home:sph") +``` + +Where the first parameter must always be `growatt` and the second must be the full inverter thing UID. +Once the action instance has been retrieved, you can invoke the following methods. + +```php +growattActions.setupChargingProgram(int chargingPower, int targetSOC, boolean allowAcCharging, String startTime, String stopTime, boolean programEnable) + +growattActions.setupDischargingProgram(int dischargingPower, int targetSOC, String startTime, String stopTime, boolean programEnable) +``` + +| Parameter | Description | +|------------------|---------------------------------------------------------------------------------------| +| chargingPower | The rate of charging the battery 1%..100% (e.g. 50) | +| dischargingPower | The rate of discharging the battery 1%..100% (e.g. 100) | +| targetSOC | The target battery SOC (state of charge) when the program stops. (e.g. 20) | +| allowAcCharging | Allow the battery to be charged from the AC mains supply. (e.g. true, false) | +| startTime | String representation of the local time when the program shall start. (e.g. "00:15") | +| stopTime | String representation of the local time when the program shall stop. (e.g. "06:45") | +| programEnable | Disable / enable the program. (e.g. true, false) | + +Example: + +```php +// charge battery from AC supply, at 20% charge rate, from 00:15 .. 06:45, up to 75% fill level +growattActions.setupChargingProgram(20, 75, true, "00:15", "06:45", true) +``` + ## Full Example ### Example `.things` file diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java new file mode 100644 index 0000000000000..62d0252dd30d4 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.action; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.growatt.internal.handler.GrowattInverterHandler; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.RuleAction; +import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.binding.ThingActionsScope; +import org.openhab.core.thing.binding.ThingHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of the {@link ThingActions} interface used for setting up battery charging and discharging programs. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@ThingActionsScope(name = "growatt") +@NonNullByDefault +public class GrowattActions implements ThingActions { + + private final Logger logger = LoggerFactory.getLogger(GrowattActions.class); + private @Nullable GrowattInverterHandler handler; + + public static void setupChargingProgram(ThingActions actions, int chargingPower, int targetSOC, + boolean allowAcCharging, String startTime, String stopTime, boolean programEnable) { + ((GrowattActions) actions).setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, + programEnable); + } + + public static void setupDischargingProgram(ThingActions actions, int dischargingPower, int targetSOC, + String startTime, String stopTime, boolean programEnable) { + ((GrowattActions) actions).setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, + programEnable); + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + this.handler = (handler instanceof GrowattInverterHandler growattHandler) ? growattHandler : null; + } + + @RuleAction(label = "@text/actions.charging.label", description = "@text/actions.charging.description") + public void setupChargingProgram( + @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") int chargingPower, + @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") int targetSOC, + @ActionInput(name = "allow-ac-charging", label = "@text/actions.allow-ac-charging.label", description = "@text/actions.allow-ac-charging.description") boolean allowAcCharging, + @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, + @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, + @ActionInput(name = "program-enable", label = "@text/actions.program-enable.label", description = "@text/actions.program-enable.description") boolean programEnable) { + // + GrowattInverterHandler handler = this.handler; + if (handler != null) { + handler.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable); + } else { + logger.warn("ThingHandler is null."); + } + } + + @RuleAction(label = "@text/actions.discharging.label", description = "@text/actions.discharging.description") + public void setupDischargingProgram( + @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") int dischargingPower, + @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") int targetSOC, + @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, + @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, + @ActionInput(name = "program-enable", label = "@text/actions.program-enable.label", description = "@text/actions.program-enable.description") boolean programEnable) { + // + GrowattInverterHandler handler = this.handler; + if (handler != null) { + handler.setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, programEnable); + } else { + logger.warn("ThingHandler is null."); + } + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/ApiException.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/ApiException.java new file mode 100644 index 0000000000000..144abeabc95b5 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/ApiException.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.cloud; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ApiException} is thrown if a call to the Growatt cloud API server fails. + * + * @author Andrew Fiddian-Green - Initial contribution. + */ +@NonNullByDefault +public class ApiException extends Exception { + + private static final long serialVersionUID = 218139823621683189L; + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java new file mode 100644 index 0000000000000..85b7d654f69f2 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -0,0 +1,504 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.cloud; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.LocalTime; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.FormContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.util.Fields; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; + +/** + * The {@link GrowattCloud} class allows the binding to access the inverter state and settings via HTTP calls to the + * remote Growatt cloud API server (instead of receiving the data from the local Grott proxy server). + *

+ * This class is necessary since the Grott proxy server does not (yet) support easy access to some inverter register + * settings, such as the settings for the battery charging and discharging programs. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattCloud implements AutoCloseable { + + // JSON field names for the battery charging program + public static final String CHARGE_PROGRAM_POWER = "chargePowerCommand"; + public static final String CHARGE_PROGRAM_TARGET_SOC = "wchargeSOCLowLimit2"; + public static final String CHARGE_PROGRAM_ALLOW_AC_CHARGING = "acChargeEnable"; + public static final String CHARGE_PROGRAM_START_TIME = "forcedChargeTimeStart1"; + public static final String CHARGE_PROGRAM_STOP_TIME = "forcedChargeTimeStop1"; + public static final String CHARGE_PROGRAM_ENABLE = "forcedChargeStopSwitch1"; + + // JSON field names for the battery discharging program + public static final String DISCHARGE_PROGRAM_POWER = "disChargePowerCommand"; + public static final String DISCHARGE_PROGRAM_TARGET_SOC = "wdisChargeSOCLowLimit2"; + public static final String DISCHARGE_PROGRAM_START_TIME = "forcedDischargeTimeStart1"; + public static final String DISCHARGE_PROGRAM_STOP_TIME = "forcedDischargeTimeStop1"; + public static final String DISCHARGE_PROGRAM_ENABLE = "forcedDischargeStopSwitch1"; + + // API server URL + private static final String SERVER_URL = "https://server-api.growatt.com/"; + + // API end points + private static final String LOGIN_API = "newTwoLoginAPI.do"; + private static final String PLANT_LIST_API = "PlantListAPI.do"; + private static final String PLANT_INFO_API = "newTwoPlantAPI.do"; + private static final String NEW_TCP_SET_API = "newTcpsetAPI.do"; + private static final String NEW_MIX_API = "newMixApi.do"; + + // HTTP headers (user agent is spoofed to mimic the Growatt Android Shine app) + private static final String USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 12; https://www.openhab.org)"; + private static final String FORM_CONTENT = "application/x-www-form-urlencoded"; + + private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(5); + + private final Logger logger = LoggerFactory.getLogger(GrowattCloud.class); + private final HttpClient httpClient; + private final GrowattInverterConfiguration configuration; + + /** + * Constructor. + * + * @param configuration the thing configuration parameters. + * @param httpClientFactory the OH core {@link HttpClientFactory} instance. + * @throws Exception if anything goes wrong. + */ + public GrowattCloud(GrowattInverterConfiguration configuration, HttpClientFactory httpClientFactory) + throws Exception { + this.configuration = configuration; + this.httpClient = httpClientFactory.createHttpClient("growatt-cloud-api", new SslContextFactory.Client(true)); + this.httpClient.start(); + } + + @Override + public void close() throws Exception { + httpClient.stop(); + } + + /** + * Create a hash of the given password using normal MD5, except add 'c' if a byte of the digest is less than 10 + * + * @param password the plain text password + * @return the hash of the password + * @throws ApiException if MD5 algorithm is not supported + */ + private static String createHash(String password) throws ApiException { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new ApiException("Hash algorithm error", e); + } + byte[] bytes = md.digest(password.getBytes()); + StringBuilder result = new StringBuilder(); + for (byte b : bytes) { + result.append(String.format("%02x", b)); + } + for (int i = 0; i < result.length(); i += 2) { + if (result.charAt(i) == '0') { + result.replace(i, i + 1, "c"); + } + } + return result.toString(); + } + + /** + * Execute an HTTP request using the given HTTP method, to the given end point, and with the given request URL + * parameters and/or request form fields. + * + * @param method the HTTP method to use. + * @param endPoint the API end point. + * @param params the request URL parameters (may be null). + * @param fields the request form fields (may be null). + * @return a Map of JSON elements containing the server response. + * @throws ApiException if any error occurs. + */ + public Map doHttpRequest(HttpMethod method, String endPoint, + @Nullable Map params, @Nullable Fields fields) throws ApiException { + + Request request = httpClient.newRequest(SERVER_URL + endPoint).method(method) + .header(HttpHeader.USER_AGENT, USER_AGENT).timeout(HTTP_TIMEOUT.getSeconds(), TimeUnit.SECONDS); + + if (params != null) { + params.entrySet().forEach(p -> request.param(p.getKey(), p.getValue())); + } + + if (fields != null) { + request.content(new FormContentProvider(fields), FORM_CONTENT); + } + + ContentResponse response; + try { + response = request.send(); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + throw new ApiException("HTTP I/O Exception", e); + } + + int status = response.getStatus(); + if (status != HttpStatus.OK_200) { + throw new ApiException(String.format("HTTP %d %s", status, HttpStatus.getMessage(status))); + } + + String content = response.getContentAsString(); + if (content == null || content.isBlank()) { + throw new ApiException("HTTP response content is " + (content == null ? "null" : "blank")); + } + + logger.trace("doHttpRequest() response:{}", content); + try { + JsonElement jsonObject = JsonParser.parseString(content).getAsJsonObject(); + if (jsonObject instanceof JsonObject jsonElement) { + return jsonElement.asMap(); + } + throw new ApiException("JSON invalid response"); + } catch (JsonParseException | IllegalStateException e) { + throw new ApiException("JSON parse error", e); + } + } + + /** + * Get all of the mix inverter settings. + * + * @return a Map of JSON elements containing the server response. + * @throws ApiException if any error occurs. + */ + public Map getMixAllSettings() throws ApiException { + Map params = new LinkedHashMap<>(); // keep params in order + params.put("op", "getMixSetParams"); + params.put("serialNum", configuration.deviceId); + params.put("kind", "0"); + + Map result = doHttpRequest(HttpMethod.GET, NEW_MIX_API, params, null); + + JsonElement obj = result.get("obj"); + if (obj instanceof JsonObject object) { + JsonElement mixBean = object.get("mixBean"); + if (mixBean instanceof JsonObject mixBeanObject) { + return mixBeanObject.asMap(); + } + } + throw new ApiException("Invalid JSON response"); + } + + /** + * Get the plant information. + *

+ * This method is not currently used, but is included as a Java template for future implementations if needed. + * See https://github.com/indykoning/PyPi_GrowattServer/blob/master/growattServer/__init__.py + * + * @return a Map of JSON elements containing the server response. + * @throws ApiException if any error occurs. + */ + public Map getPlantInfo() throws ApiException { + if (configuration.plantId == null) { + throw new ApiException("Plant Id missing"); + } + + Map params = new LinkedHashMap<>(); // keep params in order + params.put("op", "getAllDeviceList"); + params.put("plantId", Objects.requireNonNull(configuration.plantId)); + params.put("pageNum", "1"); + params.put("pageSize", "1"); + + return doHttpRequest(HttpMethod.GET, PLANT_INFO_API, params, null); + } + + /** + * Get the plant list. + *

+ * This method is not currently used, but is included as a Java template for future implementations if needed. + * See https://github.com/indykoning/PyPi_GrowattServer/blob/master/growattServer/__init__.py + * + * @return a Map of JSON elements containing the server response. + * @throws ApiException if any error occurs. + */ + public Map getPlantList() throws ApiException { + if (configuration.userId == null) { + throw new ApiException("User Id missing"); + } + + Map params = new LinkedHashMap<>(); // keep params in order + params.put("userId", Objects.requireNonNull(configuration.userId)); + + Map result = doHttpRequest(HttpMethod.GET, PLANT_LIST_API, params, null); + + JsonElement back = result.get("back"); + if (back instanceof JsonObject backObject) { + JsonElement success = backObject.get("success"); + if (success instanceof JsonPrimitive successPrimitive) { + if (successPrimitive.getAsBoolean()) { + return backObject.asMap(); + } + } + } + throw new ApiException("Invalid JSON response"); + } + + /** + * Attempt to login to the remote server by posting the given user credentials. + * + * @return a Map of JSON elements containing the server response. + * @throws ApiException if any error occurs. + */ + public Map postLoginCredentials() throws ApiException { + if (configuration.userName == null) { + throw new ApiException("User name missing"); + } + if (configuration.password == null) { + throw new ApiException("Password missing"); + } + + Fields fields = new Fields(); + fields.put("userName", Objects.requireNonNull(configuration.userName)); + fields.put("password", createHash(Objects.requireNonNull(configuration.password))); + + Map result = doHttpRequest(HttpMethod.POST, LOGIN_API, null, fields); + + JsonElement back = result.get("back"); + if (back instanceof JsonObject backObject) { + JsonElement success = backObject.get("success"); + if (success instanceof JsonPrimitive successPrimitive) { + if (successPrimitive.getAsBoolean()) { + return backObject.asMap(); + } + } + } + throw new ApiException("Login failed"); + } + + /** + * Post a command to setup the mix inverter battery charging program. + * + * @param chargingPower the rate of charging 0%..100% + * @param targetSOC the SOC at which to stop charging 0%..100% + * @param allowAcCharging allow the battery to be charged from AC power + * @param startTime the start time of the charging program + * @param stopTime the stop time of the charging program + * @param programEnable charge program shall be enabled + * @return a Map of JSON elements containing the server response + * @throws ApiException if any error occurs + */ + public Map setupChargingProgram(int chargingPower, int targetSOC, boolean allowAcCharging, + LocalTime startTime, LocalTime stopTime, boolean programEnable) throws ApiException { + if (chargingPower < 1 || chargingPower > 100) { + throw new ApiException("Charge power out of range (1%..100%)"); + } + if (targetSOC < 1 || targetSOC > 100) { + throw new ApiException("Target SOC out of range (1%..100%)"); + } + + Fields fields = new Fields(); + fields.put("op", "mixSetApiNew"); + fields.put("serialNum", configuration.deviceId); + fields.put("type", "mix_ac_charge_time_period"); + fields.put("param1", String.format("%d", chargingPower)); + fields.put("param2", String.format("%d", targetSOC)); + fields.put("param3", allowAcCharging ? "1" : "0"); + fields.put("param4", String.format("%02d", startTime.getHour())); + fields.put("param5", String.format("%02d", startTime.getMinute())); + fields.put("param6", String.format("%02d", stopTime.getHour())); + fields.put("param7", String.format("%02d", stopTime.getMinute())); + fields.put("param8", programEnable ? "1" : "0"); + fields.put("param9", "00"); + fields.put("param10", "00"); + fields.put("param11", "00"); + fields.put("param12", "00"); + fields.put("param13", "0"); + fields.put("param14", "00"); + fields.put("param15", "00"); + fields.put("param16", "00"); + fields.put("param17", "00"); + fields.put("param18", "0"); + + Map result = doHttpRequest(HttpMethod.POST, NEW_TCP_SET_API, null, fields); + + JsonElement success = result.get("success"); + if (success instanceof JsonPrimitive sucessPrimitive) { + if (sucessPrimitive.getAsBoolean()) { + return result; + } + } + throw new ApiException("Command failed"); + } + + /** + * Post a command to setup the mix inverter battery discharge program. + * + * @param dischargingPower the rate of discharging 1%..100% + * @param targetSOC the SOC at which to stop charging 1%..100% + * @param startTime the start time of the discharging program + * @param stopTime the stop time of the discharging program + * @param programEnable discharge program shall be enabled + * @return a Map of JSON elements containing the server response + * @throws ApiException if any error occurs + */ + public Map setupDischargingProgram(int dischargingPower, int targetSOC, LocalTime startTime, + LocalTime stopTime, boolean programEnable) throws ApiException { + if (dischargingPower < 1 || dischargingPower > 100) { + throw new ApiException("Discharge power out of range (1%..100%)"); + } + if (targetSOC < 1 || targetSOC > 100) { + throw new ApiException("Target SOC out of range (1%..100%)"); + } + + Fields fields = new Fields(); + fields.put("op", "mixSetApiNew"); + fields.put("serialNum", configuration.deviceId); + fields.put("type", "mix_ac_discharge_time_period"); + fields.put("param1", String.format("%d", dischargingPower)); + fields.put("param2", String.format("%d", targetSOC)); + fields.put("param3", String.format("%02d", startTime.getHour())); + fields.put("param4", String.format("%02d", startTime.getMinute())); + fields.put("param5", String.format("%02d", stopTime.getHour())); + fields.put("param6", String.format("%02d", stopTime.getMinute())); + fields.put("param7", programEnable ? "1" : "0"); + fields.put("param8", "00"); + fields.put("param9", "00"); + fields.put("param10", "00"); + fields.put("param11", "00"); + fields.put("param12", "0"); + fields.put("param13", "00"); + fields.put("param14", "00"); + fields.put("param15", "00"); + fields.put("param16", "00"); + fields.put("param17", "0"); + + Map result = doHttpRequest(HttpMethod.POST, NEW_TCP_SET_API, null, fields); + + JsonElement success = result.get("success"); + if (success instanceof JsonPrimitive sucessPrimitive) { + if (sucessPrimitive.getAsBoolean()) { + return result; + } + } + throw new ApiException("Command failed"); + } + + /** + * Look for an entry in the given Map, and return its value as a boolean. + * + * @param map the source map. + * @param key the key to search for in the map. + * @return the boolean value. + * @throws ApiException if any error occurs. + */ + public static boolean mapGetBoolean(Map map, String key) throws ApiException { + JsonElement element = map.get(key); + if (element instanceof JsonPrimitive primitive) { + if (primitive.isBoolean()) { + return primitive.getAsBoolean(); + } else if (primitive.isNumber() || primitive.isString()) { + try { + switch (primitive.getAsInt()) { + case 0: + return false; + case 1: + return true; + } + } catch (NumberFormatException e) { + throw new ApiException("Boolean bad value", e); + } + } + } + throw new ApiException("Boolean missing or bad value"); + } + + /** + * Look for an entry in the given Map, and return its value as an integer. + * + * @param map the source map. + * @param key the key to search for in the map. + * @return the integer value. + * @throws ApiException if any error occurs. + */ + public static int mapGetInteger(Map map, String key) throws ApiException { + JsonElement element = map.get(key); + if (element instanceof JsonPrimitive primitive) { + try { + return primitive.getAsInt(); + } catch (NumberFormatException e) { + throw new ApiException("Integer bad value", e); + } + } + throw new ApiException("Integer missing or bad value"); + } + + /** + * Look for an entry in the given Map, and return its value as a LocalTime. + * + * @param source the source map. + * @param key the key to search for in the map. + * @return the LocalTime. + * @throws ApiException if any error occurs. + */ + public static LocalTime mapGetLocalTime(Map source, String key) throws ApiException { + JsonElement element = source.get(key); + if ((element instanceof JsonPrimitive primitive) && primitive.isString()) { + try { + return localTimeOf(primitive.getAsString()); + } catch (DateTimeException e) { + throw new ApiException("LocalTime bad value", e); + } + } + throw new ApiException("LocalTime missing or bad value"); + } + + /** + * Parse a time formatted string into a LocalTime entity. + *

+ * Note: unlike the standard LocalTime.parse() method, this method accepts hour and minute fields from the Growatt + * server that are without leading zeros e.g. "1:1" and it accepts the conventional "01:01" format too. + * + * @param localTime a time formatted string e.g. "12:34" + * @return a corresponding LocalTime entity. + * @throws DateTimeException if any error occurs. + */ + public static LocalTime localTimeOf(String localTime) throws DateTimeException { + String splitParts[] = localTime.split(":"); + if (splitParts.length < 2) { + throw new DateTimeException("LocalTime bad value"); + } + try { + return LocalTime.of(Integer.valueOf(splitParts[0]), Integer.valueOf(splitParts[1])); + } catch (NumberFormatException | DateTimeException e) { + throw new DateTimeException("LocalTime bad value", e); + } + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java index b5621fe0905f1..c1362cd976720 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java @@ -13,6 +13,7 @@ package org.openhab.binding.growatt.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * The {@link GrowattInverterConfiguration} class contains fields mapping thing configuration parameters. @@ -23,6 +24,19 @@ public class GrowattInverterConfiguration { public static final String DEVICE_ID = "deviceId"; + public static final String USER_NAME = "userName"; + public static final String PASSWORD = "password"; + public static final String USER_ID = "userId"; + public static final String PLANT_ID = "plantId"; + // required public String deviceId = ""; + + // optional + public @Nullable String userName; + public @Nullable String password; + + // optional for tests only + public @Nullable String userId; + public @Nullable String plantId; } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index 9f1c08789a807..53be309ce70d0 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -31,6 +31,7 @@ import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -63,6 +64,7 @@ public class GrowattHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(GrowattHandlerFactory.class); private final HttpService httpService; + private final HttpClientFactory httpClientFactory; private final TranslationProvider i18nProvider; private final LocaleProvider localeProvider; private final Set bridges = Collections.synchronizedSet(new HashSet<>()); @@ -72,9 +74,10 @@ public class GrowattHandlerFactory extends BaseThingHandlerFactory { private @Nullable ServiceRegistration discoveryServiceRegistration; @Activate - public GrowattHandlerFactory(@Reference HttpService httpService, @Reference TranslationProvider i18nProvider, - @Reference LocaleProvider localeProvider) { + public GrowattHandlerFactory(@Reference HttpService httpService, @Reference HttpClientFactory httpClientFactory, + @Reference TranslationProvider i18nProvider, @Reference LocaleProvider localeProvider) { this.httpService = httpService; + this.httpClientFactory = httpClientFactory; this.i18nProvider = i18nProvider; this.localeProvider = localeProvider; try { @@ -96,7 +99,7 @@ public GrowattHandlerFactory(@Reference HttpService httpService, @Reference Tran } if (THING_TYPE_INVERTER.equals(thingTypeUID)) { - return new GrowattInverterHandler(thing); + return new GrowattInverterHandler(thing, httpClientFactory); } return null; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 0f5a824919d43..428721e4c9d11 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.growatt.internal.handler; +import java.time.format.DateTimeParseException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -21,9 +22,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.growatt.internal.cloud.ApiException; +import org.openhab.binding.growatt.internal.cloud.GrowattCloud; import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; @@ -47,17 +51,21 @@ public class GrowattInverterHandler extends BaseThingHandler { private static final int AWAITING_DATA_TIMEOUT_MINUTES = 11; private final Logger logger = LoggerFactory.getLogger(GrowattInverterHandler.class); + private final HttpClientFactory httpClientFactory; private String deviceId = "unknown"; private @Nullable ScheduledFuture awaitingDataTimeoutTask; + private @Nullable GrowattCloud growattCloud; - public GrowattInverterHandler(Thing thing) { + public GrowattInverterHandler(Thing thing, HttpClientFactory httpClientFactory) { super(thing); + this.httpClientFactory = httpClientFactory; } @Override public void dispose() { + growattCloud = null; ScheduledFuture task = awaitingDataTimeoutTask; if (task != null) { task.cancel(true); @@ -150,4 +158,58 @@ public void updateInverterValues(GrottValues inverterValues) { } }); } + + private GrowattCloud getGrowattCloud() throws IllegalStateException { + GrowattCloud growattCloud = this.growattCloud; + if (growattCloud == null) { + try { + growattCloud = new GrowattCloud(getConfigAs(GrowattInverterConfiguration.class), httpClientFactory); + } catch (Exception e) { + throw new IllegalStateException("GrowattCloud not created", e); + } + this.growattCloud = growattCloud; + } + return growattCloud; + } + + /** + * This method is called from a Rule Action to setup the battery charging program. + * + * @param chargingPower the rate of charging 0%..100% + * @param targetSOC the SOC at which to stop charging 0%..100% + * @param allowAcCharging allow the battery to be charged from AC power + * @param startTime the start time of the charging program; a time formatted string e.g. "12:34" + * @param stopTime the stop time of the charging program; a time formatted string e.g. "12:34" + * @param programEnable charge program shall be enabled + */ + public void setupChargingProgram(int chargingPower, int targetSOC, boolean allowAcCharging, String startTime, + String stopTime, boolean programEnable) { + try { + getGrowattCloud().setupChargingProgram(chargingPower, targetSOC, allowAcCharging, + GrowattCloud.localTimeOf(startTime), GrowattCloud.localTimeOf(stopTime), programEnable); + } catch (IllegalStateException | DateTimeParseException | ApiException e) { + logger.warn("setupChargingProgram() error", e); + this.growattCloud = null; + } + } + + /** + * This method is called from a Rule Action to setup the battery discharging program. + * + * @param dischargingPower the rate of discharging 1%..100% + * @param targetSOC the SOC at which to stop charging 1%..100% + * @param startTime the start time of the discharging program; a time formatted string e.g. "12:34" + * @param stopTime the stop time of the discharging program; a time formatted string e.g. "12:34" + * @param programEnable the discharge program shall be enabled + */ + public void setupDischargingProgram(int dischargingPower, int targetSOC, String startTime, String stopTime, + boolean programEnable) { + try { + getGrowattCloud().setupDischargingProgram(dischargingPower, targetSOC, GrowattCloud.localTimeOf(startTime), + GrowattCloud.localTimeOf(stopTime), programEnable); + } catch (IllegalStateException | DateTimeParseException | ApiException e) { + logger.warn("setupDischargingProgram() error", e); + this.growattCloud = null; + } + } } diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index eb8cbc434cc31..0ba38446d462c 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -214,3 +214,21 @@ discovery.growatt-inverter = Growatt Inverter status.awaiting-data = Waiting for data from Grott application status.awaiting-data-timeout = Timed out waiting for data from Grott application + +# actions +actions.allow-ac-charging.label = Allow AC Charging +actions.allow-ac-charging.description = Allow the battery to be charged from AC supply +actions.charging.label = Setup Charge Program +actions.charging.description = Setup the battery charging program +actions.charging-power.label = Charge / Discharge Power +actions.charging-power.description = The rate of battery charging / discharging (1%..100%) +actions.discharging.label = Setup Discharge Program +actions.discharging.description = Setup the battery discharging program +actions.program-enable.label = Program Enable +actions.program-enable.description = Enable / disable the battery charging / discharging program +actions.start-time.label = Start Time +actions.start-time.description = The time when the program shall start +actions.stop-time.label = Stop Time +actions.stop-time.description = The time when the program shall stop +actions.target-soc.label = Target SOC +actions.target-soc.description = The battery SOC target for when the program is completed diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 38845a46b63d2..4ad771adebfcc 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -432,6 +432,23 @@ Id of the inverter. + + + User name to login to the Shine App. + + + password + + Password to login to the Shine App. + + + + User Id as displayed in the Shine App. + + + + Plant Id as displayed in the Shine App. + diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index cc4060597e431..1707c5895a7c0 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -13,11 +13,14 @@ package org.openhab.binding.growatt.test; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Field; +import java.time.LocalTime; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -25,17 +28,24 @@ import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.Test; import org.openhab.binding.growatt.internal.GrowattChannels; import org.openhab.binding.growatt.internal.GrowattChannels.UoM; +import org.openhab.binding.growatt.internal.cloud.ApiException; +import org.openhab.binding.growatt.internal.cloud.GrowattCloud; +import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.Units; import org.openhab.core.types.State; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -245,4 +255,104 @@ private void testJsonFieldsMappedToDto(String fileName) { } }); } + + /** + * Test the Growatt remote cloud API server. + * Will not run unless actual user credentials are provided. + */ + @Test + void testServer() { + GrowattInverterConfiguration configuration = new GrowattInverterConfiguration(); + /* + * To test on an actual inverter, populate its plant data and user credentials below. + * + * configuration.deviceId = "aa"; + * configuration.userName = "bb"; + * configuration.password ="cc"; + * configuration.userId = "dd"; + * configuration.plantId = "ee"; + */ + + if (configuration.userName == null) { + return; + } + + HttpClientFactory httpClientFactory = mock(HttpClientFactory.class); + when(httpClientFactory.createHttpClient(anyString(), any(SslContextFactory.Client.class))) + .thenReturn(new HttpClient(new SslContextFactory.Client(true))); + + try (GrowattCloud api = new GrowattCloud(configuration, httpClientFactory)) { + try { + assertFalse(api.postLoginCredentials().isEmpty()); + } catch (ApiException e) { + fail(e); + } + try { + assertFalse(api.getPlantList().isEmpty()); + } catch (ApiException e) { + fail(e); + } + try { + assertFalse(api.getPlantInfo().isEmpty()); + } catch (ApiException e) { + fail(e); + } + + int chargePower = 97; + int targetSOC = 23; + boolean allowAcCharge = false; + LocalTime startTime = LocalTime.of(1, 16); + LocalTime stopTime = LocalTime.of(2, 17); + boolean programEnable = false; + try { + assertFalse(api + .setupChargingProgram(chargePower, targetSOC, allowAcCharge, startTime, stopTime, programEnable) + .isEmpty()); + } catch (ApiException e) { + fail(e); + } + try { + Map result = api.getMixAllSettings(); + assertFalse(result.isEmpty()); + assertEquals(chargePower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); + assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); + assertEquals(allowAcCharge, + GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); + assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); + assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); + assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); + } catch (ApiException e) { + fail(e); + } + + chargePower = 100; + targetSOC = 20; + allowAcCharge = true; + startTime = LocalTime.of(0, 15); + stopTime = LocalTime.of(6, 45); + programEnable = true; + try { + assertFalse(api + .setupChargingProgram(chargePower, targetSOC, allowAcCharge, startTime, stopTime, programEnable) + .isEmpty()); + } catch (ApiException e) { + fail(e); + } + try { + Map result = api.getMixAllSettings(); + assertFalse(result.isEmpty()); + assertEquals(chargePower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); + assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); + assertEquals(allowAcCharge, + GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); + assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); + assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); + assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); + } catch (ApiException e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + } } From 03cd19bc01e438727a25186d5dba7d071a0b5147 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 27 Nov 2023 10:22:51 +0000 Subject: [PATCH 123/146] [growatt] auto login depending on cookies Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/cloud/GrowattCloud.java | 43 +++++++++++++++---- .../binding/growatt/test/GrowattTest.java | 5 --- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index 85b7d654f69f2..8a45a7c42eff4 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -12,12 +12,14 @@ */ package org.openhab.binding.growatt.internal.cloud; +import java.net.HttpCookie; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.DateTimeException; import java.time.Duration; import java.time.LocalTime; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; @@ -30,7 +32,6 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.FormContentProvider; -import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.Fields; @@ -140,8 +141,10 @@ private static String createHash(String password) throws ApiException { } /** - * Execute an HTTP request using the given HTTP method, to the given end point, and with the given request URL - * parameters and/or request form fields. + * Login to the server (if necessary) and then execute an HTTP request using the given HTTP method, to the given end + * point, and with the given request URL parameters and/or request form fields. If there are no existing cookies for + * this server, or if any of the cookies has expired, then first login to the server before making the actual HTTP + * request. * * @param method the HTTP method to use. * @param endPoint the API end point. @@ -150,11 +153,33 @@ private static String createHash(String password) throws ApiException { * @return a Map of JSON elements containing the server response. * @throws ApiException if any error occurs. */ - public Map doHttpRequest(HttpMethod method, String endPoint, + private Map doHttpRequest(HttpMethod method, String endPoint, @Nullable Map params, @Nullable Fields fields) throws ApiException { + // + List cookies = httpClient.getCookieStore().getCookies(); + if (cookies.isEmpty() || cookies.stream().anyMatch(HttpCookie::hasExpired)) { + postLoginCredentials(); + } + + return doHttpRequestInner(method, endPoint, params, fields); + } - Request request = httpClient.newRequest(SERVER_URL + endPoint).method(method) - .header(HttpHeader.USER_AGENT, USER_AGENT).timeout(HTTP_TIMEOUT.getSeconds(), TimeUnit.SECONDS); + /** + * Inner method to execute an HTTP request using the given HTTP method, to the given end point, and with the given + * request URL parameters and/or request form fields. + * + * @param method the HTTP method to use. + * @param endPoint the API end point. + * @param params the request URL parameters (may be null). + * @param fields the request form fields (may be null). + * @return a Map of JSON elements containing the server response. + * @throws ApiException if any error occurs. + */ + private Map doHttpRequestInner(HttpMethod method, String endPoint, + @Nullable Map params, @Nullable Fields fields) throws ApiException { + // + Request request = httpClient.newRequest(SERVER_URL + endPoint).method(method).agent(USER_AGENT) + .timeout(HTTP_TIMEOUT.getSeconds(), TimeUnit.SECONDS); if (params != null) { params.entrySet().forEach(p -> request.param(p.getKey(), p.getValue())); @@ -181,7 +206,7 @@ public Map doHttpRequest(HttpMethod method, String endPoint throw new ApiException("HTTP response content is " + (content == null ? "null" : "blank")); } - logger.trace("doHttpRequest() response:{}", content); + logger.trace("doHttpRequestInner() response:{}", content); try { JsonElement jsonObject = JsonParser.parseString(content).getAsJsonObject(); if (jsonObject instanceof JsonObject jsonElement) { @@ -277,7 +302,7 @@ public Map getPlantList() throws ApiException { * @return a Map of JSON elements containing the server response. * @throws ApiException if any error occurs. */ - public Map postLoginCredentials() throws ApiException { + private Map postLoginCredentials() throws ApiException { if (configuration.userName == null) { throw new ApiException("User name missing"); } @@ -289,7 +314,7 @@ public Map postLoginCredentials() throws ApiException { fields.put("userName", Objects.requireNonNull(configuration.userName)); fields.put("password", createHash(Objects.requireNonNull(configuration.password))); - Map result = doHttpRequest(HttpMethod.POST, LOGIN_API, null, fields); + Map result = doHttpRequestInner(HttpMethod.POST, LOGIN_API, null, fields); JsonElement back = result.get("back"); if (back instanceof JsonObject backObject) { diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 1707c5895a7c0..cde14597e48b3 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -282,11 +282,6 @@ void testServer() { .thenReturn(new HttpClient(new SslContextFactory.Client(true))); try (GrowattCloud api = new GrowattCloud(configuration, httpClientFactory)) { - try { - assertFalse(api.postLoginCredentials().isEmpty()); - } catch (ApiException e) { - fail(e); - } try { assertFalse(api.getPlantList().isEmpty()); } catch (ApiException e) { From 6d683dfc428357f888af2e0636173f47624dd9a3 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 27 Nov 2023 15:58:38 +0000 Subject: [PATCH 124/146] [growatt] refactoring Signed-off-by: Andrew Fiddian-Green --- .../binding/growatt/test/GrowattTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index cde14597e48b3..7568bff0d76c3 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -293,15 +293,15 @@ void testServer() { fail(e); } - int chargePower = 97; + int chargingPower = 97; int targetSOC = 23; - boolean allowAcCharge = false; + boolean allowAcCharging = false; LocalTime startTime = LocalTime.of(1, 16); LocalTime stopTime = LocalTime.of(2, 17); boolean programEnable = false; try { assertFalse(api - .setupChargingProgram(chargePower, targetSOC, allowAcCharge, startTime, stopTime, programEnable) + .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) .isEmpty()); } catch (ApiException e) { fail(e); @@ -309,9 +309,9 @@ void testServer() { try { Map result = api.getMixAllSettings(); assertFalse(result.isEmpty()); - assertEquals(chargePower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); + assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); - assertEquals(allowAcCharge, + assertEquals(allowAcCharging, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); @@ -320,15 +320,15 @@ void testServer() { fail(e); } - chargePower = 100; + chargingPower = 100; targetSOC = 20; - allowAcCharge = true; + allowAcCharging = true; startTime = LocalTime.of(0, 15); stopTime = LocalTime.of(6, 45); programEnable = true; try { assertFalse(api - .setupChargingProgram(chargePower, targetSOC, allowAcCharge, startTime, stopTime, programEnable) + .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) .isEmpty()); } catch (ApiException e) { fail(e); @@ -336,9 +336,9 @@ void testServer() { try { Map result = api.getMixAllSettings(); assertFalse(result.isEmpty()); - assertEquals(chargePower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); + assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); - assertEquals(allowAcCharge, + assertEquals(allowAcCharging, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); From cdae2c4c65bd451b3281ea3a235d17957e45983c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 28 Nov 2023 10:31:45 +0000 Subject: [PATCH 125/146] [growatt] improve example Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 902e758142034..f02668772294c 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -164,8 +164,41 @@ growattActions.setupDischargingProgram(int dischargingPower, int targetSOC, Stri Example: ```php -// charge battery from AC supply, at 20% charge rate, from 00:15 .. 06:45, up to 75% fill level -growattActions.setupChargingProgram(20, 75, true, "00:15", "06:45", true) +rule "Setup Solar Battery Charging Program" +when + Time cron "0 10 0 ? * * *" +then + val growattActions = getActions("growatt", "growatt:inverter:home:ABCD1234") + if (growattActions === null) { + logWarn("Rules", "growattActions is null") + } else { + + // fixed algorithm parameters + val chargingPower = 25 + val allowAcCharging = true + val startTime = "00:20" + val stopTime = "07:00" + val programEnable = true + val batteryFull = 6500 + val batteryMin = 500 + val daylightConsumption = 10000 + val maximumSOC = 100 + val minimumSOC = 20 + + // variable algorithm parameters + val solarForecast = (ForecastSolar_PV_Whole_Site_Forecast_Today.state as QuantityType).toUnit("Wh").toBigDecimal() + var targetSOC = (100 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull + if (targetSOC > maximumSOC) { + targetSOC = maximumSOC + } else if (targetSOC < minimumSOC) { + targetSOC = minimumSOC + } + + logInfo("Rules", "Setup Charging Program:{solarForecast:" + solarForecast + ", chargingPower:" + chargingPower + ", targetSOC:" + targetSOC + ", allowAcCharging:" + + allowAcCharging + ", startTime:" + startTime + ", stopTime:" + stopTime + ", programEnable:" + programEnable +"}") + growattActions.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) + } +end ``` ## Full Example From 88cad3f9a57bb26e4332abbaf1267d0df41aaca5 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 28 Nov 2023 10:42:08 +0000 Subject: [PATCH 126/146] [growatt] spotless Signed-off-by: Andrew Fiddian-Green --- .../org/openhab/binding/growatt/test/GrowattTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 7568bff0d76c3..b9cf0e35f41c3 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -300,9 +300,8 @@ void testServer() { LocalTime stopTime = LocalTime.of(2, 17); boolean programEnable = false; try { - assertFalse(api - .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) - .isEmpty()); + assertFalse(api.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, + programEnable).isEmpty()); } catch (ApiException e) { fail(e); } @@ -327,9 +326,8 @@ void testServer() { stopTime = LocalTime.of(6, 45); programEnable = true; try { - assertFalse(api - .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) - .isEmpty()); + assertFalse(api.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, + programEnable).isEmpty()); } catch (ApiException e) { fail(e); } From 8dad796ee588e7d708fd67e51f3aa76afc6a9908 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 29 Nov 2023 16:53:58 +0000 Subject: [PATCH 127/146] [growatt] actions allow Number arguments Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/action/GrowattActions.java | 12 ++++++------ .../internal/handler/GrowattInverterHandler.java | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java index 62d0252dd30d4..a7a71b9e99297 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java @@ -35,13 +35,13 @@ public class GrowattActions implements ThingActions { private final Logger logger = LoggerFactory.getLogger(GrowattActions.class); private @Nullable GrowattInverterHandler handler; - public static void setupChargingProgram(ThingActions actions, int chargingPower, int targetSOC, + public static void setupChargingProgram(ThingActions actions, Number chargingPower, Number targetSOC, boolean allowAcCharging, String startTime, String stopTime, boolean programEnable) { ((GrowattActions) actions).setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable); } - public static void setupDischargingProgram(ThingActions actions, int dischargingPower, int targetSOC, + public static void setupDischargingProgram(ThingActions actions, Number dischargingPower, Number targetSOC, String startTime, String stopTime, boolean programEnable) { ((GrowattActions) actions).setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, programEnable); @@ -59,8 +59,8 @@ public void setThingHandler(@Nullable ThingHandler handler) { @RuleAction(label = "@text/actions.charging.label", description = "@text/actions.charging.description") public void setupChargingProgram( - @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") int chargingPower, - @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") int targetSOC, + @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") Number chargingPower, + @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") Number targetSOC, @ActionInput(name = "allow-ac-charging", label = "@text/actions.allow-ac-charging.label", description = "@text/actions.allow-ac-charging.description") boolean allowAcCharging, @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, @@ -76,8 +76,8 @@ public void setupChargingProgram( @RuleAction(label = "@text/actions.discharging.label", description = "@text/actions.discharging.description") public void setupDischargingProgram( - @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") int dischargingPower, - @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") int targetSOC, + @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") Number dischargingPower, + @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") Number targetSOC, @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, @ActionInput(name = "program-enable", label = "@text/actions.program-enable.label", description = "@text/actions.program-enable.description") boolean programEnable) { diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index c67b9843e6707..6b43c5abbf917 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -189,10 +189,10 @@ private GrowattCloud getGrowattCloud() throws IllegalStateException { * @param stopTime the stop time of the charging program; a time formatted string e.g. "12:34" * @param programEnable charge program shall be enabled */ - public void setupChargingProgram(int chargingPower, int targetSOC, boolean allowAcCharging, String startTime, + public void setupChargingProgram(Number chargingPower, Number targetSOC, boolean allowAcCharging, String startTime, String stopTime, boolean programEnable) { try { - getGrowattCloud().setupChargingProgram(chargingPower, targetSOC, allowAcCharging, + getGrowattCloud().setupChargingProgram(chargingPower.intValue(), targetSOC.intValue(), allowAcCharging, GrowattCloud.localTimeOf(startTime), GrowattCloud.localTimeOf(stopTime), programEnable); } catch (IllegalStateException | DateTimeParseException | ApiException e) { logger.warn("setupChargingProgram() error", e); @@ -209,11 +209,11 @@ public void setupChargingProgram(int chargingPower, int targetSOC, boolean allow * @param stopTime the stop time of the discharging program; a time formatted string e.g. "12:34" * @param programEnable the discharge program shall be enabled */ - public void setupDischargingProgram(int dischargingPower, int targetSOC, String startTime, String stopTime, + public void setupDischargingProgram(Number dischargingPower, Number targetSOC, String startTime, String stopTime, boolean programEnable) { try { - getGrowattCloud().setupDischargingProgram(dischargingPower, targetSOC, GrowattCloud.localTimeOf(startTime), - GrowattCloud.localTimeOf(stopTime), programEnable); + getGrowattCloud().setupDischargingProgram(dischargingPower.intValue(), targetSOC.intValue(), + GrowattCloud.localTimeOf(startTime), GrowattCloud.localTimeOf(stopTime), programEnable); } catch (IllegalStateException | DateTimeParseException | ApiException e) { logger.warn("setupDischargingProgram() error", e); this.growattCloud = null; From 2c40075c80e0e77ef9b79bf9b395d54884fed3c7 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 2 Dec 2023 12:01:19 +0000 Subject: [PATCH 128/146] [growatt] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- .../internal/action/GrowattActions.java | 17 +- ...xception.java => GrowattApiException.java} | 8 +- .../growatt/internal/cloud/GrowattCloud.java | 98 ++++++------ .../handler/GrowattInverterHandler.java | 6 +- .../binding/growatt/test/GrowattTest.java | 151 ++++++++---------- 5 files changed, 138 insertions(+), 142 deletions(-) rename bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/{ApiException.java => GrowattApiException.java} (73%) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java index a7a71b9e99297..674e79130faab 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java @@ -37,14 +37,21 @@ public class GrowattActions implements ThingActions { public static void setupChargingProgram(ThingActions actions, Number chargingPower, Number targetSOC, boolean allowAcCharging, String startTime, String stopTime, boolean programEnable) { - ((GrowattActions) actions).setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, - programEnable); + if (actions instanceof GrowattActions growattActions) { + growattActions.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, + programEnable); + } else { + throw new IllegalArgumentException("The 'actions' argument is not an instance of GrowattActions"); + } } public static void setupDischargingProgram(ThingActions actions, Number dischargingPower, Number targetSOC, String startTime, String stopTime, boolean programEnable) { - ((GrowattActions) actions).setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, - programEnable); + if (actions instanceof GrowattActions growattActions) { + growattActions.setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, programEnable); + } else { + throw new IllegalArgumentException("The 'actions' argument is not an instance of GrowattActions"); + } } @Override @@ -65,7 +72,6 @@ public void setupChargingProgram( @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, @ActionInput(name = "program-enable", label = "@text/actions.program-enable.label", description = "@text/actions.program-enable.description") boolean programEnable) { - // GrowattInverterHandler handler = this.handler; if (handler != null) { handler.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable); @@ -81,7 +87,6 @@ public void setupDischargingProgram( @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, @ActionInput(name = "program-enable", label = "@text/actions.program-enable.label", description = "@text/actions.program-enable.description") boolean programEnable) { - // GrowattInverterHandler handler = this.handler; if (handler != null) { handler.setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, programEnable); diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/ApiException.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattApiException.java similarity index 73% rename from bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/ApiException.java rename to bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattApiException.java index 144abeabc95b5..10eab7fb3d532 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/ApiException.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattApiException.java @@ -15,20 +15,20 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link ApiException} is thrown if a call to the Growatt cloud API server fails. + * The {@link GrowattApiException} is thrown if a call to the Growatt cloud API server fails. * * @author Andrew Fiddian-Green - Initial contribution. */ @NonNullByDefault -public class ApiException extends Exception { +public class GrowattApiException extends Exception { private static final long serialVersionUID = 218139823621683189L; - public ApiException(String message) { + public GrowattApiException(String message) { super(message); } - public ApiException(String message, Throwable cause) { + public GrowattApiException(String message, Throwable cause) { super(message, cause); } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index 8a45a7c42eff4..286c3c8f5d092 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -118,14 +118,14 @@ public void close() throws Exception { * * @param password the plain text password * @return the hash of the password - * @throws ApiException if MD5 algorithm is not supported + * @throws GrowattApiException if MD5 algorithm is not supported */ - private static String createHash(String password) throws ApiException { + private static String createHash(String password) throws GrowattApiException { MessageDigest md; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { - throw new ApiException("Hash algorithm error", e); + throw new GrowattApiException("Hash algorithm error", e); } byte[] bytes = md.digest(password.getBytes()); StringBuilder result = new StringBuilder(); @@ -151,10 +151,10 @@ private static String createHash(String password) throws ApiException { * @param params the request URL parameters (may be null). * @param fields the request form fields (may be null). * @return a Map of JSON elements containing the server response. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ private Map doHttpRequest(HttpMethod method, String endPoint, - @Nullable Map params, @Nullable Fields fields) throws ApiException { + @Nullable Map params, @Nullable Fields fields) throws GrowattApiException { // List cookies = httpClient.getCookieStore().getCookies(); if (cookies.isEmpty() || cookies.stream().anyMatch(HttpCookie::hasExpired)) { @@ -173,10 +173,10 @@ private Map doHttpRequest(HttpMethod method, String endPoin * @param params the request URL parameters (may be null). * @param fields the request form fields (may be null). * @return a Map of JSON elements containing the server response. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ private Map doHttpRequestInner(HttpMethod method, String endPoint, - @Nullable Map params, @Nullable Fields fields) throws ApiException { + @Nullable Map params, @Nullable Fields fields) throws GrowattApiException { // Request request = httpClient.newRequest(SERVER_URL + endPoint).method(method).agent(USER_AGENT) .timeout(HTTP_TIMEOUT.getSeconds(), TimeUnit.SECONDS); @@ -193,17 +193,17 @@ private Map doHttpRequestInner(HttpMethod method, String en try { response = request.send(); } catch (InterruptedException | TimeoutException | ExecutionException e) { - throw new ApiException("HTTP I/O Exception", e); + throw new GrowattApiException("HTTP I/O Exception", e); } int status = response.getStatus(); if (status != HttpStatus.OK_200) { - throw new ApiException(String.format("HTTP %d %s", status, HttpStatus.getMessage(status))); + throw new GrowattApiException(String.format("HTTP %d %s", status, HttpStatus.getMessage(status))); } String content = response.getContentAsString(); if (content == null || content.isBlank()) { - throw new ApiException("HTTP response content is " + (content == null ? "null" : "blank")); + throw new GrowattApiException("HTTP response content is " + (content == null ? "null" : "blank")); } logger.trace("doHttpRequestInner() response:{}", content); @@ -212,9 +212,9 @@ private Map doHttpRequestInner(HttpMethod method, String en if (jsonObject instanceof JsonObject jsonElement) { return jsonElement.asMap(); } - throw new ApiException("JSON invalid response"); + throw new GrowattApiException("JSON invalid response"); } catch (JsonParseException | IllegalStateException e) { - throw new ApiException("JSON parse error", e); + throw new GrowattApiException("JSON parse error", e); } } @@ -222,9 +222,9 @@ private Map doHttpRequestInner(HttpMethod method, String en * Get all of the mix inverter settings. * * @return a Map of JSON elements containing the server response. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ - public Map getMixAllSettings() throws ApiException { + public Map getMixAllSettings() throws GrowattApiException { Map params = new LinkedHashMap<>(); // keep params in order params.put("op", "getMixSetParams"); params.put("serialNum", configuration.deviceId); @@ -239,7 +239,7 @@ public Map getMixAllSettings() throws ApiException { return mixBeanObject.asMap(); } } - throw new ApiException("Invalid JSON response"); + throw new GrowattApiException("Invalid JSON response"); } /** @@ -249,11 +249,11 @@ public Map getMixAllSettings() throws ApiException { * See https://github.com/indykoning/PyPi_GrowattServer/blob/master/growattServer/__init__.py * * @return a Map of JSON elements containing the server response. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ - public Map getPlantInfo() throws ApiException { + public Map getPlantInfo() throws GrowattApiException { if (configuration.plantId == null) { - throw new ApiException("Plant Id missing"); + throw new GrowattApiException("Plant Id missing"); } Map params = new LinkedHashMap<>(); // keep params in order @@ -272,11 +272,11 @@ public Map getPlantInfo() throws ApiException { * See https://github.com/indykoning/PyPi_GrowattServer/blob/master/growattServer/__init__.py * * @return a Map of JSON elements containing the server response. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ - public Map getPlantList() throws ApiException { + public Map getPlantList() throws GrowattApiException { if (configuration.userId == null) { - throw new ApiException("User Id missing"); + throw new GrowattApiException("User Id missing"); } Map params = new LinkedHashMap<>(); // keep params in order @@ -293,21 +293,21 @@ public Map getPlantList() throws ApiException { } } } - throw new ApiException("Invalid JSON response"); + throw new GrowattApiException("Invalid JSON response"); } /** * Attempt to login to the remote server by posting the given user credentials. * * @return a Map of JSON elements containing the server response. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ - private Map postLoginCredentials() throws ApiException { + private Map postLoginCredentials() throws GrowattApiException { if (configuration.userName == null) { - throw new ApiException("User name missing"); + throw new GrowattApiException("User name missing"); } if (configuration.password == null) { - throw new ApiException("Password missing"); + throw new GrowattApiException("Password missing"); } Fields fields = new Fields(); @@ -325,7 +325,7 @@ private Map postLoginCredentials() throws ApiException { } } } - throw new ApiException("Login failed"); + throw new GrowattApiException("Login failed"); } /** @@ -338,15 +338,15 @@ private Map postLoginCredentials() throws ApiException { * @param stopTime the stop time of the charging program * @param programEnable charge program shall be enabled * @return a Map of JSON elements containing the server response - * @throws ApiException if any error occurs + * @throws GrowattApiException if any error occurs */ public Map setupChargingProgram(int chargingPower, int targetSOC, boolean allowAcCharging, - LocalTime startTime, LocalTime stopTime, boolean programEnable) throws ApiException { + LocalTime startTime, LocalTime stopTime, boolean programEnable) throws GrowattApiException { if (chargingPower < 1 || chargingPower > 100) { - throw new ApiException("Charge power out of range (1%..100%)"); + throw new GrowattApiException("Charge power out of range (1%..100%)"); } if (targetSOC < 1 || targetSOC > 100) { - throw new ApiException("Target SOC out of range (1%..100%)"); + throw new GrowattApiException("Target SOC out of range (1%..100%)"); } Fields fields = new Fields(); @@ -380,7 +380,7 @@ public Map setupChargingProgram(int chargingPower, int targ return result; } } - throw new ApiException("Command failed"); + throw new GrowattApiException("Command failed"); } /** @@ -392,15 +392,15 @@ public Map setupChargingProgram(int chargingPower, int targ * @param stopTime the stop time of the discharging program * @param programEnable discharge program shall be enabled * @return a Map of JSON elements containing the server response - * @throws ApiException if any error occurs + * @throws GrowattApiException if any error occurs */ public Map setupDischargingProgram(int dischargingPower, int targetSOC, LocalTime startTime, - LocalTime stopTime, boolean programEnable) throws ApiException { + LocalTime stopTime, boolean programEnable) throws GrowattApiException { if (dischargingPower < 1 || dischargingPower > 100) { - throw new ApiException("Discharge power out of range (1%..100%)"); + throw new GrowattApiException("Discharge power out of range (1%..100%)"); } if (targetSOC < 1 || targetSOC > 100) { - throw new ApiException("Target SOC out of range (1%..100%)"); + throw new GrowattApiException("Target SOC out of range (1%..100%)"); } Fields fields = new Fields(); @@ -433,7 +433,7 @@ public Map setupDischargingProgram(int dischargingPower, in return result; } } - throw new ApiException("Command failed"); + throw new GrowattApiException("Command failed"); } /** @@ -442,9 +442,9 @@ public Map setupDischargingProgram(int dischargingPower, in * @param map the source map. * @param key the key to search for in the map. * @return the boolean value. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ - public static boolean mapGetBoolean(Map map, String key) throws ApiException { + public static boolean mapGetBoolean(Map map, String key) throws GrowattApiException { JsonElement element = map.get(key); if (element instanceof JsonPrimitive primitive) { if (primitive.isBoolean()) { @@ -458,11 +458,11 @@ public static boolean mapGetBoolean(Map map, String key) th return true; } } catch (NumberFormatException e) { - throw new ApiException("Boolean bad value", e); + throw new GrowattApiException("Boolean bad value", e); } } } - throw new ApiException("Boolean missing or bad value"); + throw new GrowattApiException("Boolean missing or bad value"); } /** @@ -471,18 +471,18 @@ public static boolean mapGetBoolean(Map map, String key) th * @param map the source map. * @param key the key to search for in the map. * @return the integer value. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ - public static int mapGetInteger(Map map, String key) throws ApiException { + public static int mapGetInteger(Map map, String key) throws GrowattApiException { JsonElement element = map.get(key); if (element instanceof JsonPrimitive primitive) { try { return primitive.getAsInt(); } catch (NumberFormatException e) { - throw new ApiException("Integer bad value", e); + throw new GrowattApiException("Integer bad value", e); } } - throw new ApiException("Integer missing or bad value"); + throw new GrowattApiException("Integer missing or bad value"); } /** @@ -491,18 +491,18 @@ public static int mapGetInteger(Map map, String key) throws * @param source the source map. * @param key the key to search for in the map. * @return the LocalTime. - * @throws ApiException if any error occurs. + * @throws GrowattApiException if any error occurs. */ - public static LocalTime mapGetLocalTime(Map source, String key) throws ApiException { + public static LocalTime mapGetLocalTime(Map source, String key) throws GrowattApiException { JsonElement element = source.get(key); if ((element instanceof JsonPrimitive primitive) && primitive.isString()) { try { return localTimeOf(primitive.getAsString()); } catch (DateTimeException e) { - throw new ApiException("LocalTime bad value", e); + throw new GrowattApiException("LocalTime bad value", e); } } - throw new ApiException("LocalTime missing or bad value"); + throw new GrowattApiException("LocalTime missing or bad value"); } /** diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 6b43c5abbf917..27a5d6dd865db 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.growatt.internal.action.GrowattActions; -import org.openhab.binding.growatt.internal.cloud.ApiException; +import org.openhab.binding.growatt.internal.cloud.GrowattApiException; import org.openhab.binding.growatt.internal.cloud.GrowattCloud; import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; @@ -194,7 +194,7 @@ public void setupChargingProgram(Number chargingPower, Number targetSOC, boolean try { getGrowattCloud().setupChargingProgram(chargingPower.intValue(), targetSOC.intValue(), allowAcCharging, GrowattCloud.localTimeOf(startTime), GrowattCloud.localTimeOf(stopTime), programEnable); - } catch (IllegalStateException | DateTimeParseException | ApiException e) { + } catch (IllegalStateException | DateTimeParseException | GrowattApiException e) { logger.warn("setupChargingProgram() error", e); this.growattCloud = null; } @@ -214,7 +214,7 @@ public void setupDischargingProgram(Number dischargingPower, Number targetSOC, S try { getGrowattCloud().setupDischargingProgram(dischargingPower.intValue(), targetSOC.intValue(), GrowattCloud.localTimeOf(startTime), GrowattCloud.localTimeOf(stopTime), programEnable); - } catch (IllegalStateException | DateTimeParseException | ApiException e) { + } catch (IllegalStateException | DateTimeParseException | GrowattApiException e) { logger.warn("setupDischargingProgram() error", e); this.growattCloud = null; } diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index b9cf0e35f41c3..6bafffd02f2dd 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -17,10 +17,12 @@ import static org.mockito.Mockito.*; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Field; import java.time.LocalTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -33,7 +35,6 @@ import org.junit.jupiter.api.Test; import org.openhab.binding.growatt.internal.GrowattChannels; import org.openhab.binding.growatt.internal.GrowattChannels.UoM; -import org.openhab.binding.growatt.internal.cloud.ApiException; import org.openhab.binding.growatt.internal.cloud.GrowattCloud; import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; @@ -61,8 +62,11 @@ public class GrowattTest { /** * Load a (JSON) string from a file + * + * @throws IOException + * @throws FileNotFoundException */ - private String load(String fileName) { + private String load(String fileName) throws FileNotFoundException, IOException { try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName)); BufferedReader reader = new BufferedReader(file)) { StringBuilder builder = new StringBuilder(); @@ -71,10 +75,7 @@ private String load(String fileName) { builder.append(line).append("\n"); } return builder.toString(); - } catch (IOException e) { - fail(e.getMessage()); } - return ""; } /** @@ -82,8 +83,10 @@ private String load(String fileName) { * * @param fileName the file containing the JSON payload. * @return a GrottValues DTO. + * @throws IOException + * @throws FileNotFoundException */ - private GrottValues loadGrottValues(String fileName) { + private GrottValues loadGrottValues(String fileName) throws FileNotFoundException, IOException { String json = load(fileName); GrottDevice device = gson.fromJson(json, GrottDevice.class); assertNotNull(device); @@ -93,7 +96,7 @@ private GrottValues loadGrottValues(String fileName) { } @Test - void testGrottValuesAccessibility() { + void testGrottValuesAccessibility() throws FileNotFoundException, IOException { testGrottValuesAccessibility("simple"); testGrottValuesAccessibility("sph"); } @@ -104,8 +107,10 @@ void testGrottValuesAccessibility() { * instance. * * @param fileName the name of the JSON file to be tested. + * @throws IOException + * @throws FileNotFoundException */ - private void testGrottValuesAccessibility(String fileName) { + private void testGrottValuesAccessibility(String fileName) throws FileNotFoundException, IOException { GrottValues grottValues = loadGrottValues(fileName); List fields = Arrays.asList(GrottValues.class.getFields()).stream().map(f -> f.getName()) @@ -123,6 +128,7 @@ private void testGrottValuesAccessibility(String fileName) { // test that the CHANNEL_ID_UOM_MAP and the GrottValues DTO have the same number of fields resp. channel ids assertEquals(fields.size(), GrowattChannels.getMap().size()); + List errors = new ArrayList<>(); for (Entry entry : GrowattChannels.getMap().entrySet()) { String channelId = entry.getKey(); @@ -130,11 +136,9 @@ private void testGrottValuesAccessibility(String fileName) { // test that the field can be accessed try { field = GrottValues.class.getField(GrottValues.getFieldName(channelId)); - } catch (NoSuchFieldException e) { - fail(e.getMessage()); - continue; - } catch (SecurityException e) { - fail(e.getMessage()); + } catch (NoSuchFieldException | SecurityException e) { + String msg = e.getMessage(); + errors.add(msg != null ? msg : e.getClass().getName()); continue; } // test that the field value is either null or an Integer @@ -142,17 +146,29 @@ private void testGrottValuesAccessibility(String fileName) { Object value = field.get(grottValues); assertTrue(value == null || (value instanceof Integer)); } catch (IllegalArgumentException | IllegalAccessException e) { - fail(e.getMessage()); + String msg = e.getMessage(); + errors.add(msg != null ? msg : e.getClass().getName()); continue; } } + if (errors.size() > 0) { + fail(errors.toString()); + } } /** * Spot checks to test that GrottValues is loaded with the correct contents from the "simple" JSON file. + * + * @throws IOException + * @throws FileNotFoundException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws SecurityException + * @throws NoSuchFieldException */ @Test - void testGrottValuesContents() { + void testGrottValuesContents() throws FileNotFoundException, IOException, NoSuchFieldException, SecurityException, + IllegalAccessException, IllegalArgumentException { GrottValues grottValues = loadGrottValues("simple"); assertEquals(1, grottValues.system_status); @@ -174,17 +190,7 @@ void testGrottValuesContents() { assertEquals(65503878, grottValues.total_work_time); Map> channelStates = null; - try { - channelStates = grottValues.getChannelStates(); - } catch (NoSuchFieldException e) { - fail(e.getMessage()); - } catch (SecurityException e) { - fail(e.getMessage()); - } catch (IllegalAccessException e) { - fail(e.getMessage()); - } catch (IllegalArgumentException e) { - fail(e.getMessage()); - } + channelStates = grottValues.getChannelStates(); assertNotNull(channelStates); assertEquals(29, channelStates.size()); @@ -221,7 +227,7 @@ void testGrottValuesContents() { } @Test - void testJsonFieldsMappedToDto() { + void testJsonFieldsMappedToDto() throws FileNotFoundException, IOException { testJsonFieldsMappedToDto("simple"); testJsonFieldsMappedToDto("sph"); } @@ -231,8 +237,10 @@ void testJsonFieldsMappedToDto() { * values DTO. * * @param fileName the name of the JSON file to be tested. + * @throws IOException + * @throws FileNotFoundException */ - private void testJsonFieldsMappedToDto(String fileName) { + private void testJsonFieldsMappedToDto(String fileName) throws FileNotFoundException, IOException { Field[] fields = GrottValues.class.getFields(); String json = load(fileName); JsonParser.parseString(json).getAsJsonObject().get("values").getAsJsonObject().entrySet().forEach(e -> { @@ -242,15 +250,20 @@ private void testJsonFieldsMappedToDto(String fileName) { testJsonObject.add(key, e.getValue()); GrottValues testDto = gson.fromJson(testJsonObject, GrottValues.class); int mappedFieldCount = 0; + List errors = new ArrayList<>(); for (Field field : fields) { try { if (field.get(testDto) != null) { mappedFieldCount++; } } catch (IllegalAccessException | IllegalArgumentException ex) { - fail("Exception"); + String msg = ex.getMessage(); + errors.add(msg != null ? msg : ex.getClass().getName()); } } + if (errors.size() > 0) { + fail(errors.toString()); + } assertEquals(1, mappedFieldCount); } }); @@ -259,9 +272,11 @@ private void testJsonFieldsMappedToDto(String fileName) { /** * Test the Growatt remote cloud API server. * Will not run unless actual user credentials are provided. + * + * @throws Exception */ @Test - void testServer() { + void testServer() throws Exception { GrowattInverterConfiguration configuration = new GrowattInverterConfiguration(); /* * To test on an actual inverter, populate its plant data and user credentials below. @@ -282,16 +297,8 @@ void testServer() { .thenReturn(new HttpClient(new SslContextFactory.Client(true))); try (GrowattCloud api = new GrowattCloud(configuration, httpClientFactory)) { - try { - assertFalse(api.getPlantList().isEmpty()); - } catch (ApiException e) { - fail(e); - } - try { - assertFalse(api.getPlantInfo().isEmpty()); - } catch (ApiException e) { - fail(e); - } + assertFalse(api.getPlantList().isEmpty()); + assertFalse(api.getPlantInfo().isEmpty()); int chargingPower = 97; int targetSOC = 23; @@ -299,25 +306,18 @@ void testServer() { LocalTime startTime = LocalTime.of(1, 16); LocalTime stopTime = LocalTime.of(2, 17); boolean programEnable = false; - try { - assertFalse(api.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, - programEnable).isEmpty()); - } catch (ApiException e) { - fail(e); - } - try { - Map result = api.getMixAllSettings(); - assertFalse(result.isEmpty()); - assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); - assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); - assertEquals(allowAcCharging, - GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); - assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); - assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); - assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); - } catch (ApiException e) { - fail(e); - } + assertFalse(api + .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) + .isEmpty()); + Map result = api.getMixAllSettings(); + assertFalse(result.isEmpty()); + assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); + assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); + assertEquals(allowAcCharging, + GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); + assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); + assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); + assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); chargingPower = 100; targetSOC = 20; @@ -325,27 +325,18 @@ void testServer() { startTime = LocalTime.of(0, 15); stopTime = LocalTime.of(6, 45); programEnable = true; - try { - assertFalse(api.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, - programEnable).isEmpty()); - } catch (ApiException e) { - fail(e); - } - try { - Map result = api.getMixAllSettings(); - assertFalse(result.isEmpty()); - assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); - assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); - assertEquals(allowAcCharging, - GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); - assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); - assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); - assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); - } catch (ApiException e) { - fail(e); - } - } catch (Exception e) { - fail(e); + assertFalse(api + .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) + .isEmpty()); + result = api.getMixAllSettings(); + assertFalse(result.isEmpty()); + assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); + assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); + assertEquals(allowAcCharging, + GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); + assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); + assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); + assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); } } } From 676a7e5c14a11ec4dca30df1266844dce28a6452 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 23 Dec 2023 11:58:06 +0000 Subject: [PATCH 129/146] Update version Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.growatt/pom.xml b/bundles/org.openhab.binding.growatt/pom.xml index 0a328b0db37d8..649edba48417f 100644 --- a/bundles/org.openhab.binding.growatt/pom.xml +++ b/bundles/org.openhab.binding.growatt/pom.xml @@ -7,7 +7,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.1.0-SNAPSHOT + 4.2.0-SNAPSHOT org.openhab.binding.growatt From 955dc24a65e68bf361ea66d6eb5ff6fbf1ff4b07 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 27 Dec 2023 16:30:56 +0000 Subject: [PATCH 130/146] Add device synonyms; handle integer overflows Signed-off-by: Andrew Fiddian-Green --- .../dto/GrottIntegerDeserializer.java | 39 +++++ .../growatt/internal/dto/GrottValues.java | 70 ++++---- .../handler/GrowattBridgeHandler.java | 5 +- .../binding/growatt/test/GrowattTest.java | 23 ++- .../src/test/resources/3phase.json | 158 ++++++++++++++++++ .../src/test/resources/meter.json | 39 +++++ 6 files changed, 295 insertions(+), 39 deletions(-) create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottIntegerDeserializer.java create mode 100644 bundles/org.openhab.binding.growatt/src/test/resources/3phase.json create mode 100644 bundles/org.openhab.binding.growatt/src/test/resources/meter.json diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottIntegerDeserializer.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottIntegerDeserializer.java new file mode 100644 index 0000000000000..ba07b880e725a --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottIntegerDeserializer.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto; + +import java.lang.reflect.Type; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +/** + * Special deserializer for integer values. Handles integer overflows gently. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +public class GrottIntegerDeserializer implements JsonDeserializer { + + @Override + public @NonNull Integer deserialize(@Nullable JsonElement json, @Nullable Type typeOfT, + @Nullable JsonDeserializationContext context) throws JsonParseException { + long value = Long.parseLong(Objects.requireNonNull(json).getAsString()); + return Long.valueOf(value).intValue(); + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 41aecac895f57..793ab35527f13 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -64,22 +64,22 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "pv2watt", alternate = { "ppv2" }) Integer pv2_power; // AC mains electric data (1-phase resp. 3-phase) - public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq", "outputfreq" }) Integer grid_frequency; - public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt", "outputvolt" }) Integer grid_voltage_r; - public @Nullable @SerializedName(value = "pvgridvoltage2") Integer grid_voltage_s; - public @Nullable @SerializedName(value = "pvgridvoltage3") Integer grid_voltage_t; - public @Nullable @SerializedName(value = "Vac_RS") Integer grid_voltage_rs; - public @Nullable @SerializedName(value = "Vac_ST") Integer grid_voltage_st; - public @Nullable @SerializedName(value = "Vac_TR") Integer grid_voltage_tr; + public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq", "outputfreq", "frequency" }) Integer grid_frequency; + public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt", "outputvolt", "voltage_l1" }) Integer grid_voltage_r; + public @Nullable @SerializedName(value = "pvgridvoltage2", alternate = { "voltage_l2" }) Integer grid_voltage_s; + public @Nullable @SerializedName(value = "pvgridvoltage3", alternate = { "voltage_l3" }) Integer grid_voltage_t; + public @Nullable @SerializedName(value = "Vac_RS", alternate = { "vacrs", "L1-2_voltage" }) Integer grid_voltage_rs; + public @Nullable @SerializedName(value = "Vac_ST", alternate = { "vacst", "L2-3_voltage" }) Integer grid_voltage_st; + public @Nullable @SerializedName(value = "Vac_TR", alternate = { "vactr", "L3-1_voltage" }) Integer grid_voltage_tr; // solar AC mains power - public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr", "Inv_Curr" }) Integer inverter_current_r; - public @Nullable @SerializedName(value = "pvgridcurrent2") Integer inverter_current_s; - public @Nullable @SerializedName(value = "pvgridcurrent3") Integer inverter_current_t; + public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr", "Inv_Curr", "Current_l1" }) Integer inverter_current_r; + public @Nullable @SerializedName(value = "pvgridcurrent2", alternate = { "Current_l2"}) Integer inverter_current_s; + public @Nullable @SerializedName(value = "pvgridcurrent3", alternate = { "Current_l3"}) Integer inverter_current_t; - public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt", "AC_InWatt" }) Integer inverter_power_r; - public @Nullable @SerializedName(value = "pvgridpower2") Integer inverter_power_s; - public @Nullable @SerializedName(value = "pvgridpower3") Integer inverter_power_t; + public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt", "AC_InWatt", "act_power_l1" }) Integer inverter_power_r; + public @Nullable @SerializedName(value = "pvgridpower2", alternate = { "act_power_l2" }) Integer inverter_power_s; + public @Nullable @SerializedName(value = "pvgridpower3", alternate = { "act_power_l3" }) Integer inverter_power_t; public @Nullable @SerializedName(value = "op_va", alternate = { "AC_InVA" }) Integer inverter_va; @@ -92,19 +92,19 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "ACDischarVA", alternate = { "BatDischarVA", "acchar_VA" }) Integer discharge_va; // power exported to utility company - public @Nullable @SerializedName(value = "pactogridtot") Integer export_power; + public @Nullable @SerializedName(value = "pactogridtot", alternate = { "ptogridtotal" }) Integer export_power; public @Nullable @SerializedName(value = "pactogridr") Integer export_power_r; public @Nullable @SerializedName(value = "pactogrids") Integer export_power_s; public @Nullable @SerializedName(value = "pactogridt") Integer export_power_t; // power imported from utility company - public @Nullable @SerializedName(value = "pactousertot") Integer import_power; + public @Nullable @SerializedName(value = "pactousertot", alternate = { "ptousertotal" }) Integer import_power; public @Nullable @SerializedName(value = "pactouserr") Integer import_power_r; public @Nullable @SerializedName(value = "pactousers") Integer import_power_s; public @Nullable @SerializedName(value = "pactousert") Integer import_power_t; // power delivered to internal load - public @Nullable @SerializedName(value = "plocaloadtot") Integer load_power; + public @Nullable @SerializedName(value = "plocaloadtot", alternate = { "ptoloadtotal" }) Integer load_power; public @Nullable @SerializedName(value = "plocaloadr") Integer load_power_r; public @Nullable @SerializedName(value = "plocaloads") Integer load_power_s; public @Nullable @SerializedName(value = "plocaloadt") Integer load_power_t; @@ -123,50 +123,50 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "epv2total", alternate = { "epv2tot" }) Integer pv2_energy_total; // energy exported to utility company - public @Nullable @SerializedName(value = "etogrid_tod") Integer export_energy_today; - public @Nullable @SerializedName(value = "etogrid_tot") Integer export_energy_total; + public @Nullable @SerializedName(value = "etogrid_tod", alternate = { "etogridtoday" }) Integer export_energy_today; + public @Nullable @SerializedName(value = "etogrid_tot", alternate = { "etogridtotal" }) Integer export_energy_total; // energy imported from utility company - public @Nullable @SerializedName(value = "etouser_tod") Integer import_energy_today; - public @Nullable @SerializedName(value = "etouser_tot") Integer import_energy_total; + public @Nullable @SerializedName(value = "etouser_tod", alternate = { "etousertoday" }) Integer import_energy_today; + public @Nullable @SerializedName(value = "etouser_tot", alternate = { "etousertotal" }) Integer import_energy_total; // energy supplied to local load - public @Nullable @SerializedName(value = "elocalload_tod") Integer load_energy_today; - public @Nullable @SerializedName(value = "elocalload_tot") Integer load_energy_total; + public @Nullable @SerializedName(value = "elocalload_tod", alternate = { "eloadtoday" }) Integer load_energy_today; + public @Nullable @SerializedName(value = "elocalload_tot", alternate = { "eloadtotal" }) Integer load_energy_total; // charging energy from import - public @Nullable @SerializedName(value = "eharge1_tod") Integer import_charge_energy_today; - public @Nullable @SerializedName(value = "eharge1_tot") Integer import_charge_energy_total; + public @Nullable @SerializedName(value = "eharge1_tod", alternate = { "echrtoday" }) Integer import_charge_energy_today; + public @Nullable @SerializedName(value = "eharge1_tot", alternate = { "echrtotal" }) Integer import_charge_energy_total; // charging energy from solar - public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday" }) Integer inverter_charge_energy_today; - public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal" }) Integer inverter_charge_energy_total; + public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday", "eacchrtoday" }) Integer inverter_charge_energy_today; + public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal", "eacchrtotal" }) Integer inverter_charge_energy_total; // discharging energy - public @Nullable @SerializedName(value = "edischarge1_tod", alternate = { "eacDischarToday", "ebatDischarToday" }) Integer discharge_energy_today; - public @Nullable @SerializedName(value = "edischarge1_tot", alternate = { "eacDischarTotal","ebatDischarTotal" }) Integer discharge_energy_total; + public @Nullable @SerializedName(value = "edischarge1_tod", alternate = { "eacDischarToday", "ebatDischarToday", "edischrtoday" }) Integer discharge_energy_today; + public @Nullable @SerializedName(value = "edischarge1_tot", alternate = { "eacDischarTotal", "ebatDischarTotal", "edischrtotal" }) Integer discharge_energy_total; // inverter up time public @Nullable @SerializedName(value = "totworktime") Integer total_work_time; // bus voltages - public @Nullable @SerializedName(value = "pbusvolt", alternate = { "bus_volt" }) Integer p_bus_voltage; - public @Nullable @SerializedName(value = "nbusvolt") Integer n_bus_voltage; + public @Nullable @SerializedName(value = "pbusvolt", alternate = { "bus_volt", "pbusvoltage" }) Integer p_bus_voltage; + public @Nullable @SerializedName(value = "nbusvolt", alternate = { "nbusvoltage" }) Integer n_bus_voltage; public @Nullable @SerializedName(value = "spbusvolt") Integer sp_bus_voltage; // temperatures public @Nullable @SerializedName(value = "pvtemperature", alternate = { "dcdctemp", "buck1_ntc" }) Integer pv_temperature; public @Nullable @SerializedName(value = "pvipmtemperature", alternate = { "invtemp" }) Integer pv_ipm_temperature; - public @Nullable @SerializedName(value = "pvboosttemp", alternate = {"pvboottemperature" }) Integer pv_boost_temperature; + public @Nullable @SerializedName(value = "pvboosttemp", alternate = { "pvboottemperature", "temp3" }) Integer pv_boost_temperature; public @Nullable @SerializedName(value = "temp4") Integer temperature_4; - public @Nullable @SerializedName(value = "buck2_ntc") Integer pv2_temperature; + public @Nullable @SerializedName(value = "buck2_ntc", alternate = { "temp5" }) Integer pv2_temperature; // battery data public @Nullable @SerializedName(value = "batterytype") Integer battery_type; - public @Nullable @SerializedName(value = "batttemp") Integer battery_temperature; - public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt" }) Integer battery_voltage; + public @Nullable @SerializedName(value = "batttemp", alternate = { "bdc1_tempa" }) Integer battery_temperature; + public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt", "bdc1_vbat" }) Integer battery_voltage; public @Nullable @SerializedName(value = "bat_dsp") Integer battery_display; - public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC" }) Integer battery_soc; + public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC", "bdc1_soc" }) Integer battery_soc; // fault codes public @Nullable @SerializedName(value = "systemfaultword0", alternate = { "isof", "faultBit" }) Integer system_fault_0; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 861dfa1a769cf..7a2ce25ea8659 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.dto.GrottDevice; +import org.openhab.binding.growatt.internal.dto.GrottIntegerDeserializer; import org.openhab.binding.growatt.internal.servlet.GrowattHttpServlet; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; @@ -28,6 +29,7 @@ import org.slf4j.LoggerFactory; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; @@ -41,7 +43,8 @@ public class GrowattBridgeHandler extends BaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(GrowattBridgeHandler.class); - private final Gson gson = new Gson(); + private final Gson gson = new GsonBuilder().registerTypeAdapter(Integer.class, new GrottIntegerDeserializer()) + .create(); private final GrowattDiscoveryService discoveryService; private final Map inverters = new HashMap<>(); private final GrowattHttpServlet httpServlet; diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 6bafffd02f2dd..e4318250f8705 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -38,6 +38,7 @@ import org.openhab.binding.growatt.internal.cloud.GrowattCloud; import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; +import org.openhab.binding.growatt.internal.dto.GrottIntegerDeserializer; import org.openhab.binding.growatt.internal.dto.GrottValues; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.QuantityType; @@ -46,6 +47,7 @@ import org.openhab.core.types.State; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -58,7 +60,8 @@ @NonNullByDefault public class GrowattTest { - private final Gson gson = new Gson(); + private final Gson gson = new GsonBuilder().registerTypeAdapter(Integer.class, new GrottIntegerDeserializer()) + .create(); /** * Load a (JSON) string from a file @@ -151,7 +154,7 @@ private void testGrottValuesAccessibility(String fileName) throws FileNotFoundEx continue; } } - if (errors.size() > 0) { + if (!errors.isEmpty()) { fail(errors.toString()); } } @@ -261,7 +264,7 @@ private void testJsonFieldsMappedToDto(String fileName) throws FileNotFoundExcep errors.add(msg != null ? msg : ex.getClass().getName()); } } - if (errors.size() > 0) { + if (!errors.isEmpty()) { fail(errors.toString()); } assertEquals(1, mappedFieldCount); @@ -339,4 +342,18 @@ void testServer() throws Exception { assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); } } + + @Test + void testThreePhaseGrottValuesContents() throws FileNotFoundException, IOException, NoSuchFieldException, + SecurityException, IllegalAccessException, IllegalArgumentException { + GrottValues grottValues = loadGrottValues("3phase"); + assertNotNull(grottValues); + } + + @Test + void testMeterGrottValuesContents() throws FileNotFoundException, IOException, NoSuchFieldException, + SecurityException, IllegalAccessException, IllegalArgumentException { + GrottValues grottValues = loadGrottValues("meter"); + assertNotNull(grottValues); + } } diff --git a/bundles/org.openhab.binding.growatt/src/test/resources/3phase.json b/bundles/org.openhab.binding.growatt/src/test/resources/3phase.json new file mode 100644 index 0000000000000..ee5371721d286 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/test/resources/3phase.json @@ -0,0 +1,158 @@ +{ + "device": "KLN0D6L034", + "time": "2023-12-26T21:48:33", + "buffered": "no", + "values": { + "pvserial": "KLN0D6L034", + "pvstatus": 1, + "pvpowerin": 0, + "pv1voltage": 669, + "pv1current": 0, + "pv1watt": 0, + "pv2voltage": 695, + "pv2current": 0, + "pv2watt": 0, + "pv3voltage": 0, + "pv3current": 0, + "pv3watt": 0, + "pv4voltage": 0, + "pv4current": 0, + "pv4watt": 0, + "pvpowerout": 4294966929, + "pvfrequentie": 5001, + "pvgridvoltage": 2359, + "pvgridcurrent": 7, + "pvgridpower": 1651, + "pvgridvoltage2": 2367, + "pvgridcurrent2": 8, + "pvgridpower2": 1893, + "pvgridvoltage3": 2379, + "pvgridcurrent3": 8, + "pvgridpower3": 1903, + "vacrs": 4084, + "vacst": 4118, + "vactr": 4104, + "ptousertotal": 8099, + "ptogridtotal": 0, + "ptoloadtotal": 8239, + "totworktime": 79652, + "pvenergytoday": 178, + "pvenergytotal": 178, + "epvtotal ": 162, + "epv1today ": 79, + "epv1total": 79, + "epv2today": 83, + "epv2total": 83, + "epv3today": 0, + "epv3total": 0, + "etousertoday": 64, + "etousertotal": 64, + "etogridtoday": 1, + "etogridtotal": 1, + "eloadtoday": 237, + "eloadtotal": 0, + "deratingmode": 0, + "iso": 15997, + "dcir": 0, + "dcis": 0, + "dcit": 0, + "gfci": 137645, + "pvtemperature": 296, + "pvipmtemperature": 410, + "temp3": 296, + "temp4": 0, + "temp5": 319, + "pbusvoltage": 3311, + "nbusvoltage": 3265, + "ipf": 20000, + "realoppercent": 0, + "opfullwatt": 150000, + "standbyflag": 0, + "faultcode": 0, + "warningcode": 0, + "systemfaultword0": 0, + "systemfaultword1": 0, + "systemfaultword2": 0, + "systemfaultword3": 0, + "systemfaultword4": 0, + "systemfaultword5": 0, + "systemfaultword6": 0, + "systemfaultword7": 0, + "invstartdelaytime": 60, + "bdconoffstate": 1, + "drycontactstate": 0, + "edischrtoday": 103, + "edischrtotal": 1843, + "echrtoday": 91, + "echrtotal": 3005, + "eacchrtoday": 5, + "eacchrtotal": 5, + "priority": 1, + "epsfac": 0, + "epsvac1": 0, + "epsiac1": 0, + "epspac1": 0, + "epsvac2": 0, + "epsiac2": 0, + "epspac2": 0, + "epsvac3": 0, + "epsiac3": 0, + "epspac3": 0, + "epspac": 0, + "loadpercent": 0, + "pf": 10000, + "dcv": 0, + "bdc1_sysstatemode": 513, + "bdc1_faultcode": 0, + "bdc1_warncode": 701, + "bdc1_vbat": 6582, + "bdc1_ibat": 0, + "bdc1_soc": 11, + "bdc1_vbus1": 6582, + "bdc1_vbus2": 3303, + "bdc1_ibb": 0, + "bdc1_illc": 0, + "bdc1_tempa": 409, + "bdc1_tempb": 291, + "bdc1_pdischr": 100, + "bdc1_pchr": 0, + "bdc1_edischrtotal": 1843, + "bdc1_echrtotal": 3005, + "bdc1_flag": 1, + "bdc2_sysstatemode": 17, + "bdc2_faultcode": 12, + "bdc2_warncode": 248, + "bdc2_vbat": 266, + "bdc2_ibat": 223, + "bdc2_soc": 19, + "bdc2_vbus1": 49, + "bdc2_vbus2": 11, + "bdc2_ibb": 11, + "bdc2_illc": 4, + "bdc2_tempa": 0, + "bdc2_tempb": 394, + "bdc2_pdischr": 26214400, + "bdc2_pchr": 0, + "bdc2_edischrtotal": 0, + "bdc2_echrtotal": 0, + "bdc2_flag": 0, + "bms_status": 4, + "bms_error": 0, + "bms_warninfo": 0, + "bms_soc": 11, + "bms_batteryvolt": 4041, + "bms_batterycurr": 0, + "bms_batterytemp": 0, + "bms_maxcurr": 2200, + "bms_deltavolt": 2200, + "bms_cyclecnt": 0, + "bms_soh": 100, + "bms_constantvolt": 568, + "bms_bms_info": 464, + "bms_packinfo": 0, + "bms_usingcap": 0, + "bms_fw": 1400, + "bms_mcuversion": 0, + "bms_commtype": 1 + } +} diff --git a/bundles/org.openhab.binding.growatt/src/test/resources/meter.json b/bundles/org.openhab.binding.growatt/src/test/resources/meter.json new file mode 100644 index 0000000000000..5388464ac9652 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/test/resources/meter.json @@ -0,0 +1,39 @@ +{ + "device": "GZL0DA804M", + "time": "2023-12-26T21:48:36", + "buffered": "no", + "values": { + "datalogserial": "GZL0DA804M", + "pvserial": "KLN0D6L034", + "voltage_l1": 2355, + "voltage_l2": 2374, + "voltage_l3": 2376, + "Current_l1": 22, + "Current_l2": 15, + "Current_l3": 12, + "act_power_l1": 4909, + "act_power_l2": 1710, + "act_power_l3": 1478, + "app_power_l1": 5058, + "app_power_l2": 3321, + "app_power_l3": 2712, + "react_power_l1": -1222, + "react_power_l2": -2847, + "react_power_l3": -2275, + "powerfactor_l1": 936, + "powerfactor_l2": 481, + "powerfactor_l3": 502, + "pos_rev_act_power": 8098, + "pos_act_power": 8098, + "rev_act_power": 8098, + "app_power": 11091, + "react_power": -6346, + "powerfactor": 690, + "frequency": 500, + "L1-2_voltage": 4095, + "L2-3_voltage": 4113, + "L3-1_voltage": 4097, + "pos_act_energy": 9587, + "rev_act_energy": 1387 + } +} From b835bf57949b2deb526392b5e923f4b484295f5a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 28 Dec 2023 14:28:03 +0000 Subject: [PATCH 131/146] Fix integer overflow; Tweak synonyms; Add tests Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/dto/GrottValues.java | 8 +++---- .../GrottIntegerDeserializer.java | 11 ++++++++-- .../handler/GrowattBridgeHandler.java | 2 +- .../binding/growatt/test/GrowattTest.java | 21 ++++++++++++++++++- 4 files changed, 34 insertions(+), 8 deletions(-) rename bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/{dto => gson}/GrottIntegerDeserializer.java (69%) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 793ab35527f13..249312207b72a 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -163,10 +163,10 @@ public static String getFieldName(String channelId) { // battery data public @Nullable @SerializedName(value = "batterytype") Integer battery_type; - public @Nullable @SerializedName(value = "batttemp", alternate = { "bdc1_tempa" }) Integer battery_temperature; - public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt", "bdc1_vbat" }) Integer battery_voltage; - public @Nullable @SerializedName(value = "bat_dsp") Integer battery_display; - public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC", "bdc1_soc" }) Integer battery_soc; + public @Nullable @SerializedName(value = "batttemp", alternate = { "bms_batterytemp" }) Integer battery_temperature; + public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt", "bms_batteryvolt" }) Integer battery_voltage; + public @Nullable @SerializedName(value = "bat_dsp", alternate = { "bms_status" }) Integer battery_display; + public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC", "bms_soc" }) Integer battery_soc; // fault codes public @Nullable @SerializedName(value = "systemfaultword0", alternate = { "isof", "faultBit" }) Integer system_fault_0; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottIntegerDeserializer.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java similarity index 69% rename from bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottIntegerDeserializer.java rename to bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java index ba07b880e725a..149cad0118f54 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottIntegerDeserializer.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.growatt.internal.dto; +package org.openhab.binding.growatt.internal.gson; import java.lang.reflect.Type; import java.util.Objects; @@ -24,16 +24,23 @@ import com.google.gson.JsonParseException; /** - * Special deserializer for integer values. Handles integer overflows gently. + * Special deserializer for integer values. It processes inputs which overflow the Integer.MAX_VALUE limit by + * transposing them to negative numbers by means of the 2's complement process. * * @author Andrew Fiddian-Green - Initial contribution */ public class GrottIntegerDeserializer implements JsonDeserializer { + private static final long INT_BIT_MASK = 0xffffffff; + @Override public @NonNull Integer deserialize(@Nullable JsonElement json, @Nullable Type typeOfT, @Nullable JsonDeserializationContext context) throws JsonParseException { long value = Long.parseLong(Objects.requireNonNull(json).getAsString()); + if (value > Integer.MAX_VALUE) { + // transpose values above Integer.MAX_VALUE to a negative int by 2's complement + return Integer.valueOf(1 - (int) (value ^ INT_BIT_MASK)); + } return Long.valueOf(value).intValue(); } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 7a2ce25ea8659..50908bcbf90ad 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.dto.GrottDevice; -import org.openhab.binding.growatt.internal.dto.GrottIntegerDeserializer; +import org.openhab.binding.growatt.internal.gson.GrottIntegerDeserializer; import org.openhab.binding.growatt.internal.servlet.GrowattHttpServlet; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index e4318250f8705..29cf38231d2cc 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -38,8 +38,8 @@ import org.openhab.binding.growatt.internal.cloud.GrowattCloud; import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; -import org.openhab.binding.growatt.internal.dto.GrottIntegerDeserializer; import org.openhab.binding.growatt.internal.dto.GrottValues; +import org.openhab.binding.growatt.internal.gson.GrottIntegerDeserializer; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; @@ -348,6 +348,16 @@ void testThreePhaseGrottValuesContents() throws FileNotFoundException, IOExcepti SecurityException, IllegalAccessException, IllegalArgumentException { GrottValues grottValues = loadGrottValues("3phase"); assertNotNull(grottValues); + + Map> channelStates = grottValues.getChannelStates(); + assertNotNull(channelStates); + assertEquals(63, channelStates.size()); + + assertEquals(QuantityType.valueOf(-36.5, Units.WATT), channelStates.get("inverter-power")); + assertEquals(QuantityType.valueOf(11, Units.PERCENT), channelStates.get("battery-soc")); + assertEquals(QuantityType.valueOf(408.4, Units.VOLT), channelStates.get("grid-voltage-rs")); + assertEquals(QuantityType.valueOf(326.5, Units.VOLT), channelStates.get("n-bus-voltage")); + assertEquals(QuantityType.valueOf(404.1, Units.VOLT), channelStates.get("battery-voltage")); } @Test @@ -355,5 +365,14 @@ void testMeterGrottValuesContents() throws FileNotFoundException, IOException, N SecurityException, IllegalAccessException, IllegalArgumentException { GrottValues grottValues = loadGrottValues("meter"); assertNotNull(grottValues); + + Map> channelStates = grottValues.getChannelStates(); + assertNotNull(channelStates); + assertEquals(13, channelStates.size()); + + assertEquals(QuantityType.valueOf(171.0, Units.WATT), channelStates.get("inverter-power-s")); + assertEquals(QuantityType.valueOf(237.4, Units.VOLT), channelStates.get("grid-voltage-s")); + assertEquals(QuantityType.valueOf(1.5, Units.AMPERE), channelStates.get("inverter-current-s")); + assertEquals(QuantityType.valueOf(409.5, Units.VOLT), channelStates.get("grid-voltage-rs")); } } From 13880bed562ce787f3833785ad3bf53035349d7d Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 29 Dec 2023 14:10:15 +0000 Subject: [PATCH 132/146] Adopt user feedback on meter synonyms and tests Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/dto/GrottValues.java | 32 +++++++++---------- .../binding/growatt/test/GrowattTest.java | 7 ++-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 249312207b72a..756d00329a776 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -64,7 +64,7 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "pv2watt", alternate = { "ppv2" }) Integer pv2_power; // AC mains electric data (1-phase resp. 3-phase) - public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq", "outputfreq", "frequency" }) Integer grid_frequency; + public @Nullable @SerializedName(value = "pvfrequentie", alternate = { "line_freq", "outputfreq" }) Integer grid_frequency; public @Nullable @SerializedName(value = "pvgridvoltage", alternate = { "grid_volt", "outputvolt", "voltage_l1" }) Integer grid_voltage_r; public @Nullable @SerializedName(value = "pvgridvoltage2", alternate = { "voltage_l2" }) Integer grid_voltage_s; public @Nullable @SerializedName(value = "pvgridvoltage3", alternate = { "voltage_l3" }) Integer grid_voltage_t; @@ -74,12 +74,12 @@ public static String getFieldName(String channelId) { // solar AC mains power public @Nullable @SerializedName(value = "pvgridcurrent", alternate = { "OP_Curr", "Inv_Curr", "Current_l1" }) Integer inverter_current_r; - public @Nullable @SerializedName(value = "pvgridcurrent2", alternate = { "Current_l2"}) Integer inverter_current_s; - public @Nullable @SerializedName(value = "pvgridcurrent3", alternate = { "Current_l3"}) Integer inverter_current_t; + public @Nullable @SerializedName(value = "pvgridcurrent2", alternate = { "Current_l2" }) Integer inverter_current_s; + public @Nullable @SerializedName(value = "pvgridcurrent3", alternate = { "Current_l3" }) Integer inverter_current_t; - public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt", "AC_InWatt", "act_power_l1" }) Integer inverter_power_r; - public @Nullable @SerializedName(value = "pvgridpower2", alternate = { "act_power_l2" }) Integer inverter_power_s; - public @Nullable @SerializedName(value = "pvgridpower3", alternate = { "act_power_l3" }) Integer inverter_power_t; + public @Nullable @SerializedName(value = "pvgridpower", alternate = { "op_watt", "AC_InWatt" }) Integer inverter_power_r; + public @Nullable @SerializedName(value = "pvgridpower2") Integer inverter_power_s; + public @Nullable @SerializedName(value = "pvgridpower3") Integer inverter_power_t; public @Nullable @SerializedName(value = "op_va", alternate = { "AC_InVA" }) Integer inverter_va; @@ -98,10 +98,10 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "pactogridt") Integer export_power_t; // power imported from utility company - public @Nullable @SerializedName(value = "pactousertot", alternate = { "ptousertotal" }) Integer import_power; - public @Nullable @SerializedName(value = "pactouserr") Integer import_power_r; - public @Nullable @SerializedName(value = "pactousers") Integer import_power_s; - public @Nullable @SerializedName(value = "pactousert") Integer import_power_t; + public @Nullable @SerializedName(value = "pactousertot", alternate = { "ptousertotal", "pos_rev_act_power" }) Integer import_power; + public @Nullable @SerializedName(value = "pactouserr", alternate = { "act_power_l1" }) Integer import_power_r; + public @Nullable @SerializedName(value = "pactousers", alternate = { "act_power_l2" }) Integer import_power_s; + public @Nullable @SerializedName(value = "pactousert", alternate = { "act_power_l3" }) Integer import_power_t; // power delivered to internal load public @Nullable @SerializedName(value = "plocaloadtot", alternate = { "ptoloadtotal" }) Integer load_power; @@ -124,23 +124,23 @@ public static String getFieldName(String channelId) { // energy exported to utility company public @Nullable @SerializedName(value = "etogrid_tod", alternate = { "etogridtoday" }) Integer export_energy_today; - public @Nullable @SerializedName(value = "etogrid_tot", alternate = { "etogridtotal" }) Integer export_energy_total; + public @Nullable @SerializedName(value = "etogrid_tot", alternate = { "etogridtotal", "rev_act_energy" }) Integer export_energy_total; // energy imported from utility company public @Nullable @SerializedName(value = "etouser_tod", alternate = { "etousertoday" }) Integer import_energy_today; - public @Nullable @SerializedName(value = "etouser_tot", alternate = { "etousertotal" }) Integer import_energy_total; + public @Nullable @SerializedName(value = "etouser_tot", alternate = { "etousertotal", "pos_act_energy" }) Integer import_energy_total; // energy supplied to local load public @Nullable @SerializedName(value = "elocalload_tod", alternate = { "eloadtoday" }) Integer load_energy_today; public @Nullable @SerializedName(value = "elocalload_tot", alternate = { "eloadtotal" }) Integer load_energy_total; // charging energy from import - public @Nullable @SerializedName(value = "eharge1_tod", alternate = { "echrtoday" }) Integer import_charge_energy_today; - public @Nullable @SerializedName(value = "eharge1_tot", alternate = { "echrtotal" }) Integer import_charge_energy_total; + public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday", "eacchrtoday" }) Integer import_charge_energy_today; + public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal", "eacchrtotal" }) Integer import_charge_energy_total; // charging energy from solar - public @Nullable @SerializedName(value = "eacharge_today", alternate = { "eacCharToday", "eacchrtoday" }) Integer inverter_charge_energy_today; - public @Nullable @SerializedName(value = "eacharge_total", alternate = { "eacCharTotal", "eacchrtotal" }) Integer inverter_charge_energy_total; + public @Nullable @SerializedName(value = "eharge1_tod", alternate = { "echrtoday" }) Integer inverter_charge_energy_today; + public @Nullable @SerializedName(value = "eharge1_tot", alternate = { "echrtotal" }) Integer inverter_charge_energy_total; // discharging energy public @Nullable @SerializedName(value = "edischarge1_tod", alternate = { "eacDischarToday", "ebatDischarToday", "edischrtoday" }) Integer discharge_energy_today; diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 29cf38231d2cc..06ddbc55cc6f6 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -368,11 +368,12 @@ void testMeterGrottValuesContents() throws FileNotFoundException, IOException, N Map> channelStates = grottValues.getChannelStates(); assertNotNull(channelStates); - assertEquals(13, channelStates.size()); + assertEquals(15, channelStates.size()); - assertEquals(QuantityType.valueOf(171.0, Units.WATT), channelStates.get("inverter-power-s")); + assertEquals(QuantityType.valueOf(809.8, Units.WATT), channelStates.get("import-power")); + assertEquals(QuantityType.valueOf(171.0, Units.WATT), channelStates.get("import-power-s")); assertEquals(QuantityType.valueOf(237.4, Units.VOLT), channelStates.get("grid-voltage-s")); - assertEquals(QuantityType.valueOf(1.5, Units.AMPERE), channelStates.get("inverter-current-s")); assertEquals(QuantityType.valueOf(409.5, Units.VOLT), channelStates.get("grid-voltage-rs")); + assertEquals(QuantityType.valueOf(1.5, Units.AMPERE), channelStates.get("inverter-current-s")); } } From e5b7b40a0e2497d08dd4843b1af66d1bb56c5835 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 30 Dec 2023 15:23:16 +0000 Subject: [PATCH 133/146] Improve logging; Tweak synonyms Signed-off-by: Andrew Fiddian-Green --- .../binding/growatt/internal/cloud/GrowattCloud.java | 12 ++++++++++-- .../binding/growatt/internal/dto/GrottValues.java | 10 +++++----- .../openhab/binding/growatt/test/GrowattTest.java | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index 286c3c8f5d092..040af3ba02c63 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -189,6 +189,10 @@ private Map doHttpRequestInner(HttpMethod method, String en request.content(new FormContentProvider(fields), FORM_CONTENT); } + if (logger.isTraceEnabled()) { + logger.trace("{} {} {} params={} fields={}", method, endPoint, request.getVersion(), params, fields); + } + ContentResponse response; try { response = request.send(); @@ -197,16 +201,20 @@ private Map doHttpRequestInner(HttpMethod method, String en } int status = response.getStatus(); + String content = response.getContentAsString(); + + if (logger.isTraceEnabled()) { + logger.trace("HTTP {} {} content:{}", status, HttpStatus.getMessage(status), content); + } + if (status != HttpStatus.OK_200) { throw new GrowattApiException(String.format("HTTP %d %s", status, HttpStatus.getMessage(status))); } - String content = response.getContentAsString(); if (content == null || content.isBlank()) { throw new GrowattApiException("HTTP response content is " + (content == null ? "null" : "blank")); } - logger.trace("doHttpRequestInner() response:{}", content); try { JsonElement jsonObject = JsonParser.parseString(content).getAsJsonObject(); if (jsonObject instanceof JsonObject jsonElement) { diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 756d00329a776..c6fc67e69313a 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -84,8 +84,8 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "op_va", alternate = { "AC_InVA" }) Integer inverter_va; // battery discharge / charge power - public @Nullable @SerializedName(value = "p1charge1", alternate = { "acchr_watt", "BatWatt" }) Integer charge_power; - public @Nullable @SerializedName(value = "pdischarge1", alternate = { "ACDischarWatt", "BatDischarWatt" }) Integer discharge_power; + public @Nullable @SerializedName(value = "p1charge1", alternate = { "acchr_watt", "BatWatt", "bdc1_pchr" }) Integer charge_power; + public @Nullable @SerializedName(value = "pdischarge1", alternate = { "ACDischarWatt", "BatDischarWatt", "bdc1_pdischr" }) Integer discharge_power; // miscellaneous battery public @Nullable @SerializedName(value = "ACCharCurr") Integer charge_current; @@ -109,7 +109,7 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "plocaloads") Integer load_power_s; public @Nullable @SerializedName(value = "plocaloadt") Integer load_power_t; - // solar AC grid energy + // inverter AC energy public @Nullable @SerializedName(value = "eactoday", alternate = { "pvenergytoday" }) Integer inverter_energy_today; public @Nullable @SerializedName(value = "eactotal", alternate = { "pvenergytotal" }) Integer inverter_energy_total; @@ -163,9 +163,9 @@ public static String getFieldName(String channelId) { // battery data public @Nullable @SerializedName(value = "batterytype") Integer battery_type; - public @Nullable @SerializedName(value = "batttemp", alternate = { "bms_batterytemp" }) Integer battery_temperature; + public @Nullable @SerializedName(value = "batttemp", alternate = { "bdc1_tempa" }) Integer battery_temperature; public @Nullable @SerializedName(value = "vbat", alternate = { "uwBatVolt_DSP", "bat_Volt", "bms_batteryvolt" }) Integer battery_voltage; - public @Nullable @SerializedName(value = "bat_dsp", alternate = { "bms_status" }) Integer battery_display; + public @Nullable @SerializedName(value = "bat_dsp") Integer battery_display; public @Nullable @SerializedName(value = "SOC", alternate = { "batterySOC", "bms_soc" }) Integer battery_soc; // fault codes diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 06ddbc55cc6f6..a1159e765bfdd 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -351,7 +351,7 @@ void testThreePhaseGrottValuesContents() throws FileNotFoundException, IOExcepti Map> channelStates = grottValues.getChannelStates(); assertNotNull(channelStates); - assertEquals(63, channelStates.size()); + assertEquals(64, channelStates.size()); assertEquals(QuantityType.valueOf(-36.5, Units.WATT), channelStates.get("inverter-power")); assertEquals(QuantityType.valueOf(11, Units.PERCENT), channelStates.get("battery-soc")); From 3b58ba10c127da1b45da7e3bfa8046148094b09a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 5 Jan 2024 18:44:55 +0000 Subject: [PATCH 134/146] [growatt] implement tlx inverter (wip) Signed-off-by: Andrew Fiddian-Green --- .../internal/action/GrowattActions.java | 48 +- .../growatt/internal/cloud/GrowattCloud.java | 699 +++++++++++++----- .../config/GrowattBridgeConfiguration.java | 30 + .../config/GrowattInverterConfiguration.java | 14 - .../growatt/internal/dto/GrowattDevice.java | 38 + .../growatt/internal/dto/GrowattPlant.java | 38 + .../internal/dto/GrowattPlantList.java | 46 ++ .../growatt/internal/dto/GrowattUser.java | 32 + .../factory/GrowattHandlerFactory.java | 4 +- .../handler/GrowattBridgeHandler.java | 25 +- .../handler/GrowattInverterHandler.java | 64 +- .../resources/OH-INF/i18n/growatt.properties | 31 +- .../resources/OH-INF/thing/thing-types.xml | 34 +- .../binding/growatt/test/GrowattTest.java | 64 +- 14 files changed, 835 insertions(+), 332 deletions(-) create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java index 674e79130faab..1fdafe4274093 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java @@ -35,20 +35,11 @@ public class GrowattActions implements ThingActions { private final Logger logger = LoggerFactory.getLogger(GrowattActions.class); private @Nullable GrowattInverterHandler handler; - public static void setupChargingProgram(ThingActions actions, Number chargingPower, Number targetSOC, - boolean allowAcCharging, String startTime, String stopTime, boolean programEnable) { + public static void setupBatteryProgram(ThingActions actions, Integer programMode, Integer powerLevel, + Integer stopSOC, Boolean enableAcCharging, String startTime, String stopTime, Boolean enableProgram) { if (actions instanceof GrowattActions growattActions) { - growattActions.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, - programEnable); - } else { - throw new IllegalArgumentException("The 'actions' argument is not an instance of GrowattActions"); - } - } - - public static void setupDischargingProgram(ThingActions actions, Number dischargingPower, Number targetSOC, - String startTime, String stopTime, boolean programEnable) { - if (actions instanceof GrowattActions growattActions) { - growattActions.setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, programEnable); + growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, + enableProgram); } else { throw new IllegalArgumentException("The 'actions' argument is not an instance of GrowattActions"); } @@ -64,32 +55,19 @@ public void setThingHandler(@Nullable ThingHandler handler) { this.handler = (handler instanceof GrowattInverterHandler growattHandler) ? growattHandler : null; } - @RuleAction(label = "@text/actions.charging.label", description = "@text/actions.charging.description") - public void setupChargingProgram( - @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") Number chargingPower, - @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") Number targetSOC, - @ActionInput(name = "allow-ac-charging", label = "@text/actions.allow-ac-charging.label", description = "@text/actions.allow-ac-charging.description") boolean allowAcCharging, - @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, - @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, - @ActionInput(name = "program-enable", label = "@text/actions.program-enable.label", description = "@text/actions.program-enable.description") boolean programEnable) { - GrowattInverterHandler handler = this.handler; - if (handler != null) { - handler.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable); - } else { - logger.warn("ThingHandler is null."); - } - } - - @RuleAction(label = "@text/actions.discharging.label", description = "@text/actions.discharging.description") - public void setupDischargingProgram( - @ActionInput(name = "charging-power", label = "@text/actions.charging-power.label", description = "@text/actions.charging-power.description") Number dischargingPower, - @ActionInput(name = "target-soc", label = "@text/actions.target-soc.label", description = "@text/actions.target-soc.description") Number targetSOC, + @RuleAction(label = "@text/actions.battery-program.label", description = "@text/actions.battery-program.description") + public void setupBatteryProgram( + @ActionInput(name = "program-mode", label = "@text/actions.program-mode.label", description = "@text/actions.program-mode.description") Integer programMode, + @ActionInput(name = "power-level", label = "@text/actions.power-level.label", description = "@text/actions.power-level.description") Integer powerLevel, + @ActionInput(name = "stop-soc", label = "@text/actions.stop-soc.label", description = "@text/actions.stop-soc.description") Integer stopSOC, + @ActionInput(name = "enable-ac-charging", label = "@text/actions.enable-ac-charging.label", description = "@text/actions.enable-ac-charging.description") Boolean enableAcCharging, @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, - @ActionInput(name = "program-enable", label = "@text/actions.program-enable.label", description = "@text/actions.program-enable.description") boolean programEnable) { + @ActionInput(name = "enable-program", label = "@text/actions.enable-program.label", description = "@text/actions.enable-program.description") Boolean enableProgram) { GrowattInverterHandler handler = this.handler; if (handler != null) { - handler.setupDischargingProgram(dischargingPower, targetSOC, startTime, stopTime, programEnable); + handler.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, + enableProgram); } else { logger.warn("ThingHandler is null."); } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index 040af3ba02c63..dea8d89387f07 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -12,16 +12,20 @@ */ package org.openhab.binding.growatt.internal.cloud; +import java.lang.reflect.Type; import java.net.HttpCookie; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.DateTimeException; import java.time.Duration; import java.time.LocalTime; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -36,16 +40,23 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; +import org.openhab.binding.growatt.internal.config.GrowattBridgeConfiguration; +import org.openhab.binding.growatt.internal.dto.GrowattDevice; +import org.openhab.binding.growatt.internal.dto.GrowattPlant; +import org.openhab.binding.growatt.internal.dto.GrowattPlantList; +import org.openhab.binding.growatt.internal.dto.GrowattUser; import org.openhab.core.io.net.http.HttpClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; /** * The {@link GrowattCloud} class allows the binding to access the inverter state and settings via HTTP calls to the @@ -78,30 +89,94 @@ public class GrowattCloud implements AutoCloseable { private static final String SERVER_URL = "https://server-api.growatt.com/"; // API end points - private static final String LOGIN_API = "newTwoLoginAPI.do"; - private static final String PLANT_LIST_API = "PlantListAPI.do"; - private static final String PLANT_INFO_API = "newTwoPlantAPI.do"; - private static final String NEW_TCP_SET_API = "newTcpsetAPI.do"; - private static final String NEW_MIX_API = "newMixApi.do"; + private static final String LOGIN_API_ENDPOINT = "newTwoLoginAPI.do"; + private static final String PLANT_LIST_API_ENDPOINT = "PlantListAPI.do"; + private static final String PLANT_INFO_API_ENDPOINT = "newTwoPlantAPI.do"; + private static final String NEW_TCP_SET_API_ENDPOINT = "newTcpsetAPI.do"; + + // command operations + private static final String OP_GET_ALL_DEVICE_LIST = "getAllDeviceList"; + + // enum of device types + private static enum DeviceType { + MIX, + MAX, + MIN, + SPA, + SPH, + TLX + } + + /* + * Map of device types vs. field parameters for GET commands from FMT_API_ENDPOINT's. + * Note: some values are guesses which have not yet been confirmed by users + */ + private static final Map SUPPORTED_TYPES_GET_PARAM = Map.of( + // @formatter:off + DeviceType.MIX, "getMixSetParams", + DeviceType.MAX, "getMaxSetData", + DeviceType.MIN, "getMinSetData", + DeviceType.SPA, "getSpaSetData", + DeviceType.SPH, "getSphSetData", + DeviceType.TLX, "getTlxSetData" + // @formatter:on + ); + + /* + * Map of device types vs. field parameters for POST commands to NEW_TCP_SET_API_ENDPOINT. + * Note: some values are guesses which have not yet been confirmed by users + */ + private static final Map SUPPORTED_TYPE_POST_PARAM = Map.of( + // @formatter:off + DeviceType.MIX, "mixSetApiNew", // was "mixSetApi" + DeviceType.MAX, "maxSetApi", + DeviceType.MIN, "minSetApi", + DeviceType.SPA, "spaSetApi", + DeviceType.SPH, "sphSet", + DeviceType.TLX, "tlxSet" + // @formatter:on + ); + + // enum to select charge resp. discharge program + private static enum ProgramType { + CHARGE, + DISCHARGE + } + + // enum of program modes + public static enum ProgramMode { + LOAD_FIRST, + BATTERY_FIRST, + GRID_FIRST + } + + // @formatter:off + private static final Type DEVICE_LIST_TYPE = new TypeToken>() {}.getType(); + // @formatter:on // HTTP headers (user agent is spoofed to mimic the Growatt Android Shine app) private static final String USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 12; https://www.openhab.org)"; private static final String FORM_CONTENT = "application/x-www-form-urlencoded"; - private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(5); + private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(10); private final Logger logger = LoggerFactory.getLogger(GrowattCloud.class); private final HttpClient httpClient; - private final GrowattInverterConfiguration configuration; + private final GrowattBridgeConfiguration configuration; + private final Gson gson = new Gson(); + private final List plantIds = new ArrayList<>(); + private final Map deviceIdTypeMap = new ConcurrentHashMap<>(); + + private String userId = ""; /** * Constructor. * - * @param configuration the thing configuration parameters. + * @param configuration the bridge configuration parameters. * @param httpClientFactory the OH core {@link HttpClientFactory} instance. * @throws Exception if anything goes wrong. */ - public GrowattCloud(GrowattInverterConfiguration configuration, HttpClientFactory httpClientFactory) + public GrowattCloud(GrowattBridgeConfiguration configuration, HttpClientFactory httpClientFactory) throws Exception { this.configuration = configuration; this.httpClient = httpClientFactory.createHttpClient("growatt-cloud-api", new SslContextFactory.Client(true)); @@ -140,11 +215,22 @@ private static String createHash(String password) throws GrowattApiException { return result.toString(); } + /** + * Refresh the login cookies. + * + * @throws GrowattApiException if any error occurs. + */ + private void refreshCookies() throws GrowattApiException { + List cookies = httpClient.getCookieStore().getCookies(); + if (cookies.isEmpty() || cookies.stream().anyMatch(HttpCookie::hasExpired)) { + postLoginCredentials(); + } + } + /** * Login to the server (if necessary) and then execute an HTTP request using the given HTTP method, to the given end - * point, and with the given request URL parameters and/or request form fields. If there are no existing cookies for - * this server, or if any of the cookies has expired, then first login to the server before making the actual HTTP - * request. + * point, and with the given request URL parameters and/or request form fields. If the cookies are not valid first + * login to the server before making the actual HTTP request. * * @param method the HTTP method to use. * @param endPoint the API end point. @@ -155,12 +241,7 @@ private static String createHash(String password) throws GrowattApiException { */ private Map doHttpRequest(HttpMethod method, String endPoint, @Nullable Map params, @Nullable Fields fields) throws GrowattApiException { - // - List cookies = httpClient.getCookieStore().getCookies(); - if (cookies.isEmpty() || cookies.stream().anyMatch(HttpCookie::hasExpired)) { - postLoginCredentials(); - } - + refreshCookies(); return doHttpRequestInner(method, endPoint, params, fields); } @@ -190,7 +271,8 @@ private Map doHttpRequestInner(HttpMethod method, String en } if (logger.isTraceEnabled()) { - logger.trace("{} {} {} params={} fields={}", method, endPoint, request.getVersion(), params, fields); + logger.trace("{} {}{} {} {}", method, request.getPath(), params == null ? "" : "?" + request.getQuery(), + request.getVersion(), fields == null ? "" : "? " + FormContentProvider.convert(fields)); } ContentResponse response; @@ -203,16 +285,19 @@ private Map doHttpRequestInner(HttpMethod method, String en int status = response.getStatus(); String content = response.getContentAsString(); - if (logger.isTraceEnabled()) { - logger.trace("HTTP {} {} content:{}", status, HttpStatus.getMessage(status), content); - } + logger.trace("HTTP {} {} {}", status, HttpStatus.getMessage(status), content); if (status != HttpStatus.OK_200) { throw new GrowattApiException(String.format("HTTP %d %s", status, HttpStatus.getMessage(status))); } if (content == null || content.isBlank()) { - throw new GrowattApiException("HTTP response content is " + (content == null ? "null" : "blank")); + throw new GrowattApiException("Response is " + (content == null ? "null" : "blank")); + } + + if (content.contains("")) { + logger.warn("HTTP {} {} {}", status, HttpStatus.getMessage(status), content); + throw new GrowattApiException("Response is HTML"); } try { @@ -220,31 +305,72 @@ private Map doHttpRequestInner(HttpMethod method, String en if (jsonObject instanceof JsonObject jsonElement) { return jsonElement.asMap(); } - throw new GrowattApiException("JSON invalid response"); - } catch (JsonParseException | IllegalStateException e) { - throw new GrowattApiException("JSON parse error", e); + throw new GrowattApiException("Response JSON invalid"); + } catch (JsonSyntaxException | IllegalStateException e) { + throw new GrowattApiException("Response JSON syntax exception", e); + } + } + + /** + * Get the deviceType for the given deviceId. If the deviceIdTypeMap is empty then download it freshly. + * + * @param the deviceId to get. + * @return the deviceType. + * @throws GrowattApiException if any error occurs. + */ + private DeviceType getDeviceTypeChecked(String deviceId) throws GrowattApiException { + if (deviceIdTypeMap.isEmpty()) { + if (plantIds.isEmpty()) { + refreshCookies(); + } + for (String plantId : plantIds) { + for (GrowattDevice device : getPlantInfo(plantId)) { + try { + DeviceType deviceType = DeviceType.valueOf(device.getType().toUpperCase()); + deviceIdTypeMap.put(deviceId, deviceType); + } catch (IllegalArgumentException e) { + // ignore unsupported device types + } + } + } + logger.debug("Downloaded deviceTypes:{}", deviceIdTypeMap); + } + DeviceType deviceType = deviceIdTypeMap.get(deviceId); + if (deviceType != null) { + return deviceType; } + throw new GrowattApiException("Unsupported device:" + deviceId); } /** - * Get all of the mix inverter settings. + * Get the inverter device settings. * + * @param the deviceId to get. * @return a Map of JSON elements containing the server response. * @throws GrowattApiException if any error occurs. */ - public Map getMixAllSettings() throws GrowattApiException { + public Map getDeviceSettings(String deviceId) throws GrowattApiException { + DeviceType deviceType = getDeviceTypeChecked(deviceId); + String dt = deviceType.name().toLowerCase(); + + String endPoint = String.format("new%sApi.do", dt.substring(0, 1).toUpperCase() + dt.substring(1)); + Map params = new LinkedHashMap<>(); // keep params in order - params.put("op", "getMixSetParams"); - params.put("serialNum", configuration.deviceId); + params.put("op", Objects.requireNonNull(SUPPORTED_TYPES_GET_PARAM.get(deviceType))); + params.put("serialNum", deviceId); params.put("kind", "0"); - Map result = doHttpRequest(HttpMethod.GET, NEW_MIX_API, params, null); + Map result = doHttpRequest(HttpMethod.GET, endPoint, params, null); JsonElement obj = result.get("obj"); if (obj instanceof JsonObject object) { - JsonElement mixBean = object.get("mixBean"); - if (mixBean instanceof JsonObject mixBeanObject) { - return mixBeanObject.asMap(); + Map map = object.asMap(); + Optional key = map.keySet().stream().filter(k -> k.toLowerCase().endsWith("bean")).findFirst(); + if (key.isPresent()) { + JsonElement beanJson = map.get(key.get()); + if (beanJson instanceof JsonObject bean) { + return bean.asMap(); + } } } throw new GrowattApiException("Invalid JSON response"); @@ -252,53 +378,56 @@ public Map getMixAllSettings() throws GrowattApiException { /** * Get the plant information. - *

- * This method is not currently used, but is included as a Java template for future implementations if needed. - * See https://github.com/indykoning/PyPi_GrowattServer/blob/master/growattServer/__init__.py * - * @return a Map of JSON elements containing the server response. + * @param the plantId to get. + * @return a list of {@link GrowattDevice} containing the server response. * @throws GrowattApiException if any error occurs. */ - public Map getPlantInfo() throws GrowattApiException { - if (configuration.plantId == null) { - throw new GrowattApiException("Plant Id missing"); - } - + public List getPlantInfo(String plantId) throws GrowattApiException { Map params = new LinkedHashMap<>(); // keep params in order - params.put("op", "getAllDeviceList"); - params.put("plantId", Objects.requireNonNull(configuration.plantId)); + params.put("op", OP_GET_ALL_DEVICE_LIST); + params.put("plantId", plantId); params.put("pageNum", "1"); params.put("pageSize", "1"); - return doHttpRequest(HttpMethod.GET, PLANT_INFO_API, params, null); + Map result = doHttpRequest(HttpMethod.GET, PLANT_INFO_API_ENDPOINT, params, null); + + JsonElement deviceList = result.get("deviceList"); + if (deviceList instanceof JsonArray deviceArray) { + try { + List devices = gson.fromJson(deviceArray, DEVICE_LIST_TYPE); + if (devices != null) { + return devices; + } + } catch (JsonSyntaxException e) { + // fall through + } + } + throw new GrowattApiException("Invalid JSON response"); } /** * Get the plant list. - *

- * This method is not currently used, but is included as a Java template for future implementations if needed. - * See https://github.com/indykoning/PyPi_GrowattServer/blob/master/growattServer/__init__.py * - * @return a Map of JSON elements containing the server response. + * @param the userId to get from. + * @return a {@link GrowattPlantList} containing the server response. * @throws GrowattApiException if any error occurs. */ - public Map getPlantList() throws GrowattApiException { - if (configuration.userId == null) { - throw new GrowattApiException("User Id missing"); - } - + public GrowattPlantList getPlantList(String userId) throws GrowattApiException { Map params = new LinkedHashMap<>(); // keep params in order - params.put("userId", Objects.requireNonNull(configuration.userId)); + params.put("userId", userId); - Map result = doHttpRequest(HttpMethod.GET, PLANT_LIST_API, params, null); + Map result = doHttpRequest(HttpMethod.GET, PLANT_LIST_API_ENDPOINT, params, null); JsonElement back = result.get("back"); if (back instanceof JsonObject backObject) { - JsonElement success = backObject.get("success"); - if (success instanceof JsonPrimitive successPrimitive) { - if (successPrimitive.getAsBoolean()) { - return backObject.asMap(); + try { + GrowattPlantList plantList = gson.fromJson(backObject, GrowattPlantList.class); + if (plantList != null && plantList.getSuccess()) { + return plantList; } + } catch (JsonSyntaxException e) { + // fall through } } throw new GrowattApiException("Invalid JSON response"); @@ -307,141 +436,98 @@ public Map getPlantList() throws GrowattApiException { /** * Attempt to login to the remote server by posting the given user credentials. * - * @return a Map of JSON elements containing the server response. * @throws GrowattApiException if any error occurs. */ - private Map postLoginCredentials() throws GrowattApiException { - if (configuration.userName == null) { + private void postLoginCredentials() throws GrowattApiException { + if (configuration.userName.isBlank()) { throw new GrowattApiException("User name missing"); } - if (configuration.password == null) { + if (configuration.password.isBlank()) { throw new GrowattApiException("Password missing"); } Fields fields = new Fields(); - fields.put("userName", Objects.requireNonNull(configuration.userName)); - fields.put("password", createHash(Objects.requireNonNull(configuration.password))); + fields.put("userName", configuration.userName); + fields.put("password", createHash(configuration.password)); - Map result = doHttpRequestInner(HttpMethod.POST, LOGIN_API, null, fields); + Map result = doHttpRequestInner(HttpMethod.POST, LOGIN_API_ENDPOINT, null, fields); JsonElement back = result.get("back"); if (back instanceof JsonObject backObject) { - JsonElement success = backObject.get("success"); - if (success instanceof JsonPrimitive successPrimitive) { - if (successPrimitive.getAsBoolean()) { - return backObject.asMap(); + try { + GrowattPlantList plantList = gson.fromJson(backObject, GrowattPlantList.class); + if (plantList != null && plantList.getSuccess()) { + GrowattUser user = plantList.getUserId(); + userId = user != null ? user.getId() : userId; + plantIds.clear(); + plantIds.addAll(plantList.getPlants().stream().map(GrowattPlant::getId).toList()); + logger.debug("Logged in userId:{}, plantIds:{}", userId, plantIds); + return; } + } catch (JsonSyntaxException e) { + // fall through } } throw new GrowattApiException("Login failed"); } /** - * Post a command to setup the mix inverter battery charging program. + * Post a command to setup the inverter battery charging program. + * + * @param the deviceId to set up + * @param programModeIndex index of the type of program Load First (0) / Battery First (1) / Grid First (2) + * @param powerLevel the rate of charging / discharging + * @param stopSOC the SOC at which to stop charging / discharging + * @param enableAcCharging allow charging from AC power + * @param startTime the start time of the charging / discharging program + * @param stopTime the stop time of the charging / discharging program + * @param enableProgram charging / discharging program shall be enabled * - * @param chargingPower the rate of charging 0%..100% - * @param targetSOC the SOC at which to stop charging 0%..100% - * @param allowAcCharging allow the battery to be charged from AC power - * @param startTime the start time of the charging program - * @param stopTime the stop time of the charging program - * @param programEnable charge program shall be enabled - * @return a Map of JSON elements containing the server response * @throws GrowattApiException if any error occurs */ - public Map setupChargingProgram(int chargingPower, int targetSOC, boolean allowAcCharging, - LocalTime startTime, LocalTime stopTime, boolean programEnable) throws GrowattApiException { - if (chargingPower < 1 || chargingPower > 100) { - throw new GrowattApiException("Charge power out of range (1%..100%)"); - } - if (targetSOC < 1 || targetSOC > 100) { - throw new GrowattApiException("Target SOC out of range (1%..100%)"); + public void setupBatteryProgram(String deviceId, int programModeIndex, @Nullable Integer powerLevel, + @Nullable Integer stopSOC, @Nullable Boolean enableAcCharging, @Nullable String startTime, + @Nullable String stopTime, @Nullable Boolean enableProgram) throws GrowattApiException { + // + if (deviceId.isBlank()) { + throw new GrowattApiException("Device id is blank"); } - Fields fields = new Fields(); - fields.put("op", "mixSetApiNew"); - fields.put("serialNum", configuration.deviceId); - fields.put("type", "mix_ac_charge_time_period"); - fields.put("param1", String.format("%d", chargingPower)); - fields.put("param2", String.format("%d", targetSOC)); - fields.put("param3", allowAcCharging ? "1" : "0"); - fields.put("param4", String.format("%02d", startTime.getHour())); - fields.put("param5", String.format("%02d", startTime.getMinute())); - fields.put("param6", String.format("%02d", stopTime.getHour())); - fields.put("param7", String.format("%02d", stopTime.getMinute())); - fields.put("param8", programEnable ? "1" : "0"); - fields.put("param9", "00"); - fields.put("param10", "00"); - fields.put("param11", "00"); - fields.put("param12", "00"); - fields.put("param13", "0"); - fields.put("param14", "00"); - fields.put("param15", "00"); - fields.put("param16", "00"); - fields.put("param17", "00"); - fields.put("param18", "0"); - - Map result = doHttpRequest(HttpMethod.POST, NEW_TCP_SET_API, null, fields); - - JsonElement success = result.get("success"); - if (success instanceof JsonPrimitive sucessPrimitive) { - if (sucessPrimitive.getAsBoolean()) { - return result; - } + ProgramMode programMode; + try { + programMode = ProgramMode.values()[programModeIndex]; + } catch (IndexOutOfBoundsException e) { + throw new GrowattApiException("Program mode index is out of range (0..2)"); } - throw new GrowattApiException("Command failed"); - } - /** - * Post a command to setup the mix inverter battery discharge program. - * - * @param dischargingPower the rate of discharging 1%..100% - * @param targetSOC the SOC at which to stop charging 1%..100% - * @param startTime the start time of the discharging program - * @param stopTime the stop time of the discharging program - * @param programEnable discharge program shall be enabled - * @return a Map of JSON elements containing the server response - * @throws GrowattApiException if any error occurs - */ - public Map setupDischargingProgram(int dischargingPower, int targetSOC, LocalTime startTime, - LocalTime stopTime, boolean programEnable) throws GrowattApiException { - if (dischargingPower < 1 || dischargingPower > 100) { - throw new GrowattApiException("Discharge power out of range (1%..100%)"); - } - if (targetSOC < 1 || targetSOC > 100) { - throw new GrowattApiException("Target SOC out of range (1%..100%)"); - } + DeviceType deviceType = getDeviceTypeChecked(deviceId); + switch (deviceType) { - Fields fields = new Fields(); - fields.put("op", "mixSetApiNew"); - fields.put("serialNum", configuration.deviceId); - fields.put("type", "mix_ac_discharge_time_period"); - fields.put("param1", String.format("%d", dischargingPower)); - fields.put("param2", String.format("%d", targetSOC)); - fields.put("param3", String.format("%02d", startTime.getHour())); - fields.put("param4", String.format("%02d", startTime.getMinute())); - fields.put("param5", String.format("%02d", stopTime.getHour())); - fields.put("param6", String.format("%02d", stopTime.getMinute())); - fields.put("param7", programEnable ? "1" : "0"); - fields.put("param8", "00"); - fields.put("param9", "00"); - fields.put("param10", "00"); - fields.put("param11", "00"); - fields.put("param12", "0"); - fields.put("param13", "00"); - fields.put("param14", "00"); - fields.put("param15", "00"); - fields.put("param16", "00"); - fields.put("param17", "0"); - - Map result = doHttpRequest(HttpMethod.POST, NEW_TCP_SET_API, null, fields); + case MIX: + case SPA: + setTimeProgram(deviceId, deviceType, + programMode == ProgramMode.BATTERY_FIRST ? ProgramType.CHARGE : ProgramType.DISCHARGE, + powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram); + return; - JsonElement success = result.get("success"); - if (success instanceof JsonPrimitive sucessPrimitive) { - if (sucessPrimitive.getAsBoolean()) { - return result; - } + case TLX: + if (enableAcCharging != null) { + setEnableAcCharging(deviceId, deviceType, enableAcCharging); + } + if (powerLevel != null) { + setPowerLevel(deviceId, deviceType, programMode, powerLevel); + } + if (stopSOC != null) { + setStopSOC(deviceId, deviceType, programMode, stopSOC); + } + if (startTime != null || stopTime != null || enableProgram != null) { + setTimeSegment(deviceId, deviceType, programMode, startTime, stopTime, enableProgram); + } + return; + + default: } - throw new GrowattApiException("Command failed"); + throw new GrowattApiException("Unsupported device type:" + deviceType.name()); } /** @@ -534,4 +620,279 @@ public static LocalTime localTimeOf(String localTime) throws DateTimeException { throw new DateTimeException("LocalTime bad value", e); } } + + /** + * Post a command to set up the inverter battery charging / discharging program. + * + * @param the deviceId to set up + * @param the deviceType to set up + * @param programType selects whether the program is for charge or discharge + * @param powerLevel the rate of charging / discharging 1%..100% + * @param stopSOC the SOC at which to stop the program 5%..100% + * @param enableAcCharging allow charging from AC power (only applies to hybrid/mix inverters) + * @param startTime the start time of the program + * @param stopTime the stop time of the program + * @param enableProgram the program shall be enabled + * + * @throws GrowattApiException if any error occurs + */ + private void setTimeProgram(String deviceId, DeviceType deviceType, ProgramType programType, + @Nullable Integer powerLevel, @Nullable Integer stopSOC, @Nullable Boolean enableAcCharging, + @Nullable String startTime, @Nullable String stopTime, @Nullable Boolean enableProgram) + throws GrowattApiException { + // + if (powerLevel == null || powerLevel < 1 || powerLevel > 100) { + throw new GrowattApiException("Power level parameter is null or out of range (1%..100%)"); + } + if (stopSOC == null || stopSOC < 5 || stopSOC > 100) { + throw new GrowattApiException("Target SOC parameter is null out of range (5%..100%)"); + } + if (startTime == null) { + throw new GrowattApiException("Start time parameter is null"); + } + if (stopTime == null) { + throw new GrowattApiException("Stop time parameter is null"); + } + if (enableProgram == null) { + throw new GrowattApiException("Program enable parameter is null"); + } + boolean isMixChargeCommand = deviceType == DeviceType.MIX && programType == ProgramType.CHARGE; + if (isMixChargeCommand && enableAcCharging == null) { + throw new GrowattApiException("Allow ac charging parameter is null"); + } + LocalTime localStartTime; + try { + localStartTime = GrowattCloud.localTimeOf(startTime); + } catch (DateTimeException e) { + throw new GrowattApiException("Start time is invalid"); + } + LocalTime localStopTime; + try { + localStopTime = GrowattCloud.localTimeOf(stopTime); + } catch (DateTimeException e) { + throw new GrowattApiException("Stop time is invalid"); + } + + Fields fields = new Fields(); + + fields.put("op", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("serialNum", deviceId); + fields.put("type", String.format("%s_ac_%s_time_period", deviceType.name().toLowerCase(), + programType.name().toLowerCase())); + + int paramId = 1; + + paramId = addParam(fields, paramId, String.format("%d", powerLevel)); + paramId = addParam(fields, paramId, String.format("%d", stopSOC)); + if (isMixChargeCommand) { + paramId = addParam(fields, paramId, enableAcCharging ? "1" : "0"); + } + paramId = addParam(fields, paramId, String.format("%02d", localStartTime.getHour())); + paramId = addParam(fields, paramId, String.format("%02d", localStartTime.getMinute())); + paramId = addParam(fields, paramId, String.format("%02d", localStopTime.getHour())); + paramId = addParam(fields, paramId, String.format("%02d", localStopTime.getMinute())); + paramId = addParam(fields, paramId, enableProgram ? "1" : "0"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "0"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "00"); + paramId = addParam(fields, paramId, "0"); + + postSetCommandForm(fields); + } + + /** + * Add a new entry in the given {@link Fields} map in the form "paramN" = paramValue where N is the parameter index. + * + * @param fields the map to be added to. + * @param parameterIndex the parameter index. + * @param parameterValue the parameter value. + * + * @return the next parameter index. + */ + private int addParam(Fields fields, int parameterIndex, String parameterValue) { + fields.put(String.format("param%d", parameterIndex), parameterValue); + return parameterIndex + 1; + } + + /** + * Inner method to execute a POST setup command using the given form fields. + * + * @param fields the form fields to be posted. + * + * @throws GrowattApiException if any error occurs + */ + private void postSetCommandForm(Fields fields) throws GrowattApiException { + Map result = doHttpRequest(HttpMethod.POST, NEW_TCP_SET_API_ENDPOINT, null, fields); + JsonElement success = result.get("success"); + if (success instanceof JsonPrimitive sucessPrimitive) { + if (sucessPrimitive.getAsBoolean()) { + return; + } + } + throw new GrowattApiException("Command failed"); + } + + /** + * Post a command to enable / disable ac charging. + * + * @param the deviceId to set up + * @param the deviceType to set up + * @param enableAcCharging enable or disable the function + * + * @throws GrowattApiException if any error occurs + */ + private void setEnableAcCharging(String deviceId, DeviceType deviceType, boolean enableAcCharging) + throws GrowattApiException { + // + Fields fields = new Fields(); + + fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("serialNum", deviceId); + fields.put("type", "ac_charge"); + fields.put("param1", enableAcCharging ? "1" : "0"); + + postSetCommandForm(fields); + } + + /** + * Post a command to set up a program charge / discharge power level. + * + * @param the deviceId to set up + * @param the deviceType to set up + * @param programMode the program mode that the setting shall apply to + * @param powerLevel the rate of charging / discharging 1%..100% + * + * @throws GrowattApiException if any error occurs + */ + private void setPowerLevel(String deviceId, DeviceType deviceType, ProgramMode programMode, int powerLevel) + throws GrowattApiException { + // + if (powerLevel < 1 || powerLevel > 100) { + throw new GrowattApiException("Power level out of range (1%..100%)"); + } + + String typeParam; + switch (programMode) { + case BATTERY_FIRST: + typeParam = "charge_power"; + break; + case GRID_FIRST: + case LOAD_FIRST: + typeParam = "discharge_power"; + break; + default: + throw new GrowattApiException("Unexpected exception"); + } + + Fields fields = new Fields(); + + fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("serialNum", deviceId); + fields.put("type", typeParam); + fields.put("param1", String.format("%d", powerLevel)); + + postSetCommandForm(fields); + } + + /** + * Post a command to set up a program target (stop) SOC level. + * + * @param the deviceId to set up + * @param the deviceType to set up + * @param programMode the program mode that the setting shall apply to + * @param stopSOC the SOC at which to stop the program 11%..100% + * + * @throws GrowattApiException if any error occurs + */ + private void setStopSOC(String deviceId, DeviceType deviceType, ProgramMode programMode, int stopSOC) + throws GrowattApiException { + // + if (stopSOC < 11 || stopSOC > 100) { + throw new GrowattApiException("Target SOC out of range (11%..100%)"); + } + + String typeParam; + switch (programMode) { + case BATTERY_FIRST: + typeParam = "charge_stop_soc"; + break; + case GRID_FIRST: + typeParam = "on-grid-discharge_stop_soc"; + break; + case LOAD_FIRST: + typeParam = "discharge_stop_soc"; + break; + default: + throw new GrowattApiException("Unexpected exception"); + } + + Fields fields = new Fields(); + + fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("serialNum", deviceId); + fields.put("type", typeParam); + fields.put("param1", String.format("%d", stopSOC)); + + postSetCommandForm(fields); + } + + /** + * Post a command to set up a time segment program. + * Note: uses separate dedicated time segments for Load First, Battery First, Grid First modes. + * + * @param the deviceId to set up + * @param the deviceType to set up + * @param programMode the program mode for the time segment + * @param startTime the start time of the program + * @param stopTime the stop time of the program + * @param enableProgram the program shall be enabled + * + * @throws GrowattApiException if any error occurs + */ + private void setTimeSegment(String deviceId, DeviceType deviceType, ProgramMode programMode, + @Nullable String startTime, @Nullable String stopTime, @Nullable Boolean enableProgram) + throws GrowattApiException { + // + if (startTime == null) { + throw new GrowattApiException("Start time parameter is null"); + } + if (stopTime == null) { + throw new GrowattApiException("Stop time parameter is null"); + } + if (enableProgram == null) { + throw new GrowattApiException("Program enable parameter is null"); + } + LocalTime localStartTime; + try { + localStartTime = GrowattCloud.localTimeOf(startTime); + } catch (DateTimeException e) { + throw new GrowattApiException("Start time is invalid"); + } + LocalTime localStopTime; + try { + localStopTime = GrowattCloud.localTimeOf(stopTime); + } catch (DateTimeException e) { + throw new GrowattApiException("Stop time is invalid"); + } + + Fields fields = new Fields(); + + fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("serialNum", deviceId); + fields.put("type", String.format("time_segment%d", programMode.ordinal() + 1)); + fields.put("param1", String.format("%d", programMode.ordinal())); + fields.put("param2", String.format("%02d", localStartTime.getHour())); + fields.put("param3", String.format("%02d", localStartTime.getMinute())); + fields.put("param4", String.format("%02d", localStopTime.getHour())); + fields.put("param5", String.format("%02d", localStopTime.getMinute())); + fields.put("param6", enableProgram ? "1" : "0"); + + postSetCommandForm(fields); + } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java new file mode 100644 index 0000000000000..f919687a1376c --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link GrowattBridgeConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattBridgeConfiguration { + + public static final String USER_NAME = "userName"; + public static final String PASSWORD = "password"; + + public String userName = ""; + public String password = ""; +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java index c1362cd976720..b5621fe0905f1 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java @@ -13,7 +13,6 @@ package org.openhab.binding.growatt.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; /** * The {@link GrowattInverterConfiguration} class contains fields mapping thing configuration parameters. @@ -24,19 +23,6 @@ public class GrowattInverterConfiguration { public static final String DEVICE_ID = "deviceId"; - public static final String USER_NAME = "userName"; - public static final String PASSWORD = "password"; - public static final String USER_ID = "userId"; - public static final String PLANT_ID = "plantId"; - // required public String deviceId = ""; - - // optional - public @Nullable String userName; - public @Nullable String password; - - // optional for tests only - public @Nullable String userId; - public @Nullable String plantId; } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java new file mode 100644 index 0000000000000..ddb34e35becda --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link GrowattDevice} is a DTO containing device data fields received from the Growatt cloud server. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattDevice { + + private @Nullable String deviceType; + private @Nullable String deviceSn; + + public String getId() { + String deviceSn = this.deviceSn; + return deviceSn != null ? deviceSn : ""; + } + + public String getType() { + String deviceType = this.deviceType; + return deviceType != null ? deviceType : ""; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java new file mode 100644 index 0000000000000..190d61e577812 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link GrowattPlant} is a DTO containing plant data fields received from the Growatt cloud server. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattPlant { + + private @Nullable String plantId; + private @Nullable String plantName; + + public String getId() { + String plantId = this.plantId; + return plantId != null ? plantId : ""; + } + + public String getName() { + String plantName = this.plantName; + return plantName != null ? plantName : ""; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java new file mode 100644 index 0000000000000..6a0600f1fef1b --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link GrowattPlantList} is a DTO containing plant list and user data fields received from the Growatt cloud + * server. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattPlantList { + + private @Nullable List data; + private @Nullable GrowattUser user; + private @Nullable Boolean success; + + public List getPlants() { + List data = this.data; + return data != null ? data : List.of(); + } + + public Boolean getSuccess() { + Boolean success = this.success; + return success != null ? success : false; + } + + public @Nullable GrowattUser getUserId() { + return user; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java new file mode 100644 index 0000000000000..bfb9003e403d5 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link GrowattUser} is a DTO containing user data fields received from the Growatt cloud server. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrowattUser { + + private @Nullable String id; + + public String getId() { + String id = this.id; + return id != null ? id : ""; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index 53be309ce70d0..9671353aea4f6 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -95,11 +95,11 @@ public GrowattHandlerFactory(@Reference HttpService httpService, @Reference Http discoveryRegister(); bridges.add(thing.getUID()); return new GrowattBridgeHandler((Bridge) thing, Objects.requireNonNull(httpServlet), - Objects.requireNonNull(discoveryService)); + Objects.requireNonNull(discoveryService), httpClientFactory); } if (THING_TYPE_INVERTER.equals(thingTypeUID)) { - return new GrowattInverterHandler(thing, httpClientFactory); + return new GrowattInverterHandler(thing); } return null; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 50908bcbf90ad..07e21a0b2f97a 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -16,10 +16,14 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.growatt.internal.cloud.GrowattCloud; +import org.openhab.binding.growatt.internal.config.GrowattBridgeConfiguration; import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.gson.GrottIntegerDeserializer; import org.openhab.binding.growatt.internal.servlet.GrowattHttpServlet; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingStatus; @@ -48,12 +52,16 @@ public class GrowattBridgeHandler extends BaseBridgeHandler { private final GrowattDiscoveryService discoveryService; private final Map inverters = new HashMap<>(); private final GrowattHttpServlet httpServlet; + private final HttpClientFactory httpClientFactory; - public GrowattBridgeHandler(Bridge bridge, GrowattHttpServlet httpServlet, - GrowattDiscoveryService discoveryService) { + private @Nullable GrowattCloud growattCloud; + + public GrowattBridgeHandler(Bridge bridge, GrowattHttpServlet httpServlet, GrowattDiscoveryService discoveryService, + HttpClientFactory httpClientFactory) { super(bridge); this.httpServlet = httpServlet; this.discoveryService = discoveryService; + this.httpClientFactory = httpClientFactory; } @Override @@ -63,6 +71,19 @@ public void dispose() { discoveryService.putInverters(thing.getUID(), inverters.keySet()); } + public GrowattCloud getGrowattCloud() throws IllegalStateException { + GrowattCloud growattCloud = this.growattCloud; + if (growattCloud == null) { + try { + growattCloud = new GrowattCloud(getConfigAs(GrowattBridgeConfiguration.class), httpClientFactory); + } catch (Exception e) { + throw new IllegalStateException("GrowattCloud not created", e); + } + this.growattCloud = growattCloud; + } + return growattCloud; + } + @Override public void handleCommand(ChannelUID channelUID, Command command) { // everything is read only so do nothing diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 27a5d6dd865db..69068e99e2dcb 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.growatt.internal.handler; -import java.time.format.DateTimeParseException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -28,8 +27,8 @@ import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; -import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -53,21 +52,17 @@ public class GrowattInverterHandler extends BaseThingHandler { private static final int AWAITING_DATA_TIMEOUT_MINUTES = 11; private final Logger logger = LoggerFactory.getLogger(GrowattInverterHandler.class); - private final HttpClientFactory httpClientFactory; private String deviceId = "unknown"; private @Nullable ScheduledFuture awaitingDataTimeoutTask; - private @Nullable GrowattCloud growattCloud; - public GrowattInverterHandler(Thing thing, HttpClientFactory httpClientFactory) { + public GrowattInverterHandler(Thing thing) { super(thing); - this.httpClientFactory = httpClientFactory; } @Override public void dispose() { - growattCloud = null; ScheduledFuture task = awaitingDataTimeoutTask; if (task != null) { task.cancel(true); @@ -167,56 +162,31 @@ public void updateInverterValues(GrottValues inverterValues) { } private GrowattCloud getGrowattCloud() throws IllegalStateException { - GrowattCloud growattCloud = this.growattCloud; - if (growattCloud == null) { - try { - growattCloud = new GrowattCloud(getConfigAs(GrowattInverterConfiguration.class), httpClientFactory); - } catch (Exception e) { - throw new IllegalStateException("GrowattCloud not created", e); - } - this.growattCloud = growattCloud; + Bridge bridge = getBridge(); + if (bridge != null && (bridge.getHandler() instanceof GrowattBridgeHandler bridgeHandler)) { + return bridgeHandler.getGrowattCloud(); } - return growattCloud; + throw new IllegalStateException("Unable to get GrowattCloud from bridge handler"); } /** * This method is called from a Rule Action to setup the battery charging program. * - * @param chargingPower the rate of charging 0%..100% - * @param targetSOC the SOC at which to stop charging 0%..100% - * @param allowAcCharging allow the battery to be charged from AC power + * @param programMode indicates if the program is Load first (0), Battery first (1), Grid first (2) + * @param powerLevel the rate of charging / discharging 0%..100% + * @param stopSOC the SOC at which to stop charging / discharging 0%..100% + * @param enableAcCharging allow the battery to be charged from AC power * @param startTime the start time of the charging program; a time formatted string e.g. "12:34" * @param stopTime the stop time of the charging program; a time formatted string e.g. "12:34" - * @param programEnable charge program shall be enabled - */ - public void setupChargingProgram(Number chargingPower, Number targetSOC, boolean allowAcCharging, String startTime, - String stopTime, boolean programEnable) { - try { - getGrowattCloud().setupChargingProgram(chargingPower.intValue(), targetSOC.intValue(), allowAcCharging, - GrowattCloud.localTimeOf(startTime), GrowattCloud.localTimeOf(stopTime), programEnable); - } catch (IllegalStateException | DateTimeParseException | GrowattApiException e) { - logger.warn("setupChargingProgram() error", e); - this.growattCloud = null; - } - } - - /** - * This method is called from a Rule Action to setup the battery discharging program. - * - * @param dischargingPower the rate of discharging 1%..100% - * @param targetSOC the SOC at which to stop charging 1%..100% - * @param startTime the start time of the discharging program; a time formatted string e.g. "12:34" - * @param stopTime the stop time of the discharging program; a time formatted string e.g. "12:34" - * @param programEnable the discharge program shall be enabled + * @param enableProgram charge / discharge program shall be enabled */ - public void setupDischargingProgram(Number dischargingPower, Number targetSOC, String startTime, String stopTime, - boolean programEnable) { + public void setupBatteryProgram(Integer programMode, Integer powerLevel, Integer stopSOC, Boolean enableAcCharging, + String startTime, String stopTime, Boolean enableProgram) { try { - getGrowattCloud().setupDischargingProgram(dischargingPower.intValue(), targetSOC.intValue(), - GrowattCloud.localTimeOf(startTime), GrowattCloud.localTimeOf(stopTime), programEnable); - } catch (IllegalStateException | DateTimeParseException | GrowattApiException e) { - logger.warn("setupDischargingProgram() error", e); - this.growattCloud = null; + getGrowattCloud().setupBatteryProgram(deviceId, programMode, powerLevel, stopSOC, enableAcCharging, + startTime, stopTime, enableProgram); + } catch (GrowattApiException e) { + logger.warn("setupBatteryProgram() error", e); } } } diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 0ba38446d462c..2d2f8335d1ab1 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -188,8 +188,12 @@ thing-type.growatt.inverter.channel.total-work-time.description = Total inverter # thing types config +thing-type.config.growatt.bridge.password.label = Password +thing-type.config.growatt.bridge.password.description = Password to login to the Shine App. +thing-type.config.growatt.bridge.userName.label = User Name +thing-type.config.growatt.bridge.userName.description = User name to login to the Shine App. thing-type.config.growatt.inverter.deviceId.label = Device Id -thing-type.config.growatt.inverter.deviceId.description = Id of the inverter. +thing-type.config.growatt.inverter.deviceId.description = Id (serial number) of the inverter. # channel types @@ -216,19 +220,20 @@ status.awaiting-data = Waiting for data from Grott application status.awaiting-data-timeout = Timed out waiting for data from Grott application # actions -actions.allow-ac-charging.label = Allow AC Charging -actions.allow-ac-charging.description = Allow the battery to be charged from AC supply -actions.charging.label = Setup Charge Program -actions.charging.description = Setup the battery charging program -actions.charging-power.label = Charge / Discharge Power -actions.charging-power.description = The rate of battery charging / discharging (1%..100%) -actions.discharging.label = Setup Discharge Program -actions.discharging.description = Setup the battery discharging program -actions.program-enable.label = Program Enable -actions.program-enable.description = Enable / disable the battery charging / discharging program + +actions.battery-program.label = Setup Battery Program +actions.battery-program.description = Setup the battery charging / discharging program +actions.enable-ac-charging.label = Enable AC Charging +actions.enable-ac-charging.description = Enable the battery to be charged from AC supply +actions.enable-program.label = Program Enable +actions.enable-program.description = Enable / disable the battery charging / discharging program +actions.power-level.label = Charge / Discharge Power +actions.power-level.description = The rate of battery charging / discharging (1%..100%) +actions.program-mode.label = Battery Program Mode +actions.program-mode.description = Select Load First (0), Battery First (2), Grid First (2) actions.start-time.label = Start Time actions.start-time.description = The time when the program shall start +actions.stop-soc.label = Stop SOC Level +actions.stop-soc.description = The battery SOC target for when the program is completed actions.stop-time.label = Stop Time actions.stop-time.description = The time when the program shall stop -actions.target-soc.label = Target SOC -actions.target-soc.description = The battery SOC target for when the program is completed diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 4ad771adebfcc..21bc4f06b2b37 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -8,6 +8,20 @@ Bridge Thing for Growatt Binding + + + + + User name to login to the Shine App. + true + + + password + + Password to login to the Shine App. + true + + @@ -430,26 +444,10 @@ - Id of the inverter. - - - - User name to login to the Shine App. - - - password - - Password to login to the Shine App. - - - - User Id as displayed in the Shine App. - - - - Plant Id as displayed in the Shine App. + Id (serial number) of the inverter. + diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index a1159e765bfdd..fe5c48f8ebbe9 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -21,7 +21,6 @@ import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Field; -import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -36,7 +35,7 @@ import org.openhab.binding.growatt.internal.GrowattChannels; import org.openhab.binding.growatt.internal.GrowattChannels.UoM; import org.openhab.binding.growatt.internal.cloud.GrowattCloud; -import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; +import org.openhab.binding.growatt.internal.config.GrowattBridgeConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; import org.openhab.binding.growatt.internal.gson.GrottIntegerDeserializer; @@ -280,18 +279,19 @@ private void testJsonFieldsMappedToDto(String fileName) throws FileNotFoundExcep */ @Test void testServer() throws Exception { - GrowattInverterConfiguration configuration = new GrowattInverterConfiguration(); + GrowattBridgeConfiguration configuration = new GrowattBridgeConfiguration(); + /* * To test on an actual inverter, populate its plant data and user credentials below. * - * configuration.deviceId = "aa"; - * configuration.userName = "bb"; - * configuration.password ="cc"; - * configuration.userId = "dd"; - * configuration.plantId = "ee"; + * configuration.userName = "aa"; + * configuration.password ="bb"; + * + * deviceId = "cc"; + * */ - if (configuration.userName == null) { + if (configuration.userName.isBlank()) { return; } @@ -300,45 +300,45 @@ void testServer() throws Exception { .thenReturn(new HttpClient(new SslContextFactory.Client(true))); try (GrowattCloud api = new GrowattCloud(configuration, httpClientFactory)) { - assertFalse(api.getPlantList().isEmpty()); - assertFalse(api.getPlantInfo().isEmpty()); - - int chargingPower = 97; - int targetSOC = 23; - boolean allowAcCharging = false; - LocalTime startTime = LocalTime.of(1, 16); - LocalTime stopTime = LocalTime.of(2, 17); - boolean programEnable = false; - assertFalse(api - .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) - .isEmpty()); - Map result = api.getMixAllSettings(); + Integer programMode = GrowattCloud.ProgramMode.BATTERY_FIRST.ordinal(); + Integer chargingPower = 97; + Integer targetSOC = 23; + Boolean allowAcCharging = false; + String startTime = "01:16"; + String stopTime = "02:17"; + Boolean programEnable = false; + api.setupBatteryProgram(deviceId, programMode, chargingPower, targetSOC, allowAcCharging, startTime, + stopTime, programEnable); + Map result = api.getDeviceSettings(deviceId); assertFalse(result.isEmpty()); assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); assertEquals(allowAcCharging, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); - assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); - assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); + assertEquals(GrowattCloud.localTimeOf(startTime), + GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); + assertEquals(GrowattCloud.localTimeOf(stopTime), + GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); chargingPower = 100; targetSOC = 20; allowAcCharging = true; - startTime = LocalTime.of(0, 15); - stopTime = LocalTime.of(6, 45); + startTime = "00:15"; + stopTime = "06:45"; programEnable = true; - assertFalse(api - .setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) - .isEmpty()); - result = api.getMixAllSettings(); + api.setupBatteryProgram(deviceId, programMode, chargingPower, targetSOC, allowAcCharging, startTime, + stopTime, programEnable); + result = api.getDeviceSettings(deviceId); assertFalse(result.isEmpty()); assertEquals(chargingPower, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_POWER)); assertEquals(targetSOC, GrowattCloud.mapGetInteger(result, GrowattCloud.CHARGE_PROGRAM_TARGET_SOC)); assertEquals(allowAcCharging, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ALLOW_AC_CHARGING)); - assertEquals(startTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); - assertEquals(stopTime, GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); + assertEquals(GrowattCloud.localTimeOf(startTime), + GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_START_TIME)); + assertEquals(GrowattCloud.localTimeOf(stopTime), + GrowattCloud.mapGetLocalTime(result, GrowattCloud.CHARGE_PROGRAM_STOP_TIME)); assertEquals(programEnable, GrowattCloud.mapGetBoolean(result, GrowattCloud.CHARGE_PROGRAM_ENABLE)); } } From c1792fb2b61b777c964553cdfe1c343da89b32b5 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 5 Jan 2024 22:17:08 +0000 Subject: [PATCH 135/146] [growatt] fix build Signed-off-by: Andrew Fiddian-Green --- .../test/java/org/openhab/binding/growatt/test/GrowattTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index fe5c48f8ebbe9..085cb66503f12 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -280,6 +280,7 @@ private void testJsonFieldsMappedToDto(String fileName) throws FileNotFoundExcep @Test void testServer() throws Exception { GrowattBridgeConfiguration configuration = new GrowattBridgeConfiguration(); + String deviceId = ""; /* * To test on an actual inverter, populate its plant data and user credentials below. From 0132eccfc6f1beb00bd7bb06444cebff238ba320 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 6 Jan 2024 16:32:03 +0000 Subject: [PATCH 136/146] Implement tlx settings Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 77 +++++++++++-------- .../growatt/internal/GrowattChannels.java | 4 +- .../growatt/internal/cloud/GrowattCloud.java | 42 +++++----- .../config/GrowattBridgeConfiguration.java | 5 +- .../resources/OH-INF/i18n/growatt.properties | 12 +-- .../resources/OH-INF/thing/thing-types.xml | 24 +++--- .../binding/growatt/test/GrowattTest.java | 2 +- 7 files changed, 95 insertions(+), 71 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index f02668772294c..75604a5493228 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -23,15 +23,18 @@ However if a bridge exists and it receives inverter data, then a matching invert ## Thing Configuration -The `bridge` thing requires no configuration. +The `bridge` thing requires configuration of the user credentials: + +| Name | Type | Description | Required | +|-----------|---------|------------------------------------------------------------------------------------------|----------| +| userName | text | User name for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | +| password | text | Password for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | The `inverter` thing requires configuration of its serial number resp. `deviceId`: | Name | Type | Description | Required | |-----------|---------|------------------------------------------------------------------------------------------|----------| | deviceId | text | Device serial number or id as configured in the Growatt cloud and the Grott application. | yes | -| userName | text | User name for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | -| password | text | Password for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | ## Channels @@ -129,9 +132,9 @@ The list of all possible channels is as follows: | sp-display-status | Number:Dimensionless | Solar panel display status code. | yes | | constant-power-ok | Number:Dimensionless | Constant power OK code. | yes | | load-percent | Number:Dimensionless | Percent of full load. | yes | -| rac | Number:Dimensionless | RAC code. | yes | -| erac-today | Number:Dimensionless | ERAC count today. | yes | -| erac-total | Number:Dimensionless | Total ERAC count. | yes | +| rac | Number:Dimensionless | RAC state. | yes | +| erac-today | Number:Energy | RAC energy today. | yes | +| erac-total | Number:Energy | Total RAC energy. | yes | ## Rule Actions @@ -143,23 +146,36 @@ val growattActions = getActions("growatt", "growatt:inverter:home:sph") ``` Where the first parameter must always be `growatt` and the second must be the full inverter thing UID. -Once the action instance has been retrieved, you can invoke the following methods. +Once the action instance has been retrieved, you can invoke the following method: ```php -growattActions.setupChargingProgram(int chargingPower, int targetSOC, boolean allowAcCharging, String startTime, String stopTime, boolean programEnable) - -growattActions.setupDischargingProgram(int dischargingPower, int targetSOC, String startTime, String stopTime, boolean programEnable) +growattActions.setupBatteryProgram(int programMode, @Nullable Integer powerLevel, @Nullable Integer stopSOC, @Nullable Boolean enableAcCharging, @Nullable String startTime, @Nullable String stopTime, @Nullable Boolean enableProgram) ``` +The meaning of the method parameters is as follows: + | Parameter | Description | |------------------|---------------------------------------------------------------------------------------| -| chargingPower | The rate of charging the battery 1%..100% (e.g. 50) | -| dischargingPower | The rate of discharging the battery 1%..100% (e.g. 100) | -| targetSOC | The target battery SOC (state of charge) when the program stops. (e.g. 20) | -| allowAcCharging | Allow the battery to be charged from the AC mains supply. (e.g. true, false) | +| programMode | The program mode to set i.e. 'Load First' (0), 'Battery First' (1), 'Grid First' (2) | +| powerLevel | The rate of charging resp. discharging the battery 1%..100% (e.g. 100) | +| stopSOC | The target battery SOC (state of charge) when the program stops. (e.g. 20) | +| enableAcCharging | Allow the battery to be charged from the AC mains supply. (e.g. true, false) | | startTime | String representation of the local time when the program shall start. (e.g. "00:15") | | stopTime | String representation of the local time when the program shall stop. (e.g. "06:45") | -| programEnable | Disable / enable the program. (e.g. true, false) | +| enableProgram | Disable / enable the program. (e.g. true, false) | + +Depending on the inverter type and the program mode certain parameters may be 'null'. +The 'mix', 'sph' or 'spa' inverter types set their battery program in one single command, so all parameters except `enableAcCharging` MUST be non-null. +By contrast the 'tlx' inverter type sets its battery program in up to four partial commands, and to omit such a partial command you can set its respective parameter(s) to null. +The permission for using null paramater values, and the effect of null paramater values, is shown in the table below: + +| Parameter | Permission for- resp. Effect of- Null Parameter Value | +|------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| programMode | Shall NOT be null under any circumstance. | +| powerLevel | May be null on 'tlx' inverters whereby the prior programMode powerLevel continues. | +| stopSOC | May be null on 'tlx' inverters whereby the prior programMode stopSOC continues. | +| enableAcCharging | Shall NOT be null on 'mix' inverter 'Battery First' programs. If null the prior enableAcCharging state (if any) continues. | +| startTime, stopTime, enableProgram | May be null on 'tlx' inverters whereby the prior programMode time segment continues. Either ALL null or ALL non-null. | Example: @@ -174,29 +190,30 @@ then } else { // fixed algorithm parameters - val chargingPower = 25 - val allowAcCharging = true - val startTime = "00:20" - val stopTime = "07:00" - val programEnable = true - val batteryFull = 6500 - val batteryMin = 500 - val daylightConsumption = 10000 - val maximumSOC = 100 - val minimumSOC = 20 + val Integer programMode = 1 + val Integer powerLevel = 23 + val Boolean enableAcCharging = true + val String startTime = "00:20" + val String stopTime = "07:00" + val Boolean enableProgram = true + val Integer batteryFull = 6500 + val Integer batteryMin = 500 + val Integer daylightConsumption = 10000 + val Integer maximumSOC = 100 + val Integer minimumSOC = 20 // variable algorithm parameters - val solarForecast = (ForecastSolar_PV_Whole_Site_Forecast_Today.state as QuantityType).toUnit("Wh").toBigDecimal() - var targetSOC = (100 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull + val Double solarForecast = (ForecastSolar_PV_Whole_Site_Forecast_Today.state as QuantityType).toUnit("Wh").doubleValue() + var Double targetSOC = (100 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull if (targetSOC > maximumSOC) { targetSOC = maximumSOC } else if (targetSOC < minimumSOC) { targetSOC = minimumSOC } + val Integer stopSOC = targetSOC.intValue() - logInfo("Rules", "Setup Charging Program:{solarForecast:" + solarForecast + ", chargingPower:" + chargingPower + ", targetSOC:" + targetSOC + ", allowAcCharging:" + - allowAcCharging + ", startTime:" + startTime + ", stopTime:" + stopTime + ", programEnable:" + programEnable +"}") - growattActions.setupChargingProgram(chargingPower, targetSOC, allowAcCharging, startTime, stopTime, programEnable) + logInfo("Rules", "Setup Charging Program:{solarForecast:" + solarForecast + ", programMode:" + programMode + ", powerLevel:" + powerLevel + ", stopSOC:" + stopSOC + ", enableCharging:" + enableAcCharging + ", startTime:" + startTime + ", stopTime:" + stopTime + ", enableProgram:" + enableProgram +"}") + growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram) } end ``` diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java index 63b3fbce06438..e10f9bea22836 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java @@ -185,8 +185,8 @@ public UoM(Unit units, float divisor) { // rac ?? new AbstractMap.SimpleEntry("rac", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("erac-today", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("erac-total", new UoM(Units.ONE, 1)) + new AbstractMap.SimpleEntry("erac-today", new UoM(Units.KILOWATT_HOUR, 10)), + new AbstractMap.SimpleEntry("erac-total", new UoM(Units.KILOWATT_HOUR, 10)) // ); diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index dea8d89387f07..08e603479a4c2 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -94,6 +94,8 @@ public class GrowattCloud implements AutoCloseable { private static final String PLANT_INFO_API_ENDPOINT = "newTwoPlantAPI.do"; private static final String NEW_TCP_SET_API_ENDPOINT = "newTcpsetAPI.do"; + private static final String FMT_NEW_DEVICE_TYPE_API_DO = "new%sApi.do"; + // command operations private static final String OP_GET_ALL_DEVICE_LIST = "getAllDeviceList"; @@ -108,7 +110,7 @@ private static enum DeviceType { } /* - * Map of device types vs. field parameters for GET commands from FMT_API_ENDPOINT's. + * Map of device types vs. field parameters for GET requests to FMT_NEW_DEVICE_TYPE_API_DO end-points. * Note: some values are guesses which have not yet been confirmed by users */ private static final Map SUPPORTED_TYPES_GET_PARAM = Map.of( @@ -326,15 +328,17 @@ private DeviceType getDeviceTypeChecked(String deviceId) throws GrowattApiExcept for (String plantId : plantIds) { for (GrowattDevice device : getPlantInfo(plantId)) { try { - DeviceType deviceType = DeviceType.valueOf(device.getType().toUpperCase()); - deviceIdTypeMap.put(deviceId, deviceType); + deviceIdTypeMap.put(device.getId(), DeviceType.valueOf(device.getType().toUpperCase())); } catch (IllegalArgumentException e) { - // ignore unsupported device types + // just ignore unsupported device types } } } logger.debug("Downloaded deviceTypes:{}", deviceIdTypeMap); } + if (deviceId.isBlank()) { + throw new GrowattApiException("Device id is blank"); + } DeviceType deviceType = deviceIdTypeMap.get(deviceId); if (deviceType != null) { return deviceType; @@ -353,7 +357,7 @@ public Map getDeviceSettings(String deviceId) throws Growat DeviceType deviceType = getDeviceTypeChecked(deviceId); String dt = deviceType.name().toLowerCase(); - String endPoint = String.format("new%sApi.do", dt.substring(0, 1).toUpperCase() + dt.substring(1)); + String endPoint = String.format(FMT_NEW_DEVICE_TYPE_API_DO, dt.substring(0, 1).toUpperCase() + dt.substring(1)); Map params = new LinkedHashMap<>(); // keep params in order params.put("op", Objects.requireNonNull(SUPPORTED_TYPES_GET_PARAM.get(deviceType))); @@ -439,16 +443,18 @@ public GrowattPlantList getPlantList(String userId) throws GrowattApiException { * @throws GrowattApiException if any error occurs. */ private void postLoginCredentials() throws GrowattApiException { - if (configuration.userName.isBlank()) { + String userName = configuration.userName; + if (userName == null || userName.isBlank()) { throw new GrowattApiException("User name missing"); } - if (configuration.password.isBlank()) { + String password = configuration.password; + if (password == null || password.isBlank()) { throw new GrowattApiException("Password missing"); } Fields fields = new Fields(); - fields.put("userName", configuration.userName); - fields.put("password", createHash(configuration.password)); + fields.put("userName", userName); + fields.put("password", createHash(password)); Map result = doHttpRequestInner(HttpMethod.POST, LOGIN_API_ENDPOINT, null, fields); @@ -475,7 +481,7 @@ private void postLoginCredentials() throws GrowattApiException { * Post a command to setup the inverter battery charging program. * * @param the deviceId to set up - * @param programModeIndex index of the type of program Load First (0) / Battery First (1) / Grid First (2) + * @param programModeInt index of the type of program Load First (0) / Battery First (1) / Grid First (2) * @param powerLevel the rate of charging / discharging * @param stopSOC the SOC at which to stop charging / discharging * @param enableAcCharging allow charging from AC power @@ -485,7 +491,7 @@ private void postLoginCredentials() throws GrowattApiException { * * @throws GrowattApiException if any error occurs */ - public void setupBatteryProgram(String deviceId, int programModeIndex, @Nullable Integer powerLevel, + public void setupBatteryProgram(String deviceId, int programModeInt, @Nullable Integer powerLevel, @Nullable Integer stopSOC, @Nullable Boolean enableAcCharging, @Nullable String startTime, @Nullable String stopTime, @Nullable Boolean enableProgram) throws GrowattApiException { // @@ -495,9 +501,9 @@ public void setupBatteryProgram(String deviceId, int programModeIndex, @Nullable ProgramMode programMode; try { - programMode = ProgramMode.values()[programModeIndex]; + programMode = ProgramMode.values()[programModeInt]; } catch (IndexOutOfBoundsException e) { - throw new GrowattApiException("Program mode index is out of range (0..2)"); + throw new GrowattApiException("Program mode is out of range (0..2)"); } DeviceType deviceType = getDeviceTypeChecked(deviceId); @@ -752,7 +758,7 @@ private void setEnableAcCharging(String deviceId, DeviceType deviceType, boolean // Fields fields = new Fields(); - fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("op", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); fields.put("serialNum", deviceId); fields.put("type", "ac_charge"); fields.put("param1", enableAcCharging ? "1" : "0"); @@ -792,7 +798,7 @@ private void setPowerLevel(String deviceId, DeviceType deviceType, ProgramMode p Fields fields = new Fields(); - fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("op", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); fields.put("serialNum", deviceId); fields.put("type", typeParam); fields.put("param1", String.format("%d", powerLevel)); @@ -823,7 +829,7 @@ private void setStopSOC(String deviceId, DeviceType deviceType, ProgramMode prog typeParam = "charge_stop_soc"; break; case GRID_FIRST: - typeParam = "on-grid-discharge_stop_soc"; + typeParam = "on_grid_discharge_stop_soc"; break; case LOAD_FIRST: typeParam = "discharge_stop_soc"; @@ -834,7 +840,7 @@ private void setStopSOC(String deviceId, DeviceType deviceType, ProgramMode prog Fields fields = new Fields(); - fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("op", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); fields.put("serialNum", deviceId); fields.put("type", typeParam); fields.put("param1", String.format("%d", stopSOC)); @@ -883,7 +889,7 @@ private void setTimeSegment(String deviceId, DeviceType deviceType, ProgramMode Fields fields = new Fields(); - fields.put("action", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); + fields.put("op", Objects.requireNonNull(SUPPORTED_TYPE_POST_PARAM.get(deviceType))); fields.put("serialNum", deviceId); fields.put("type", String.format("time_segment%d", programMode.ordinal() + 1)); fields.put("param1", String.format("%d", programMode.ordinal())); diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java index f919687a1376c..83d6187dc8211 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java @@ -13,6 +13,7 @@ package org.openhab.binding.growatt.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * The {@link GrowattBridgeConfiguration} class contains fields mapping thing configuration parameters. @@ -25,6 +26,6 @@ public class GrowattBridgeConfiguration { public static final String USER_NAME = "userName"; public static final String PASSWORD = "password"; - public String userName = ""; - public String password = ""; + public @Nullable String userName; + public @Nullable String password; } diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 2d2f8335d1ab1..1bc223f2827bc 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -33,10 +33,10 @@ thing-type.growatt.inverter.channel.discharge-power.label = Discharge Power thing-type.growatt.inverter.channel.discharge-power.description = Discharge power from battery. thing-type.growatt.inverter.channel.discharge-va.label = Discharge VA thing-type.growatt.inverter.channel.discharge-va.description = Discharge VA from battery. -thing-type.growatt.inverter.channel.erac-today.label = ERAC Today -thing-type.growatt.inverter.channel.erac-today.description = ERAC count today. -thing-type.growatt.inverter.channel.erac-total.label = ERAC Total -thing-type.growatt.inverter.channel.erac-total.description = Total ERAC count. +thing-type.growatt.inverter.channel.erac-today.label = Reserve Energy Today +thing-type.growatt.inverter.channel.erac-today.description = Reserve AC supply energy today. +thing-type.growatt.inverter.channel.erac-total.label = Total Reserve Energy +thing-type.growatt.inverter.channel.erac-total.description = Total reserve AC supply energy. thing-type.growatt.inverter.channel.export-energy-today.label = Export Energy Today thing-type.growatt.inverter.channel.export-energy-today.description = Energy exported to grid today. thing-type.growatt.inverter.channel.export-energy-total.label = Export Energy Total @@ -155,8 +155,8 @@ thing-type.growatt.inverter.channel.pv2-temperature.label = Solar Panel Temperat thing-type.growatt.inverter.channel.pv2-temperature.description = Temperature of the solar panels (string #2). thing-type.growatt.inverter.channel.pv2-voltage.label = String #2 Voltage thing-type.growatt.inverter.channel.pv2-voltage.description = Voltage from solar panel string #2. -thing-type.growatt.inverter.channel.rac.label = RAC -thing-type.growatt.inverter.channel.rac.description = RAC code. +thing-type.growatt.inverter.channel.rac.label = Reserve AC Status +thing-type.growatt.inverter.channel.rac.description = Reserve AC supply status. thing-type.growatt.inverter.channel.sp-bus-voltage.label = SP Bus Voltage thing-type.growatt.inverter.channel.sp-bus-voltage.description = SP Bus voltage. thing-type.growatt.inverter.channel.sp-display-status.label = Solar Panel Display diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 21bc4f06b2b37..15e529fb328de 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -10,12 +10,12 @@ Bridge Thing for Growatt Binding - + User name to login to the Shine App. true - + password Password to login to the Shine App. @@ -425,18 +425,18 @@ Percent of full load. - - - - RAC code. + + + + Reserve AC supply status. - - - ERAC count today. + + + Reserve AC supply energy today. - - - Total ERAC count. + + + Total reserve AC supply energy. diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 085cb66503f12..db5de23771e57 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -292,7 +292,7 @@ void testServer() throws Exception { * */ - if (configuration.userName.isBlank()) { + if (configuration.userName == null) { return; } From a15173e60a18773cd06021eebe3017c0a72ec0f2 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 6 Jan 2024 17:42:25 +0000 Subject: [PATCH 137/146] Accept null arguments Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/action/GrowattActions.java | 17 +++++++++-------- .../internal/gson/GrottIntegerDeserializer.java | 2 ++ .../handler/GrowattInverterHandler.java | 5 +++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java index 1fdafe4274093..75e8bfc1612b7 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java @@ -35,8 +35,9 @@ public class GrowattActions implements ThingActions { private final Logger logger = LoggerFactory.getLogger(GrowattActions.class); private @Nullable GrowattInverterHandler handler; - public static void setupBatteryProgram(ThingActions actions, Integer programMode, Integer powerLevel, - Integer stopSOC, Boolean enableAcCharging, String startTime, String stopTime, Boolean enableProgram) { + public static void setupBatteryProgram(ThingActions actions, Integer programMode, @Nullable Integer powerLevel, + @Nullable Integer stopSOC, @Nullable Boolean enableAcCharging, @Nullable String startTime, + @Nullable String stopTime, @Nullable Boolean enableProgram) { if (actions instanceof GrowattActions growattActions) { growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram); @@ -58,12 +59,12 @@ public void setThingHandler(@Nullable ThingHandler handler) { @RuleAction(label = "@text/actions.battery-program.label", description = "@text/actions.battery-program.description") public void setupBatteryProgram( @ActionInput(name = "program-mode", label = "@text/actions.program-mode.label", description = "@text/actions.program-mode.description") Integer programMode, - @ActionInput(name = "power-level", label = "@text/actions.power-level.label", description = "@text/actions.power-level.description") Integer powerLevel, - @ActionInput(name = "stop-soc", label = "@text/actions.stop-soc.label", description = "@text/actions.stop-soc.description") Integer stopSOC, - @ActionInput(name = "enable-ac-charging", label = "@text/actions.enable-ac-charging.label", description = "@text/actions.enable-ac-charging.description") Boolean enableAcCharging, - @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") String startTime, - @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") String stopTime, - @ActionInput(name = "enable-program", label = "@text/actions.enable-program.label", description = "@text/actions.enable-program.description") Boolean enableProgram) { + @ActionInput(name = "power-level", label = "@text/actions.power-level.label", description = "@text/actions.power-level.description") @Nullable Integer powerLevel, + @ActionInput(name = "stop-soc", label = "@text/actions.stop-soc.label", description = "@text/actions.stop-soc.description") @Nullable Integer stopSOC, + @ActionInput(name = "enable-ac-charging", label = "@text/actions.enable-ac-charging.label", description = "@text/actions.enable-ac-charging.description") @Nullable Boolean enableAcCharging, + @ActionInput(name = "start-time", label = "@text/actions.start-time.label", description = "@text/actions.start-time.description") @Nullable String startTime, + @ActionInput(name = "stop-time", label = "@text/actions.stop-time.label", description = "@text/actions.stop-time.description") @Nullable String stopTime, + @ActionInput(name = "enable-program", label = "@text/actions.enable-program.label", description = "@text/actions.enable-program.description") @Nullable Boolean enableProgram) { GrowattInverterHandler handler = this.handler; if (handler != null) { handler.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java index 149cad0118f54..d2a8996d2f8f5 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java @@ -16,6 +16,7 @@ import java.util.Objects; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.google.gson.JsonDeserializationContext; @@ -29,6 +30,7 @@ * * @author Andrew Fiddian-Green - Initial contribution */ +@NonNullByDefault public class GrottIntegerDeserializer implements JsonDeserializer { private static final long INT_BIT_MASK = 0xffffffff; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index 69068e99e2dcb..b737e83837be4 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -180,8 +180,9 @@ private GrowattCloud getGrowattCloud() throws IllegalStateException { * @param stopTime the stop time of the charging program; a time formatted string e.g. "12:34" * @param enableProgram charge / discharge program shall be enabled */ - public void setupBatteryProgram(Integer programMode, Integer powerLevel, Integer stopSOC, Boolean enableAcCharging, - String startTime, String stopTime, Boolean enableProgram) { + public void setupBatteryProgram(Integer programMode, @Nullable Integer powerLevel, @Nullable Integer stopSOC, + @Nullable Boolean enableAcCharging, @Nullable String startTime, @Nullable String stopTime, + @Nullable Boolean enableProgram) { try { getGrowattCloud().setupBatteryProgram(deviceId, programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram); From e9c2a0130d25097e7097302eee63e0f1def55e7d Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 7 Jan 2024 10:53:20 +0000 Subject: [PATCH 138/146] [growatt] fix rule example Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 75604a5493228..df4acf1d4959f 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -196,20 +196,25 @@ then val String startTime = "00:20" val String stopTime = "07:00" val Boolean enableProgram = true - val Integer batteryFull = 6500 - val Integer batteryMin = 500 - val Integer daylightConsumption = 10000 - val Integer maximumSOC = 100 - val Integer minimumSOC = 20 - // variable algorithm parameters + // calculation intermediaries + val batteryFull = 6500.0 // kWh + val batteryMin = 500.0 // kWh + val daylightConsumption = 10000.0 // kWh + val maximumSOC = 100.0 // percent + val minimumSOC = 20.0 // percent + + + // calculate stop SOC based on weather forecast val Double solarForecast = (ForecastSolar_PV_Whole_Site_Forecast_Today.state as QuantityType).toUnit("Wh").doubleValue() - var Double targetSOC = (100 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull + var Double targetSOC = (100.0 * (batteryMin + daylightConsumption - solarForecast)) / batteryFull if (targetSOC > maximumSOC) { targetSOC = maximumSOC } else if (targetSOC < minimumSOC) { targetSOC = minimumSOC } + + // convert to integer val Integer stopSOC = targetSOC.intValue() logInfo("Rules", "Setup Charging Program:{solarForecast:" + solarForecast + ", programMode:" + programMode + ", powerLevel:" + powerLevel + ", stopSOC:" + stopSOC + ", enableCharging:" + enableAcCharging + ", startTime:" + startTime + ", stopTime:" + stopTime + ", enableProgram:" + enableProgram +"}") From 9ade0beaafb6616e49d719b135d9fcf7e5749a64 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 7 Jan 2024 11:16:47 +0000 Subject: [PATCH 139/146] [growatt] update copyright header Signed-off-by: Andrew Fiddian-Green --- .../binding/growatt/internal/GrowattBindingConstants.java | 2 +- .../org/openhab/binding/growatt/internal/GrowattChannels.java | 2 +- .../openhab/binding/growatt/internal/action/GrowattActions.java | 2 +- .../binding/growatt/internal/cloud/GrowattApiException.java | 2 +- .../openhab/binding/growatt/internal/cloud/GrowattCloud.java | 2 +- .../growatt/internal/config/GrowattBridgeConfiguration.java | 2 +- .../growatt/internal/config/GrowattInverterConfiguration.java | 2 +- .../growatt/internal/discovery/GrowattDiscoveryService.java | 2 +- .../org/openhab/binding/growatt/internal/dto/GrottDevice.java | 2 +- .../org/openhab/binding/growatt/internal/dto/GrottValues.java | 2 +- .../org/openhab/binding/growatt/internal/dto/GrowattDevice.java | 2 +- .../org/openhab/binding/growatt/internal/dto/GrowattPlant.java | 2 +- .../openhab/binding/growatt/internal/dto/GrowattPlantList.java | 2 +- .../org/openhab/binding/growatt/internal/dto/GrowattUser.java | 2 +- .../binding/growatt/internal/factory/GrowattHandlerFactory.java | 2 +- .../binding/growatt/internal/gson/GrottIntegerDeserializer.java | 2 +- .../binding/growatt/internal/handler/GrowattBridgeHandler.java | 2 +- .../growatt/internal/handler/GrowattInverterHandler.java | 2 +- .../binding/growatt/internal/servlet/GrowattHttpServlet.java | 2 +- .../test/java/org/openhab/binding/growatt/test/GrowattTest.java | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java index 6fcfcbb40f4f2..9fc854880064e 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattBindingConstants.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java index e10f9bea22836..c88b14ec2116d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java index 75e8bfc1612b7..bd55c04a07985 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/action/GrowattActions.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattApiException.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattApiException.java index 10eab7fb3d532..3ab477cb1ad6a 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattApiException.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattApiException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index 08e603479a4c2..f780d72afd5a8 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java index 83d6187dc8211..c47f7fb0d9b26 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattBridgeConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java index b5621fe0905f1..88e0b4aa2cbc7 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/config/GrowattInverterConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java index 52580cc217797..cc39b3b7aa923 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/discovery/GrowattDiscoveryService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java index 2a3d4beeecbfb..140078d1b6a38 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottDevice.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index c6fc67e69313a..1f3f4243a27e1 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java index ddb34e35becda..bfabe18412fb1 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattDevice.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java index 190d61e577812..d66cb69658089 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlant.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java index 6a0600f1fef1b..ac6f61cea6beb 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattPlantList.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java index bfb9003e403d5..e3a9dbacc526d 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrowattUser.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java index 9671353aea4f6..402cb9c86c332 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/factory/GrowattHandlerFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java index d2a8996d2f8f5..e357aee800ab2 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 07e21a0b2f97a..823d6fcba2376 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index b737e83837be4..cbc7fb25bad4b 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrowattHttpServlet.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrowattHttpServlet.java index a13b7a583c7d9..831fc3bb53737 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrowattHttpServlet.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/servlet/GrowattHttpServlet.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index db5de23771e57..56518962fb8cc 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. From 9a8638cd9302ae373e682ef57479fc1fb0acadb9 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 7 Jan 2024 16:17:12 +0000 Subject: [PATCH 140/146] [growatt] fix channel uom errors Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/GrowattChannels.java | 10 +++--- .../growatt/internal/dto/GrottValues.java | 7 ++-- .../resources/OH-INF/i18n/growatt.properties | 14 ++++---- .../resources/OH-INF/thing/thing-types.xml | 34 +++++++++++++------ .../binding/growatt/test/GrowattTest.java | 2 +- 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java index c88b14ec2116d..503fd1ccd4685 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/GrowattChannels.java @@ -84,7 +84,7 @@ public UoM(Unit units, float divisor) { new AbstractMap.SimpleEntry("inverter-va", new UoM(Units.VOLT_AMPERE, 10)), // battery discharge / charge power - new AbstractMap.SimpleEntry("charge-current", new UoM(Units.VOLT_AMPERE, 10)), + new AbstractMap.SimpleEntry("charge-current", new UoM(Units.AMPERE, 10)), new AbstractMap.SimpleEntry("charge-power", new UoM(Units.WATT, 10)), new AbstractMap.SimpleEntry("discharge-power", new UoM(Units.WATT, 10)), @@ -183,10 +183,10 @@ public UoM(Unit units, float divisor) { new AbstractMap.SimpleEntry("constant-power-ok", new UoM(Units.ONE, 1)), new AbstractMap.SimpleEntry("load-percent", new UoM(Units.PERCENT, 10)), - // rac ?? - new AbstractMap.SimpleEntry("rac", new UoM(Units.ONE, 1)), - new AbstractMap.SimpleEntry("erac-today", new UoM(Units.KILOWATT_HOUR, 10)), - new AbstractMap.SimpleEntry("erac-total", new UoM(Units.KILOWATT_HOUR, 10)) + // reactive 'power' resp. 'energy' + new AbstractMap.SimpleEntry("rac", new UoM(Units.VAR, 10)), + new AbstractMap.SimpleEntry("erac-today", new UoM(Units.KILOVAR_HOUR, 10)), + new AbstractMap.SimpleEntry("erac-total", new UoM(Units.KILOVAR_HOUR, 10)) // ); diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 1f3f4243a27e1..58a67040cbbfd 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -81,6 +81,7 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "pvgridpower2") Integer inverter_power_s; public @Nullable @SerializedName(value = "pvgridpower3") Integer inverter_power_t; + // apparent power VA public @Nullable @SerializedName(value = "op_va", alternate = { "AC_InVA" }) Integer inverter_va; // battery discharge / charge power @@ -184,9 +185,9 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "constantPowerOK") Integer constant_power_ok; public @Nullable @SerializedName(value = "loadpercent") Integer load_percent; - // rac ?? - public @Nullable @SerializedName(value = "rac") Integer rac; - public @Nullable @SerializedName(value = "eractoday") Integer erac_today; + // reactive 'power' resp. 'energy' + public @Nullable @SerializedName(value = "rac", alternate = { "react_power" }) Integer rac; + public @Nullable @SerializedName(value = "eractoday", alternate = { "react_energy_kvar" }) Integer erac_today; public @Nullable @SerializedName(value = "eractotal") Integer erac_total; // @formatter:on diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 1bc223f2827bc..178433ca4cd8a 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -33,10 +33,10 @@ thing-type.growatt.inverter.channel.discharge-power.label = Discharge Power thing-type.growatt.inverter.channel.discharge-power.description = Discharge power from battery. thing-type.growatt.inverter.channel.discharge-va.label = Discharge VA thing-type.growatt.inverter.channel.discharge-va.description = Discharge VA from battery. -thing-type.growatt.inverter.channel.erac-today.label = Reserve Energy Today -thing-type.growatt.inverter.channel.erac-today.description = Reserve AC supply energy today. -thing-type.growatt.inverter.channel.erac-total.label = Total Reserve Energy -thing-type.growatt.inverter.channel.erac-total.description = Total reserve AC supply energy. +thing-type.growatt.inverter.channel.erac-today.label = Reactive Energy Today +thing-type.growatt.inverter.channel.erac-today.description = Reactive energy supplied today. +thing-type.growatt.inverter.channel.erac-total.label = Total Reactive Energy +thing-type.growatt.inverter.channel.erac-total.description = Total reactive energy supplied. thing-type.growatt.inverter.channel.export-energy-today.label = Export Energy Today thing-type.growatt.inverter.channel.export-energy-today.description = Energy exported to grid today. thing-type.growatt.inverter.channel.export-energy-total.label = Export Energy Total @@ -155,8 +155,8 @@ thing-type.growatt.inverter.channel.pv2-temperature.label = Solar Panel Temperat thing-type.growatt.inverter.channel.pv2-temperature.description = Temperature of the solar panels (string #2). thing-type.growatt.inverter.channel.pv2-voltage.label = String #2 Voltage thing-type.growatt.inverter.channel.pv2-voltage.description = Voltage from solar panel string #2. -thing-type.growatt.inverter.channel.rac.label = Reserve AC Status -thing-type.growatt.inverter.channel.rac.description = Reserve AC supply status. +thing-type.growatt.inverter.channel.rac.label = Reactive Power +thing-type.growatt.inverter.channel.rac.description = Reactive power output. thing-type.growatt.inverter.channel.sp-bus-voltage.label = SP Bus Voltage thing-type.growatt.inverter.channel.sp-bus-voltage.description = SP Bus voltage. thing-type.growatt.inverter.channel.sp-display-status.label = Solar Panel Display @@ -200,8 +200,10 @@ thing-type.config.growatt.inverter.deviceId.description = Id (serial number) of channel-type.growatt.advanced-electric-current.label = Electric Current channel-type.growatt.advanced-electric-energy.label = Electric Energy channel-type.growatt.advanced-electric-frequency.label = Electric Frequency +channel-type.growatt.advanced-electric-kvarh.label = Electric Reactive Energy channel-type.growatt.advanced-electric-power.label = Electric Power channel-type.growatt.advanced-electric-va.label = Electric VA +channel-type.growatt.advanced-electric-var.label = Electric Reactive Power channel-type.growatt.advanced-electric-voltage.label = Electric Voltage channel-type.growatt.advanced-fault-code.label = Fault Code channel-type.growatt.advanced-outdoor-temperature.label = Outdoor Temperature diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 15e529fb328de..30d724783bee1 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -425,18 +425,18 @@ Percent of full load. - - - - Reserve AC supply status. + + + + Reactive power output. - - - Reserve AC supply energy today. + + + Reactive energy supplied today. - - - Total reserve AC supply energy. + + + Total reactive energy supplied. @@ -527,6 +527,20 @@ + + Number:Power + + Energy + + + + + Number:Energy + + Energy + + + Number:Temperature diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 56518962fb8cc..9f9bdee4672f9 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -369,7 +369,7 @@ void testMeterGrottValuesContents() throws FileNotFoundException, IOException, N Map> channelStates = grottValues.getChannelStates(); assertNotNull(channelStates); - assertEquals(15, channelStates.size()); + assertEquals(16, channelStates.size()); assertEquals(QuantityType.valueOf(809.8, Units.WATT), channelStates.get("import-power")); assertEquals(QuantityType.valueOf(171.0, Units.WATT), channelStates.get("import-power-s")); From ac313ae973473fc5e56c311ef60bf142be5efadf Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 8 Jan 2024 13:16:42 +0000 Subject: [PATCH 141/146] [growatt] corrections to readme Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index df4acf1d4959f..ec4a4136532f1 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -132,9 +132,9 @@ The list of all possible channels is as follows: | sp-display-status | Number:Dimensionless | Solar panel display status code. | yes | | constant-power-ok | Number:Dimensionless | Constant power OK code. | yes | | load-percent | Number:Dimensionless | Percent of full load. | yes | -| rac | Number:Dimensionless | RAC state. | yes | -| erac-today | Number:Energy | RAC energy today. | yes | -| erac-total | Number:Energy | Total RAC energy. | yes | +| rac | Number:Power | Reactive 'power' (var). | yes | +| erac-today | Number:Energy | Reactive 'energy' today (kvarh). | yes | +| erac-total | Number:Energy | Total reactive 'energy' (kvarh). | yes | ## Rule Actions @@ -154,28 +154,33 @@ growattActions.setupBatteryProgram(int programMode, @Nullable Integer powerLevel The meaning of the method parameters is as follows: -| Parameter | Description | -|------------------|---------------------------------------------------------------------------------------| -| programMode | The program mode to set i.e. 'Load First' (0), 'Battery First' (1), 'Grid First' (2) | -| powerLevel | The rate of charging resp. discharging the battery 1%..100% (e.g. 100) | -| stopSOC | The target battery SOC (state of charge) when the program stops. (e.g. 20) | -| enableAcCharging | Allow the battery to be charged from the AC mains supply. (e.g. true, false) | -| startTime | String representation of the local time when the program shall start. (e.g. "00:15") | -| stopTime | String representation of the local time when the program shall stop. (e.g. "06:45") | -| enableProgram | Disable / enable the program. (e.g. true, false) | - -Depending on the inverter type and the program mode certain parameters may be 'null'. -The 'mix', 'sph' or 'spa' inverter types set their battery program in one single command, so all parameters except `enableAcCharging` MUST be non-null. -By contrast the 'tlx' inverter type sets its battery program in up to four partial commands, and to omit such a partial command you can set its respective parameter(s) to null. -The permission for using null paramater values, and the effect of null paramater values, is shown in the table below: - -| Parameter | Permission for- resp. Effect of- Null Parameter Value | -|------------------------------------|----------------------------------------------------------------------------------------------------------------------------| -| programMode | Shall NOT be null under any circumstance. | -| powerLevel | May be null on 'tlx' inverters whereby the prior programMode powerLevel continues. | -| stopSOC | May be null on 'tlx' inverters whereby the prior programMode stopSOC continues. | -| enableAcCharging | Shall NOT be null on 'mix' inverter 'Battery First' programs. If null the prior enableAcCharging state (if any) continues. | -| startTime, stopTime, enableProgram | May be null on 'tlx' inverters whereby the prior programMode time segment continues. Either ALL null or ALL non-null. | +| Parameter | Description | +|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| programMode | The program mode to set i.e. 'Load First' (0), 'Battery First' (1), 'Grid First' (2). | +| powerLevel2) | The percentage rate of battery (dis-)charge e.g. 100 - in 'Battery First' mode => charge power, otherwise => discharge power. | +| stopSOC2) | The battery SOC (state of charge) percentage when the program shall stop e.g. 20 - in 'Battery First' mode => max. SOC, otherwise => min. SOC. | +| enableAcCharging2) | Allow the battery to be charged from the AC mains supply e.g. true, false. | +| startTime1,2) | String representation of the local time when the program `time segment` shall start e.g. "00:15" | +| stopTime1,2) | String representation of the local time when the program `time segment` shall stop e.g. "06:45" | +| enableProgram1,2) | Enable / disable the program `time segment` e.g. true, false | + +Notes: + +-1) ***WARNING*** inverters have different program `time segment`'s for each `programMode`. +To prevent unexpected results do not overlap the `time segment`'s. + +-2) Depending on inverter type and `programMode` certain parameters may accept 'null' values. +The 'mix', 'sph' and 'spa' types set the battery program in a single command, so all parameters - except `enableAcCharging` - **must** be ***non-***'null'. +By contrast 'tlx' types set the battery program in up to four partial commands, and you may pass 'null' parameters in order to omit a partial command. +The permission for passing 'null' parameters, and the effect of such 'null' parameters, is shown in detail in the table below: + +| Parameter | Permission for.. / effect of.. passing a 'null' parameter | +|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| programMode | Shall **not** be 'null' under any circumstance! | +| powerLevel | May be 'null' on 'tlx' inverters whereby the prior `programMode` / `powerLevel` continues to apply. | +| stopSOC | May be 'null' on 'tlx' inverters whereby the prior `programMode` / `stopSOC` continues to apply. | +| enableAcCharging | If 'null' the prior `enableAcCharging` (if any) continues to apply. Shall **not** be 'null' on 'mix' inverter 'Battery First' program. | +| startTime, stopTime, enableProgram | May be 'null' on 'tlx' inverters whereby the prior `programMode` / `time segment` continues to apply - note all 'null' resp. non-'null'. | Example: @@ -184,23 +189,23 @@ rule "Setup Solar Battery Charging Program" when Time cron "0 10 0 ? * * *" then - val growattActions = getActions("growatt", "growatt:inverter:home:ABCD1234") + val growattActions = getActions("growatt", "growatt:inverter:home:ABCD1234") // thing UID if (growattActions === null) { logWarn("Rules", "growattActions is null") } else { // fixed algorithm parameters - val Integer programMode = 1 - val Integer powerLevel = 23 + val Integer programMode = 1 // 0 = Load First, 1 = Battery First, 2 = Grid First + val Integer powerLevel = 23 // percent val Boolean enableAcCharging = true val String startTime = "00:20" val String stopTime = "07:00" val Boolean enableProgram = true // calculation intermediaries - val batteryFull = 6500.0 // kWh - val batteryMin = 500.0 // kWh - val daylightConsumption = 10000.0 // kWh + val batteryFull = 6500.0 // Wh + val batteryMin = 500.0 // Wh + val daylightConsumption = 10000.0 // Wh val maximumSOC = 100.0 // percent val minimumSOC = 20.0 // percent @@ -215,9 +220,9 @@ then } // convert to integer - val Integer stopSOC = targetSOC.intValue() + val Integer stopSOC = targetSOC.intValue() // percent - logInfo("Rules", "Setup Charging Program:{solarForecast:" + solarForecast + ", programMode:" + programMode + ", powerLevel:" + powerLevel + ", stopSOC:" + stopSOC + ", enableCharging:" + enableAcCharging + ", startTime:" + startTime + ", stopTime:" + stopTime + ", enableProgram:" + enableProgram +"}") + logInfo("Rules", "Setup Charging Program:{solarForecast:" + solarForecast + "Wh, programMode:" + programMode + ", powerLevel:" + powerLevel + "%, stopSOC:" + stopSOC + "%, enableCharging:" + enableAcCharging + ", startTime:" + startTime + ", stopTime:" + stopTime + ", enableProgram:" + enableProgram +"}") growattActions.setupBatteryProgram(programMode, powerLevel, stopSOC, enableAcCharging, startTime, stopTime, enableProgram) } end From 265282f35e4b6328b257760b7d4f70c9a4482610 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 9 Jan 2024 14:23:49 +0000 Subject: [PATCH 142/146] [growatt] validate domain, trust certificates Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/cloud/GrowattCloud.java | 27 +++++++++++++++++-- .../binding/growatt/test/GrowattTest.java | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index f780d72afd5a8..f90b1e54179dc 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -30,6 +30,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.net.ssl.SSLSession; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; @@ -86,7 +88,8 @@ public class GrowattCloud implements AutoCloseable { public static final String DISCHARGE_PROGRAM_ENABLE = "forcedDischargeStopSwitch1"; // API server URL - private static final String SERVER_URL = "https://server-api.growatt.com/"; + private static final String SERVER_DOMAIN = "growatt.com"; + private static final String SERVER_URL = String.format("https://server-api.%s/", SERVER_DOMAIN); // API end points private static final String LOGIN_API_ENDPOINT = "newTwoLoginAPI.do"; @@ -181,7 +184,7 @@ public static enum ProgramMode { public GrowattCloud(GrowattBridgeConfiguration configuration, HttpClientFactory httpClientFactory) throws Exception { this.configuration = configuration; - this.httpClient = httpClientFactory.createHttpClient("growatt-cloud-api", new SslContextFactory.Client(true)); + this.httpClient = httpClientFactory.createHttpClient("growatt-cloud-api", createSslContextFactory()); this.httpClient.start(); } @@ -217,6 +220,26 @@ private static String createHash(String password) throws GrowattApiException { return result.toString(); } + /** + * Create a {@link SslContextFactory} to validate the Growatt API server domain and trust its certificate. + * + * @return a new SslContextFactory.Client instance. + */ + public static SslContextFactory.Client createSslContextFactory() { + SslContextFactory.Client result = new SslContextFactory.Client.Client(); + result.setHostnameVerifier((@Nullable String host, @Nullable SSLSession session) -> { + if (session != null) { + String peerHost = session.getPeerHost(); + if (peerHost != null && host != null) { + return peerHost.endsWith(SERVER_DOMAIN) && host.endsWith(SERVER_DOMAIN); + } + } + return false; + }); + result.setValidatePeerCerts(false); + return result; + } + /** * Refresh the login cookies. * diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 9f9bdee4672f9..fba622b9d7f75 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -298,7 +298,7 @@ void testServer() throws Exception { HttpClientFactory httpClientFactory = mock(HttpClientFactory.class); when(httpClientFactory.createHttpClient(anyString(), any(SslContextFactory.Client.class))) - .thenReturn(new HttpClient(new SslContextFactory.Client(true))); + .thenReturn(new HttpClient(GrowattCloud.createSslContextFactory())); try (GrowattCloud api = new GrowattCloud(configuration, httpClientFactory)) { Integer programMode = GrowattCloud.ProgramMode.BATTERY_FIRST.ordinal(); From 04b7866a87d25c5c70aa4bb4b85e6f6a7bd3d3e5 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 9 Jan 2024 17:43:26 +0000 Subject: [PATCH 143/146] [growatt] revert prior; use OH stock createHttpClient() Signed-off-by: Andrew Fiddian-Green --- .../growatt/internal/cloud/GrowattCloud.java | 29 ++----------------- .../binding/growatt/test/GrowattTest.java | 13 ++++++--- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java index f90b1e54179dc..4d3eeaa758db8 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/cloud/GrowattCloud.java @@ -30,8 +30,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import javax.net.ssl.SSLSession; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; @@ -41,7 +39,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.openhab.binding.growatt.internal.GrowattBindingConstants; import org.openhab.binding.growatt.internal.config.GrowattBridgeConfiguration; import org.openhab.binding.growatt.internal.dto.GrowattDevice; import org.openhab.binding.growatt.internal.dto.GrowattPlant; @@ -88,8 +86,7 @@ public class GrowattCloud implements AutoCloseable { public static final String DISCHARGE_PROGRAM_ENABLE = "forcedDischargeStopSwitch1"; // API server URL - private static final String SERVER_DOMAIN = "growatt.com"; - private static final String SERVER_URL = String.format("https://server-api.%s/", SERVER_DOMAIN); + private static final String SERVER_URL = "https://server-api.growatt.com/"; // API end points private static final String LOGIN_API_ENDPOINT = "newTwoLoginAPI.do"; @@ -184,7 +181,7 @@ public static enum ProgramMode { public GrowattCloud(GrowattBridgeConfiguration configuration, HttpClientFactory httpClientFactory) throws Exception { this.configuration = configuration; - this.httpClient = httpClientFactory.createHttpClient("growatt-cloud-api", createSslContextFactory()); + this.httpClient = httpClientFactory.createHttpClient(GrowattBindingConstants.BINDING_ID); this.httpClient.start(); } @@ -220,26 +217,6 @@ private static String createHash(String password) throws GrowattApiException { return result.toString(); } - /** - * Create a {@link SslContextFactory} to validate the Growatt API server domain and trust its certificate. - * - * @return a new SslContextFactory.Client instance. - */ - public static SslContextFactory.Client createSslContextFactory() { - SslContextFactory.Client result = new SslContextFactory.Client.Client(); - result.setHostnameVerifier((@Nullable String host, @Nullable SSLSession session) -> { - if (session != null) { - String peerHost = session.getPeerHost(); - if (peerHost != null && host != null) { - return peerHost.endsWith(SERVER_DOMAIN) && host.endsWith(SERVER_DOMAIN); - } - } - return false; - }); - result.setValidatePeerCerts(false); - return result; - } - /** * Refresh the login cookies. * diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index fba622b9d7f75..6eef37c08d75f 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -13,7 +13,7 @@ package org.openhab.binding.growatt.test; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; import java.io.BufferedReader; @@ -28,7 +28,10 @@ import java.util.Map.Entry; import java.util.stream.Collectors; +import javax.net.ssl.SSLSession; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.Test; @@ -287,7 +290,6 @@ void testServer() throws Exception { * * configuration.userName = "aa"; * configuration.password ="bb"; - * * deviceId = "cc"; * */ @@ -296,9 +298,12 @@ void testServer() throws Exception { return; } + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client.Client(); + sslContextFactory.setHostnameVerifier((@Nullable String host, @Nullable SSLSession session) -> true); + sslContextFactory.setValidatePeerCerts(false); + HttpClientFactory httpClientFactory = mock(HttpClientFactory.class); - when(httpClientFactory.createHttpClient(anyString(), any(SslContextFactory.Client.class))) - .thenReturn(new HttpClient(GrowattCloud.createSslContextFactory())); + when(httpClientFactory.createHttpClient(anyString())).thenReturn(new HttpClient(sslContextFactory)); try (GrowattCloud api = new GrowattCloud(configuration, httpClientFactory)) { Integer programMode = GrowattCloud.ProgramMode.BATTERY_FIRST.ordinal(); From 0070b7f1799881ec82549f0e8c512fd62050e48e Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 17 Jan 2024 14:31:54 +0000 Subject: [PATCH 144/146] [growatt] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 28 ++++----- .../src/main/feature/feature.xml | 2 +- .../growatt/internal/dto/GrottValues.java | 32 ----------- .../helper}/GrottIntegerDeserializer.java | 2 +- .../dto/helper/GrottValuesHelper.java | 57 +++++++++++++++++++ .../handler/GrowattBridgeHandler.java | 2 +- .../handler/GrowattInverterHandler.java | 3 +- .../src/main/resources/OH-INF/addon/addon.xml | 1 + .../binding/growatt/test/GrowattTest.java | 9 +-- 9 files changed, 82 insertions(+), 54 deletions(-) rename bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/{gson => dto/helper}/GrottIntegerDeserializer.java (96%) create mode 100644 bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/helper/GrottValuesHelper.java diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index ec4a4136532f1..2d0a05a576333 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -7,7 +7,7 @@ This binding supports the integration of Growatt solar inverters. It depends on the independent [Grott](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor) proxy server application. This intercepts the logging data that the Growatt inverter data logger normally sends directly to the Growatt cloud server. It sends the original (encoded) data onwards to the cloud server (so the cloud server will not notice anything different). -But it also sends a (decoded) copy to OpenHAB as well. +But it also sends a (decoded) copy to openHAB as well. ## Supported Things @@ -23,12 +23,12 @@ However if a bridge exists and it receives inverter data, then a matching invert ## Thing Configuration -The `bridge` thing requires configuration of the user credentials: +The `bridge` thing allows configuration of the user credentials, which are only required if you want to send inverter commands via the Growatt cloud server: -| Name | Type | Description | Required | -|-----------|---------|------------------------------------------------------------------------------------------|----------| -| userName | text | User name for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | -| password | text | Password for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | no | +| Name | Type | Description | Advanced |Required | +|-----------|---------|------------------------------------------------------------------------------------------|----------|---------| +| userName | text | User name for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | yes | no | +| password | text | Password for the Growatt Shine app. Only needed if using [Rule Actions](#rule-actions) | yes | no | The `inverter` thing requires configuration of its serial number resp. `deviceId`: @@ -41,7 +41,7 @@ The `inverter` thing requires configuration of its serial number resp. `deviceId The `bridge` thing has no channels. The `inverter` thing supports many possible channels relating to solar generation and consumption. -All channels are read only. +All channels are read-only. Depending on the inverter model, and its configuration, not all of the channels will be present. The list of all possible channels is as follows: @@ -233,7 +233,7 @@ end ### Example `.things` file ```java -Bridge growatt:bridge:home "Growattt Bridge" [] { +Bridge growatt:bridge:home "Growattt Bridge" [userName="USERNAME", password="PASSWORD"] { Thing inverter sph "Growatt SPH Inverter" [deviceId="INVERTERTID"] } ``` @@ -259,12 +259,12 @@ Number:Power Discharge_Power "Discharge Power [%.0f W]" {channel="growa ## Grott Application Installation and Setup -You can install the Grott application either on the same computer as OpenHAB or on another. +You can install the Grott application either on the same computer as openHAB or on another. The following assumes you will be running it on the same computer. The Grott application acts as a proxy server between your Growatt inverter and the Growatt cloud server. -It intercepts data packets between the inverter and the cloud server, and it sends a copy of the intercepted data also to OpenHAB. +It intercepts data packets between the inverter and the cloud server, and it sends a copy of the intercepted data also to openHAB. -**NOTE**: make sure that the Grott application is **FULLY OPERATIONAL** for your inverter **BEFORE** you create any things in OpenHAB! +**NOTE**: make sure that the Grott application is **FULLY OPERATIONAL** for your inverter **BEFORE** you create any things in openHAB! Otherwise the binding might create a wrong (or even empty) list of channels for the inverter thing. (Yet if you do make that mistake you can rectify it by deleting and recreating the thing). @@ -291,9 +291,9 @@ The installation is as follows: - Copy `grott.py`, `grottconf.py`, `grottdata.py`, `grottproxy.py`, `grottsniffer.py`, `grottserver.py` to the home folder. - Copy `grottext.py` application extension to the home folder. - Copy `grott.ini` configuration file to the home folder. -- Modify `grott.ini` to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; not run PVOutput; enable the `grottext` extension; and set the OpenHAB `/growatt` servlet url. +- Modify `grott.ini` to run in proxy mode; not in compatibility mode; show your inverter type; not run MQTT; not run PVOutput; enable the `grottext` extension; and set the openHAB `/growatt` servlet url. -A suggested Grott configuration for OpenHAB is as follows: +A suggested Grott configuration for openHAB is as follows: ```php [Generic] @@ -310,7 +310,7 @@ pvoutput = False // disable pvoutput [extension] // enable the 'grottext' extension extension = True extname = grottext -extvar = {"url": "http://127.0.0.1:8080/growatt"} // or ip address of openhab (if remote) +extvar = {"url": "http://127.0.0.1:8080/growatt"} // or ip address of openHAB (if remote) ``` ### Start Grott as a Service diff --git a/bundles/org.openhab.binding.growatt/src/main/feature/feature.xml b/bundles/org.openhab.binding.growatt/src/main/feature/feature.xml index 75e9fd1cccd54..735964b4dbc80 100644 --- a/bundles/org.openhab.binding.growatt/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.growatt/src/main/feature/feature.xml @@ -2,7 +2,7 @@ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - + openhab-runtime-base mvn:org.openhab.addons.bundles/org.openhab.binding.growatt/${project.version} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java index 58a67040cbbfd..a8fc983b5581a 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/GrottValues.java @@ -12,14 +12,8 @@ */ package org.openhab.binding.growatt.internal.dto; -import java.util.HashMap; -import java.util.Map; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.growatt.internal.GrowattChannels; -import org.openhab.binding.growatt.internal.GrowattChannels.UoM; -import org.openhab.core.library.types.QuantityType; import com.google.gson.annotations.SerializedName; @@ -191,30 +185,4 @@ public static String getFieldName(String channelId) { public @Nullable @SerializedName(value = "eractotal") Integer erac_total; // @formatter:on - - /** - * Return the valid values from this DTO in a map between channel id and respective QuantityType states. - * - * @return a map of channel ids and respective QuantityType state values. - */ - public Map> getChannelStates() - throws NoSuchFieldException, SecurityException, IllegalAccessException, IllegalArgumentException { - Map> map = new HashMap<>(); - GrowattChannels.getMap().entrySet().forEach(entry -> { - String channelId = entry.getKey(); - try { - Object field = getClass().getField(getFieldName(channelId)).get(this); - if (field instanceof Integer) { - UoM uom = entry.getValue(); - map.put(channelId, QuantityType.valueOf(((Integer) field).doubleValue() / uom.divisor, uom.units)); - } - } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) { - // Ignore exceptions because they never actually occur at run time.. - // - NoSuchFieldException never occurs since we have explicitly tested this in the JUnit tests. - // - SecurityException, IllegalAccessException never occur since all fields are public. - // - IllegalArgumentException never occurs since we are explicitly working within this same class. - } - }); - return map; - } } diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/helper/GrottIntegerDeserializer.java similarity index 96% rename from bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java rename to bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/helper/GrottIntegerDeserializer.java index e357aee800ab2..a105c515ac56f 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/gson/GrottIntegerDeserializer.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/helper/GrottIntegerDeserializer.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.growatt.internal.gson; +package org.openhab.binding.growatt.internal.dto.helper; import java.lang.reflect.Type; import java.util.Objects; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/helper/GrottValuesHelper.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/helper/GrottValuesHelper.java new file mode 100644 index 0000000000000..4b33af1167175 --- /dev/null +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/dto/helper/GrottValuesHelper.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.growatt.internal.dto.helper; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.growatt.internal.GrowattChannels; +import org.openhab.binding.growatt.internal.GrowattChannels.UoM; +import org.openhab.binding.growatt.internal.dto.GrottValues; +import org.openhab.core.library.types.QuantityType; + +/** + * Helper routines for the {@link GrottValues} DTO class. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class GrottValuesHelper { + + /** + * Return the valid values from the given target DTO in a map between channel id and respective QuantityType states. + * + * @return a map of channel ids and respective QuantityType state values. + */ + public static Map> getChannelStates(GrottValues target) + throws NoSuchFieldException, SecurityException, IllegalAccessException, IllegalArgumentException { + Map> map = new HashMap<>(); + GrowattChannels.getMap().entrySet().forEach(entry -> { + String channelId = entry.getKey(); + try { + Object field = target.getClass().getField(GrottValues.getFieldName(channelId)).get(target); + if (field instanceof Integer) { + UoM uom = entry.getValue(); + map.put(channelId, QuantityType.valueOf(((Integer) field).doubleValue() / uom.divisor, uom.units)); + } + } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) { + // Exceptions should never actually occur at run time; nevertheless the caller logs if one would occur.. + // - NoSuchFieldException never occurs since we have explicitly tested this in the JUnit tests. + // - SecurityException, IllegalAccessException never occur since all fields are public. + // - IllegalArgumentException never occurs since we are explicitly working within this same class. + } + }); + return map; + } +} diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java index 823d6fcba2376..e10dc5e006c99 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattBridgeHandler.java @@ -21,7 +21,7 @@ import org.openhab.binding.growatt.internal.config.GrowattBridgeConfiguration; import org.openhab.binding.growatt.internal.discovery.GrowattDiscoveryService; import org.openhab.binding.growatt.internal.dto.GrottDevice; -import org.openhab.binding.growatt.internal.gson.GrottIntegerDeserializer; +import org.openhab.binding.growatt.internal.dto.helper.GrottIntegerDeserializer; import org.openhab.binding.growatt.internal.servlet.GrowattHttpServlet; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; diff --git a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java index cbc7fb25bad4b..eeb960461f5f4 100644 --- a/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java +++ b/bundles/org.openhab.binding.growatt/src/main/java/org/openhab/binding/growatt/internal/handler/GrowattInverterHandler.java @@ -27,6 +27,7 @@ import org.openhab.binding.growatt.internal.config.GrowattInverterConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; +import org.openhab.binding.growatt.internal.dto.helper.GrottValuesHelper; import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Channel; @@ -129,7 +130,7 @@ public void updateInverterValues(GrottValues inverterValues) { // get channel states Map> channelStates; try { - channelStates = inverterValues.getChannelStates(); + channelStates = GrottValuesHelper.getChannelStates(inverterValues); } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) { logger.warn("updateInverterValues() unexpected exception:{}, message:{}", e.getClass().getName(), e.getMessage(), e); diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml index c848fe79b96ba..2c52b5e92ac0e 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/addon/addon.xml @@ -6,5 +6,6 @@ binding Growatt Binding This is the binding for Growatt solar inverters. + hybrid diff --git a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java index 6eef37c08d75f..474092ae622e7 100644 --- a/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java +++ b/bundles/org.openhab.binding.growatt/src/test/java/org/openhab/binding/growatt/test/GrowattTest.java @@ -41,7 +41,8 @@ import org.openhab.binding.growatt.internal.config.GrowattBridgeConfiguration; import org.openhab.binding.growatt.internal.dto.GrottDevice; import org.openhab.binding.growatt.internal.dto.GrottValues; -import org.openhab.binding.growatt.internal.gson.GrottIntegerDeserializer; +import org.openhab.binding.growatt.internal.dto.helper.GrottIntegerDeserializer; +import org.openhab.binding.growatt.internal.dto.helper.GrottValuesHelper; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; @@ -195,7 +196,7 @@ void testGrottValuesContents() throws FileNotFoundException, IOException, NoSuch assertEquals(65503878, grottValues.total_work_time); Map> channelStates = null; - channelStates = grottValues.getChannelStates(); + channelStates = GrottValuesHelper.getChannelStates(grottValues); assertNotNull(channelStates); assertEquals(29, channelStates.size()); @@ -355,7 +356,7 @@ void testThreePhaseGrottValuesContents() throws FileNotFoundException, IOExcepti GrottValues grottValues = loadGrottValues("3phase"); assertNotNull(grottValues); - Map> channelStates = grottValues.getChannelStates(); + Map> channelStates = GrottValuesHelper.getChannelStates(grottValues); assertNotNull(channelStates); assertEquals(64, channelStates.size()); @@ -372,7 +373,7 @@ void testMeterGrottValuesContents() throws FileNotFoundException, IOException, N GrottValues grottValues = loadGrottValues("meter"); assertNotNull(grottValues); - Map> channelStates = grottValues.getChannelStates(); + Map> channelStates = GrottValuesHelper.getChannelStates(grottValues); assertNotNull(channelStates); assertEquals(16, channelStates.size()); From e2263d3a297d6dd1b5c5efcc96544cec237bbd77 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 17 Jan 2024 14:50:37 +0000 Subject: [PATCH 145/146] Delete manufacturer logo Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.binding.growatt/README.md | 2 -- .../org.openhab.binding.growatt/doc/growatt.png | Bin 12018 -> 0 bytes 2 files changed, 2 deletions(-) delete mode 100644 bundles/org.openhab.binding.growatt/doc/growatt.png diff --git a/bundles/org.openhab.binding.growatt/README.md b/bundles/org.openhab.binding.growatt/README.md index 2d0a05a576333..e960d787437c8 100644 --- a/bundles/org.openhab.binding.growatt/README.md +++ b/bundles/org.openhab.binding.growatt/README.md @@ -1,7 +1,5 @@ # Growatt Binding -![Growatt](doc/growatt.png) - This binding supports the integration of Growatt solar inverters. It depends on the independent [Grott](https://github.com/johanmeijer/grott#the-growatt-inverter-monitor) proxy server application. diff --git a/bundles/org.openhab.binding.growatt/doc/growatt.png b/bundles/org.openhab.binding.growatt/doc/growatt.png deleted file mode 100644 index 0512c4e02603b2d808c76ec4478839b446171cb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12018 zcmdtIXE>Z)7cY)sq6R_qlBm(!5PkF>Jp_Z%yXc*amPhY{L3ANRuhEH!&geZlQKLtj zGtc|}|KHB1bDi_yoDVbiz4vwRy~w|$H2fKQdLpV#lXN!LcjmS z1)~3dP&99#ADEuH%5oT0V|4rI2DY87rYr_VZ6d)v6bIeLd$01&69ePve}0%^78K>^ z&L>`~nuid};>?fH!T zkM&`eMo^B>fY5-|orbjwT)}-t?kl-(@-x- z2q-{5UPc4ubjTGIpAki%dt+E=a20jPDO80?VhTApQ|y_@6?ODkCc)@0%Tx+s+URDo z!vCIu$RtqGlVIQN)o?D4ii4DfW#06u3`2FZ6~YA*Fttu2CNIuCd5aW8gM&)qwpvuu65Uk5|S9&Fqkp{1Euqt5M}B{=ws$ck5vl-3!LN zTgt2qRGy8`YG}+zVXsVjyxpVb)0uWwI}^j^uunW+&qs?pm}0!G5;>UgP{=45>553+YbEkiQ9WRU9=v<$G|kM z45=%W=E5LMo-&!I+CuLjS5|slt?t!B<+ltE{ik+oUTv_I)%8n$A zgd=|(S@wj)^dI02zO#dUj|?bU^ukE_2OI)D+S+syAP5cug3b!yQP0p&3ZtryRf2|VY0WMv={7QgnhM5A42L75D8NF%x*-y&!=1<=b<~>=3D=N6})A7@9|#_W%c1zNADgRP4xLpc>H+ z`ShH2x1XP4i;9aXdQhckN&8FywoDztxgOtaT3>y2!u0byP2=WzbaFW6Nc;HE@hJKk zJ<%r`62SiN{iOSE#G0=l#O-~tHvOT@6fPzXFUb{&oNQLW$5Jh9cVB)CE)!Lz}D)IY^SC0%oyrt}c2tSeF7bk)_N%H}Z48EN2`( z9?D?pdSn(0_Afyj)D;CUM1sRcQ+-?EhU^GU-|Fm#~1W#HlLL=_!k^j`mJJz9*H-N z45I*I-u^gF3}_<|h#3XfJfHqFlQYw*co-i_oN0Z=tO>*ET3eKv9M7_@EsHAHmz@nKPUTsM&JMhtaovMp~%5uHhTe zwKNIe;~V9uX4jQ(+bBxKNc2<-dVgamwKpMjq_I4%;&%iN`y2t>CWD0wSa$`6rVHG| zY9Tj`_fV_yx>8oNQLEF;fxeH#B^7N;TOCVxsD!2F?VfOZ5Ht43Rl?KO!0W2e?{93M zCi%K-v67r68HJr39e9oz+Sz>`9Cta9Xcc+o9XR*#Vv{OwP-X}gG%^qYs% zxZ{;JyGPoA(=}hZwSKVQ&QG$lC@;Z56Z19s^Q9V1W($+LB1k{n#4b6kAmc_WvEzL@?gGt(k@-mhnqmKp*)e{Syj~CkLu>`nID8R>q)H*w)sT z%IaY1k0j@5B+~LsTR%cdmm!Ps_(wdg%~F%gPFF#1uiQeNP3&JcT9B~tPh^R@W{C9t zsr=yZ@HxtoeHkFEm@6J^{I=T6rA# zcLgc^?24^|wAps974&`F{vJ(MXVHU|B@@~?Xqe^8xI3E7Uf*$%NH1agz$@pM=YH~@ zF}brJ9DH}BYG{}NJz4Fj3q^&TUvmuo z1m7}V4t?UBmiA))r}sWgCM_%~3po=w+*+K(BXxV9yc|5imS@mz!G`yIcAVaee#dWN zHRxQE41)ysM?5_jpCqbX@_cD+Xy9P(E>Deb*$vw_P0n39`&wv{n*1gxUCugsijAzdMmQq62OzV_3wk;|~3 zv~KM$s^h4j$i|t>$)auF)xTElcY=#|(p-jLZ`EFL=bC2MI669xh%uY{_itV1`mPF~ zLcEt>AM6y|-UfI+o{vkh*Gn-gp9UQ-*lCJ?{rdIs^Ty1A=kC_!oVn=S&p%;@c-0dh zcI}FS1Fy4uf&NkN5-wNiC5zv8<|U%#?O~ztPYO$o~vJPp9+B^~)a6JZj*X)`rc{d`&79akE7=b}c zl@kB{=~%9$f8BI|tZm1&6`1Mo#gtZ;E=Y@=3iqsL6{$iN>7*<`g5B+DrX3Y_lDHTh z59cv=mF}|aS34&>_Vq#ZVkFoMgC`o z{#j5~H+VJhL~1I>p+W&5`}OcPp7zna*ZX`&Gu1a|X@1`Nm}}Mlo_2gtiJqSR$Gp4N zWLo4RE+L)+IVI(=YIV{Fr+H~+&19nD>a~hT(>S++?>YBwgMYpavO$I*(ZDc9X0w)9 z1B0D4+!hfzS`q13Pgg%aex&`W+ACQa5nx!;xq%rT<1U?8fPSfFt}UX6gi!iSZ7Q$> z$*K0a?A<&rt4c8rS5FvjF&z(yD40er73;Ndpfb4p|Vl=SMcTE~qmdnX!CcL}e5&7rE$?jOL z=zvl{BpL1cHLTP=C!Eb@nqNo$dzVg|Y(2jQDb7Gyj5Cwyv0>JrCxTi5is~1z^5UR+6GD(0ke1OoyQM1lU@ef)YOadr1sjJ zkmK|kmpk$abJ)T79}QegPK*++B{p2DpB2@eoIIpqd{y-QU|aUIXAmN6P464YTPG69 zD!*NvaaJeb2IPsRVGD4T)?}SJKP~qB%a@8#iEzYALhS36rUhPp5^_PTb%~AEwze-4WBBCNARPaBq?2gE0M- z>t7-R?v9!tv`Q#Hjk;HpgbAOv$7XavW04=lbm_bi6@Tgy!TB&QV3xY{c(083>KfWiCu*7PYn~Vn`efw)-kXF0zo@ZZ|&Uphp3{C`)9i(}9RpuVl z6xb{vvaEhj8_wwr*xlqOnNB!e>S?`^Vm@ifySZL$Jh<12Bp5B*%kK5RIWD6wbR$DW z{mDpA|C<+Assi-*T*1@i&!`^rsQcTEq3-FMv?}zLs!`ZyJj&kVmoM*AN^H0L@FD)U zlp4F0QlbF;uToTx5ZRJl1mliwLvsKqVSCun&tTtGY`dduPelP-GfEuMVDY+asOWdX zn0+zK!p`JU36K1`bf(}zoTRg_%?z|Q3wGp#2=3-j36X@9y^cy&66sB(>%khwkO{f> zooLA90rb?5j*p85i`rW%)HHQ;aqw%L<1>`G6k5lklz;ab{Y!Fld*~4yBL_6xyDhg;YE^25(ux%i-gK$i@1sSds2(x_JAkyB0Y)e#kUw6 z$&P7n@&2rCNMrjCaPThenB$%-lb>A2E}}5UzbCQZ%iO}*G!fQHwl;+-K;6|Q0rI{iyI84qM#D?-S|{UJ!NzLrT!uaUb^Y>)*NNtBw^KVGFCA|%|#el-E@j<{9aTz_E;jY&hM z(ZFZeE{^v2AQHkAMRQWW;lL_;ph*LVXbPULe$$pS6Fuazu9W+n!nd~5Kym4F$QS)m zITQ}Ud%`Q}NS8AmQV`McT6AtCR951qIGRk8@tGo#h#0NP(#n*>+{e1@%_$V8Z`*=9 z!{>>S4MFi}n&n<3eMWZnDF0h&JxBhb1u#DC&X=fPSXTsmPGcZiZFF#>q@tcC8Q6)=D5(od^M4apZv8nc6!mb&O9(bbGgYUlo zUXuVm8dU(@Y*{n0AFyTTVu7q`w(HL*OR7UpoxlG(nPO@x?C>i?IU0$N|QHbN|u7EQ*E;1REF_FL|a zY0Tg=hsUO;+bS9z^3+;xtJP;;Of)CwMsLE1(PwRd2~O6CCbch8*HvtkyinXuKSH8@ zx@7QIhDVk(kQ%udHP|Po8S3BIg+k_6p9fZ0F~2&rjxq>I?RmE8j;HQw)#|fFaOaDi z&A)KA>^Yv*!CYg*4rgv&>FRrnc6#5Q6alY7_Vx!QTh{;D@3Yri|FJArIp=nXOb_Yn zWcT?e?q%i+5zM|F6{oq%o7H!6JyM#4sxX&}c1K+Zrp9wBbN$B|P)-sh}b!-9y z>p$bK8oi@65p&w@C$fw>dW(agBix!z5>C>3gEAc*u-MrvQ5|bc`o6vF(hXGPxMU^Z z(?bV|?dv%1R1rR|U^ZabhpTws}$%^3i%!FJU6(wK$= zq`c7l;@v!0RoxsAXj){Q7hOXvD#D9I65mV{ycUr&v)Cxpbe*q}T@Ad3)aR+rOKjtd zX!iMhHZBV`b zrob>FJ8kjrPaPM<2CXfb=38(jj-a1~d5&7ZAKrC^bl^SB!p%Oh-*@^Y(b!H-rq}RHh0a35a><`yIe(3%2NfOv(FC|>G{V5{ zalM?5Iedw9*>-}YL<;PaU~Sr#fJ?;w94-MU}B+SLT@d8vMVa~?M!vk8vb8>L9Xw$nOQlPjMnFuN?q5p6_+>}^;--`o^cMhk~ zH!}+<-qk=;O@+}_`a{rS(tQb#@L+c*p9Fh7BPw3t;?epftomRoZc2phufP}yp9O!o zXOQ&c*HxCl;=V%8B4;wiy}yK(wrPj0)`Wy3T*ZA3=hr{~;`MU;{QCeOte57p|L9JQ z3nsIL$`<621x|vOx2>7_b95pO0JIFWv;)Z`KK+1u(IU-{?$ocep*^lV zyuMlRM0AJqLfy~Yt?__4YLG{9`{SaMZNzNF=8r)xW+ep?=B>M}=+uzo!^o#7fgZ0_o*?)h4;k*FJ9}cfhPj7{an>GQ@km`Jc_9ve*x^j8D~ffp9OTxf96+uP$OqWUu2PH`W__6B{YpR#V;hQwds zOAI=&gkOuNFIc?>?ewyQFs!YKF@B zP!h3#&{$(mfGL2F4@-YTfEG|*U7hiVRD9&mSy9)^l?e^6pXS$C<7luUmk58&ZY)P^ z3@vt+uDX`hP8_vuVt}FIej?V*8=W18{J;2~Tn)mQpY9$3PwusrUQL3Nswvwoq&LHf zi|2(1sfn1>_YH$8m0%PFnnttyZnM~;M zvKaOH%(upa5=(U4Xs#*`m>aadX-|F734JZ zm(KP-v%TgqZ_<#yNPzzH3PFj)Gw{mzK>J~bb42w$=`BdZ^J9vcc$3@O@n;M6w9G~? zVT-VtnVH8dpKrmt37*|**sM#(u6GD5gdhJ$7Rdf{oOmw7svEocSNtTxick+r7l>pa zM!_o{vCw_+ltZbUXF1%5)8eN)!Kl*>#C}d)QMTX?jAbZB`?GUMz;VL!Mf2Wx2|CJ^ z=|7ACZaiJIoAlCNo3KDUONlI{i#wO0Ka+QGboP20Ri;WOefa2_uh8amWbFT=mMsh~ zmA4Ul+Ni-x7q%ms@uFlPkuh~Vm$V*!>33TP<7b$`c1%Xq3`N^4A7Do4u9=_R$MNokjs}`mecAv zb5Lxx9u~RNra-#mXmJq_cjdG&qEfNoN}K;IAzWhN8t+1e32*v2I%+Ugi?jCR+}Szu z>0Qlw+g_?W1;S1+m%1A7M}#c7u{5a0)wuLPY7(qFya29R;}Qko@|?ETwCr{L{5nP> zGjoV`yPhKWR`X6n78`3#BbyB7k`>CnT0p;RL?BY(KH}16VeLnY?{MSnO=gjk-S*r| z$zc1`kduzgkCq+!T}1#y5rAhGW2h#tr}XWOl;IINmT<~FhK^(kFmI+i3w-SLhD)gZqMETW zIrxUg2<^+Il4IcyVn5~6*4qXx@JN@EL3*Dx^`>yUIYh~Uz}Tb=)u-Wv_Dl=u^)N7N z#;%=Y0m%s8O~Uk=Ez~pC~U(@_?WT zp9Ryn<^EGb?c}qnSNG66<#7UA!X>6e>pEQ3bgv=4eh0JQTuP0HDS^WI#r=Cz^)q|% zIu|a{V2aqoJ5F?fPqSDJ*i@ejiY#T}PP8;R7Yo^l$n$VqmoBMbE&O}5S9|#U)Kn2L zSzRt*PlRpoz_S}#4(#HfEB9X`uv~B$TJ$*q%D5I!v{t0c@l zrA*qK!I;8}YJA^=b-9e;^|q)9l&SFl)BiSc8%=XxSX1*$tKE3iU1r;F2C2j1EqXOD z7BwZK*QHKJ7y--QDnELXQm&o7&>Y$oFzRIi$ZhdClTRhEJwr1v@R#7KzD37`2HI-O zJ91&9qAGp(3A!Kn(?^hgHPQ@V&;&KzJ(=)F}np~P?1aaVZF`o^9G3{U1OtnsX5Su z(rIq^xp2}UCo=#RL%SO&ds1GF)1v|Qe7h3wmn|$vdc}v1deojj4>25dTb6V{V`EY3 zsPOnR0{m&G529O>Z)ERG1^E&1 z*|(l%g*AxUtG^00AqM8=I{*_4#7#)k63YM)5lb676L2ik!po)p_=YGV>w|6KZ>LFL zqgeG^V(~4773+8UH&URygiRVerow);6)nlz}is%ci{K&C;+y48OOq z=TcojRJOfgVKQ*}naz?8q19iO%w9X9jk|+zPn5F9+2+A+?K+w|q2||U|JFD(@#&=N zlO;PTYyat=I<|?i3ec=1uT(&7gM_1a^q0!YhE+yDpaTm&h+#y1QkwSk@$O{!@<8-n zs1UFIWYY-Fs)s%Y4Y+~Gm8BPLT;}@WP`XNqOS1BoZ;Amxd%PQol~LWrc9rJ?g71*@ zS1{`;4xr!x^L4P}1hM5(RibUqR_Hd+~6{7lCYyDm-RpaEBiuV-Q{)bT^lCq53BZZJMLj%Qa|>Sb!l@83`N_nfahQ;f@8Ok0CxITuBS3lMWQeOaNj6Hh&8qv~M!*xziu zZ<$Kmg?Ch)s06=2*y&~Sy$y7dA9y2is|?WCb2~)BxAaIxc1n|TrPXsT0NOwEsYjN- zs)qidCdEyEot}`EA%~DnyReCLdZ_omr~eiqc28?jy{a@isSe_|jTQ6PQhoE-JaYK{ za3MiDmB$WsZ8#rvMOjyOaWc9#xt=)g>@- zb{_!~;{6FkZDqckxDm#x4lfHO#$Ecj+>{!C{LUx7F`g$={V0{(5qNc5-o_;(W3DMg zQRd&mYcBtv%V>*`g@lc&Dh_&b!Jb~=%tR8E`|VgO!D6p26Dkg6^jI47!QA6|Ph<2H8=i7^V4KL%EZ3Yk$9nk-U;#`qDg~k|69)Pes8j9rt3)%e{jIow)zBW zUC{YDwxD%kN(96LOpAOttb~4!<;M5fjR*l_z5FEP}kV_ zcOG@#Cp4<<=ysUC+$!mA^<g}Z2R}yiEqn8lb4DKQY z*M_9QJGqS9XKj6zgmZbk?M2j{AV%HOMe7N6L!apE{a2-H2A=0v%VCHI)v8O z$19Hu3wpCe*fV#fSE+S7(=ru3DM7jG7EFG}P1%e%`H-ONPg1ic!;bj0rC(~_ulK6- zaO>Q3a>4lwr+l|Z|G3dGH;}?vRYg;VpHDONB1VvSLgNrDIuVjsU8C+V-+47{68pgX zOf0VJ4<6;~FvZr^|59YpJ~(>jQ)wbdM~K}U7PQzn1#5Co6D-25uY@3yF_g5R8^(0K zypitMp29r~_(C@jCsUg9ozZi>xKiVjRjZfz@FpN)y2i7(g8zNDO`GN6!n2ZUR-Gzf z7{Xp;%aLh5U1*7h9{>wC>oR<`G@M60EpIP`X|lo_1MOd3w`8Jve(+6HTs(48dUa}3 zcOT6>6iX^MrKiq-b) zBVMo`Rb$k68;!8irp>pp#f$x^nFc)==|5CZxT>bH-h8Rm;4jGciQg^f$z_;AN#GSJ zUzX~SANkqs8Lzn19?cx9j^I+1%3nf#p)FLnVM_`05;>wzu>0k65wu_LDS7C3$F4@YzQ3rv*5@Vgj%ymEu2q6vsmhLX^}e6%#sA} z+6LiXzaE0Zy~JUKVK|T8)SD-W7n??tuXUP+Z`i|n70WY-$$!-sV2YBIRd<evfu!K*CB(#5{L0S$hQ9 zMcgCELkMp~)vEWL6Y5Z6oiaY;aZYEJOqC%Okn!i-DfW7tTGO4u$AJ^$; z$`gU^>%Q7Aza3HdKH}*3D;0b21}K~p3!$(hT0Plo4}dr?cpuY z<;x}B&3lMA%HT#JEw|#FQu{!BREk7(Eo@gsiSkr~JK`wYe6S0dgmCZH+bUxlxK^!! zx_ayst`LzVBe!5o`j_xPVVfaxG@EiYjOkG6_byw2aDp)39jTKwol7Gk?BBujtQivc zTT`B0Dd=LNGEDCp0a28RN2O1SX`9J$dPLW))n`}`1oxjz``tzAES{u9^&us7cU+0n zR>_YF{4sdLeQo%f;3_gvfKtGmWOf78Kp$#0FhcEmw=VNksr|lA?;HuJhJW+eRs((2 zcn>0(h8Inr^->{$&&qb!gB`wk>o!4dMnAaFAN;vJH2)R*kj8s#_{@vpv|_9up69j=3%i58QjwTS zN7Xf$o<96m(y54q3K)0>1gZ_?GX%hr66dU_gp+^P@F#(Bb3k)4sSgI5BMeN&AL#Ah zray+$eGS$^EcGc5On{LG^R=6`C=BNwcpa51!sTeCO;lHCmS!Bg5TCwZ2T^Mw>F%_) z8vMIa5C_J%v$neWVv}C^n)MmlIOFtVtulSU?Zm=FrI-v(V!qG>F%g`w?L5UeVUJbeQ~X?p#rZXz`Hj{9EW+$oW&JJYwcA zu>4ogBg0=uIfj=3!!TL)nmrb>1nB#a;AfE*i+<wCS#~Yp)|&<5L})cC7iqJ zYN<=Ig?E3#(nPk0(i7}WDv{vB2wTc~+!{@XX)p;eWN*Io>OPX-(csCODtTi8OhGc} zUiE>!Fm`^+PB#mIra#rqY2oIVkwK=!vMkn<$%IGva)0c07A_mb3>b@IYj;35Ho=hz z784t5rv-ZdRR{P%1JHsaE7Y02)L=g_53lbQsz=- z+hQi$V|_@;!CvO2=!xNQ_-f$eOkp%|qcpqOb?}eXfFG3qz8$QK?2eJi_UWaySGiK6C5;bp!fTxAXS15-1$8dj ze`)uQx=wPZwsH2Kto{wI^(kvw<-J>MgrG~aeu!yts9I7?Y_h*Gf(dMEYHB_%BpTjS z{ghE6D|lP?^G-G&J5g1esxP4*hwPdXHu;N6d}|<%+6wO6eUIm5mf?4e0pl`Zbus}# zIxAPN^_LypuE*o5W}8GnNBS02o;fF-Y{kTYZqKajROXY;^91Ucm|aS8DvO)>SnLu9 z?3wN1jO@FQc2sbg5vLE-O#fIeq%a-j|2_0IL%Ulr8}P)FC`mf$p!vh1I zE^EB?cB6uGjG{i3{(Q+FyG>6~uSI494cGkdT(X1$o3booOMC?>&S17sa;D}$hnFsd z6h8=qg4|MxyWB>$`tC%}VC^r*mpa<5stojr-T?}tnk}&6KlKzuM1B!}8?#a$*u35i z1MC>7sz$%Uv!qUSej>-o08OJtkpHfzschh$*Z= zCmhz3pU6=~G^~r43YbJihS7C0UX{o)@JG-CWAFmqM{bD8@PTZMtJg}>?}N7he|y}3 ze_LqWJ6f&(k5t(Huh;YvqHCLR-Ie}dEPwu=m3AYUyRK1~Prs8$GJdrgMwg^xs4Biu JsFJe?`ybdK(8B-# From a9912f48a5915b7c0a5dac50d888264fca507c99 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 20 Jan 2024 12:40:43 +0000 Subject: [PATCH 146/146] [growatt] adopt reviewer suggestions for shorter labels Signed-off-by: Andrew Fiddian-Green --- .../resources/OH-INF/i18n/growatt.properties | 56 +++++++++---------- .../resources/OH-INF/thing/thing-types.xml | 56 +++++++++---------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties index 178433ca4cd8a..6c11e744716be 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/i18n/growatt.properties @@ -5,9 +5,9 @@ addon.growatt.description = This is the binding for Growatt solar inverters. # thing types -thing-type.growatt.bridge.label = Growatt Bridge Thing +thing-type.growatt.bridge.label = Growatt Bridge thing-type.growatt.bridge.description = Bridge Thing for Growatt Binding -thing-type.growatt.inverter.label = Growatt Inverter Thing +thing-type.growatt.inverter.label = Growatt Inverter thing-type.growatt.inverter.description = Inverter Thing for Growatt Binding thing-type.growatt.inverter.channel.battery-display.label = Battery Display thing-type.growatt.inverter.channel.battery-display.description = Battery display code. @@ -25,9 +25,9 @@ thing-type.growatt.inverter.channel.charge-power.label = Charge Power thing-type.growatt.inverter.channel.charge-power.description = Charge power to battery. thing-type.growatt.inverter.channel.constant-power-ok.label = Constant Power OK thing-type.growatt.inverter.channel.constant-power-ok.description = Constant power OK code. -thing-type.growatt.inverter.channel.discharge-energy-today.label = Energy from Battery Today +thing-type.growatt.inverter.channel.discharge-energy-today.label = Battery Energy Today thing-type.growatt.inverter.channel.discharge-energy-today.description = Energy consumed from battery today. -thing-type.growatt.inverter.channel.discharge-energy-total.label = Energy from Battery Total +thing-type.growatt.inverter.channel.discharge-energy-total.label = Battery Energy Total thing-type.growatt.inverter.channel.discharge-energy-total.description = Total energy consumed from battery. thing-type.growatt.inverter.channel.discharge-power.label = Discharge Power thing-type.growatt.inverter.channel.discharge-power.description = Discharge power from battery. @@ -63,9 +63,9 @@ thing-type.growatt.inverter.channel.grid-voltage-t.label = Grid Voltage #T thing-type.growatt.inverter.channel.grid-voltage-t.description = Voltage of the grid phase #T. thing-type.growatt.inverter.channel.grid-voltage-tr.label = Grid Voltage #TR thing-type.growatt.inverter.channel.grid-voltage-tr.description = Voltage of the grid phases #TR. -thing-type.growatt.inverter.channel.import-charge-energy-today.label = Import Energy to Charge Battery Today +thing-type.growatt.inverter.channel.import-charge-energy-today.label = Battery Import Energy Today thing-type.growatt.inverter.channel.import-charge-energy-today.description = Energy imported from grid to charge battery today. -thing-type.growatt.inverter.channel.import-charge-energy-total.label = Import Energy to Charge Battery Total +thing-type.growatt.inverter.channel.import-charge-energy-total.label = Battery Import Energy Totals thing-type.growatt.inverter.channel.import-charge-energy-total.description = Total energy imported from grid to charge battery. thing-type.growatt.inverter.channel.import-energy-today.label = Import Energy Today thing-type.growatt.inverter.channel.import-energy-today.description = Energy imported from grid today. @@ -79,33 +79,33 @@ thing-type.growatt.inverter.channel.import-power-s.label = Import Power #S thing-type.growatt.inverter.channel.import-power-s.description = Power imported phase #S. thing-type.growatt.inverter.channel.import-power-t.label = Import Power #T thing-type.growatt.inverter.channel.import-power-t.description = Power imported phase #T. -thing-type.growatt.inverter.channel.inverter-charge-energy-today.label = Inverter Energy to Charge Battery Today +thing-type.growatt.inverter.channel.inverter-charge-energy-today.label = Battery Inverter Energy Today thing-type.growatt.inverter.channel.inverter-charge-energy-today.description = Energy from inverter to charge battery today. -thing-type.growatt.inverter.channel.inverter-charge-energy-total.label = Inverter Energy to Charge Battery Total +thing-type.growatt.inverter.channel.inverter-charge-energy-total.label = Battery Inverter Energy Total thing-type.growatt.inverter.channel.inverter-charge-energy-total.description = Total energy from inverter to charge battery. -thing-type.growatt.inverter.channel.inverter-current-r.label = Inverter Output Current (#R) +thing-type.growatt.inverter.channel.inverter-current-r.label = Inverter Current (#R) thing-type.growatt.inverter.channel.inverter-current-r.description = AC current from inverter (phase #R). -thing-type.growatt.inverter.channel.inverter-current-s.label = Inverter Output Current #S +thing-type.growatt.inverter.channel.inverter-current-s.label = Inverter Current #S thing-type.growatt.inverter.channel.inverter-current-s.description = AC current from inverter phase #S. -thing-type.growatt.inverter.channel.inverter-current-t.label = Inverter Output Current #T +thing-type.growatt.inverter.channel.inverter-current-t.label = Inverter Current #T thing-type.growatt.inverter.channel.inverter-current-t.description = AC current from inverter phase #T. -thing-type.growatt.inverter.channel.inverter-energy-today.label = Inverter Output Energy Today +thing-type.growatt.inverter.channel.inverter-energy-today.label = Inverter Energy Today thing-type.growatt.inverter.channel.inverter-energy-today.description = Inverter output energy produced today. -thing-type.growatt.inverter.channel.inverter-energy-total.label = Inverter Output Energy Total +thing-type.growatt.inverter.channel.inverter-energy-total.label = Inverter Energy Total thing-type.growatt.inverter.channel.inverter-energy-total.description = Total inverter output energy produced. -thing-type.growatt.inverter.channel.inverter-power.label = Inverter Output Power +thing-type.growatt.inverter.channel.inverter-power.label = Inverter Power thing-type.growatt.inverter.channel.inverter-power.description = AC power the inverter (total). -thing-type.growatt.inverter.channel.inverter-power-r.label = Inverter Output Power (#R) +thing-type.growatt.inverter.channel.inverter-power-r.label = Inverter Power (#R) thing-type.growatt.inverter.channel.inverter-power-r.description = AC power from inverter (phase #R). -thing-type.growatt.inverter.channel.inverter-power-s.label = Inverter Output Power #S +thing-type.growatt.inverter.channel.inverter-power-s.label = Inverter Power #S thing-type.growatt.inverter.channel.inverter-power-s.description = AC power from inverter phase #S. -thing-type.growatt.inverter.channel.inverter-power-t.label = Inverter Output Power #T +thing-type.growatt.inverter.channel.inverter-power-t.label = Inverter Power #T thing-type.growatt.inverter.channel.inverter-power-t.description = AC power from inverter phase #T. thing-type.growatt.inverter.channel.inverter-va.label = Inverter VA thing-type.growatt.inverter.channel.inverter-va.description = AC VA produced by inverter. -thing-type.growatt.inverter.channel.load-energy-today.label = Energy to Load Today +thing-type.growatt.inverter.channel.load-energy-today.label = Load Energy Today thing-type.growatt.inverter.channel.load-energy-today.description = Energy supplied to load today. -thing-type.growatt.inverter.channel.load-energy-total.label = Energy to Load Total +thing-type.growatt.inverter.channel.load-energy-total.label = Load Energy Total thing-type.growatt.inverter.channel.load-energy-total.description = Total energy supplied to load. thing-type.growatt.inverter.channel.load-percent.label = Load Percent thing-type.growatt.inverter.channel.load-percent.description = Percent of full load. @@ -123,9 +123,9 @@ thing-type.growatt.inverter.channel.p-bus-voltage.label = P Bus Voltage thing-type.growatt.inverter.channel.p-bus-voltage.description = P Bus voltage. thing-type.growatt.inverter.channel.pv-boost-temperature.label = Boost Temperature thing-type.growatt.inverter.channel.pv-boost-temperature.description = Boost temperature. -thing-type.growatt.inverter.channel.pv-energy-today.label = Solar DC Energy Today +thing-type.growatt.inverter.channel.pv-energy-today.label = DC Energy Today thing-type.growatt.inverter.channel.pv-energy-today.description = Solar DC energy collected. -thing-type.growatt.inverter.channel.pv-energy-total.label = Solar DC Energy Total +thing-type.growatt.inverter.channel.pv-energy-total.label = DC Energy Total thing-type.growatt.inverter.channel.pv-energy-total.description = Total solar energy supplied to grid. thing-type.growatt.inverter.channel.pv-ipm-temperature.label = Solar IPM Temperature thing-type.growatt.inverter.channel.pv-ipm-temperature.description = Temperature of the IPM. @@ -135,21 +135,21 @@ thing-type.growatt.inverter.channel.pv-temperature.label = Solar Panel Temperatu thing-type.growatt.inverter.channel.pv-temperature.description = Temperature of the solar panels (string #1). thing-type.growatt.inverter.channel.pv1-current.label = String #1 Current thing-type.growatt.inverter.channel.pv1-current.description = Current from solar panel string #1. -thing-type.growatt.inverter.channel.pv1-energy-today.label = Solar DC Energy #1 Today +thing-type.growatt.inverter.channel.pv1-energy-today.label = DC Energy #1 Today thing-type.growatt.inverter.channel.pv1-energy-today.description = Solar DC energy collected by string #1 to grid today. -thing-type.growatt.inverter.channel.pv1-energy-total.label = Solar DC Energy #1 Total +thing-type.growatt.inverter.channel.pv1-energy-total.label = DC Energy #1 Total thing-type.growatt.inverter.channel.pv1-energy-total.description = Total solar DC collected by string #1. -thing-type.growatt.inverter.channel.pv1-power.label = Solar String #1 Power +thing-type.growatt.inverter.channel.pv1-power.label = String #1 Power thing-type.growatt.inverter.channel.pv1-power.description = Power from solar panel string #1. thing-type.growatt.inverter.channel.pv1-voltage.label = String #1 Voltage thing-type.growatt.inverter.channel.pv1-voltage.description = Voltage from solar panel string #1. thing-type.growatt.inverter.channel.pv2-current.label = String #2 Current thing-type.growatt.inverter.channel.pv2-current.description = Current from solar panel string #2. -thing-type.growatt.inverter.channel.pv2-energy-today.label = Solar DC Energy #2 Today +thing-type.growatt.inverter.channel.pv2-energy-today.label = DC Energy #2 Today thing-type.growatt.inverter.channel.pv2-energy-today.description = Solar DC energy collected by string #2 to grid today. -thing-type.growatt.inverter.channel.pv2-energy-total.label = Solar DC Energy #2 Total +thing-type.growatt.inverter.channel.pv2-energy-total.label = DC Energy #2 Total thing-type.growatt.inverter.channel.pv2-energy-total.description = Total solar DC collected by string #2. -thing-type.growatt.inverter.channel.pv2-power.label = Solar String #2 Power +thing-type.growatt.inverter.channel.pv2-power.label = String #2 Power thing-type.growatt.inverter.channel.pv2-power.description = Power from solar panel string #2. thing-type.growatt.inverter.channel.pv2-temperature.label = Solar Panel Temperature #2 thing-type.growatt.inverter.channel.pv2-temperature.description = Temperature of the solar panels (string #2). @@ -181,7 +181,7 @@ thing-type.growatt.inverter.channel.system-status.label = Inverter Status thing-type.growatt.inverter.channel.system-status.description = Status code of the inverter. thing-type.growatt.inverter.channel.system-work-mode.label = System Work Mode thing-type.growatt.inverter.channel.system-work-mode.description = System work mode code. -thing-type.growatt.inverter.channel.temperature-4.label = Temp #4 +thing-type.growatt.inverter.channel.temperature-4.label = Temperature #4 thing-type.growatt.inverter.channel.temperature-4.description = Temperature #4. thing-type.growatt.inverter.channel.total-work-time.label = Total Working Time thing-type.growatt.inverter.channel.total-work-time.description = Total inverter working time. diff --git a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml index 30d724783bee1..a736a13e8fd24 100644 --- a/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.growatt/src/main/resources/OH-INF/thing/thing-types.xml @@ -6,7 +6,7 @@ - + Bridge Thing for Growatt Binding @@ -30,7 +30,7 @@ - + Inverter Thing for Growatt Binding @@ -66,11 +66,11 @@ - + Power from solar panel string #1. - + Power from solar panel string #2. @@ -106,32 +106,32 @@ - + AC current from inverter (phase #R). - + AC current from inverter phase #S. - + AC current from inverter phase #T. - + AC power the inverter (total). - + AC power from inverter (phase #R). - + AC power from inverter phase #S. - + AC power from inverter phase #T. @@ -214,38 +214,38 @@ - + Inverter output energy produced today. - + Total inverter output energy produced. - + Solar DC energy collected. - + Solar DC energy collected by string #1 to grid today. - + Solar DC energy collected by string #2 to grid today. - + Total solar energy supplied to grid. - + Total solar DC collected by string #1. - + Total solar DC collected by string #2. @@ -271,41 +271,41 @@ - + Energy supplied to load today. - + Total energy supplied to load. - + Energy imported from grid to charge battery today. - + Total energy imported from grid to charge battery. - + Energy from inverter to charge battery today. - + Total energy from inverter to charge battery. - + Energy consumed from battery today. - + Total energy consumed from battery. @@ -343,7 +343,7 @@ Boost temperature. - + Temperature #4.