mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[siemenshvac] Initial contribution (#14263)
Signed-off-by: Laurent ARNAL <laurent@clae.net>
This commit is contained in:
parent
0786bfde9a
commit
80bd5bfcb3
@ -1626,6 +1626,11 @@
|
||||
<artifactId>org.openhab.binding.shelly</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.siemenshvac</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.siemensrds</artifactId>
|
||||
|
20
bundles/org.openhab.binding.siemenshvac/NOTICE
Normal file
20
bundles/org.openhab.binding.siemenshvac/NOTICE
Normal file
@ -0,0 +1,20 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
RuntimeTypeAdapterFactory
|
||||
* License: Apache License, Version 2.0
|
||||
* Project: https://github.com/google/gson
|
||||
* Source: https://github.com/google/gson/blob/main/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
|
98
bundles/org.openhab.binding.siemenshvac/README.md
Normal file
98
bundles/org.openhab.binding.siemenshvac/README.md
Normal file
@ -0,0 +1,98 @@
|
||||
# SiemensHVAC Binding
|
||||
|
||||
This binding provides support for the Siemens HVAC controller ecosystem, and the Web Gateway interface OZW672.
|
||||
A typical system is composed of:
|
||||
|
||||
![Diagram](doc/Diagram.png)
|
||||
|
||||
There's a lot of different HVAC controllers depending on model in lot of different PAC constructors.
|
||||
Siemens RVS41.813/327 inside a Atlantic Hybrid Duo was used for the development, and is fully supported and tested.
|
||||
|
||||
Siemens have a complete set of controller references under the name "Siemens Albatros".
|
||||
Here is a picture of such device.
|
||||
You can also find this device in other types of heating systems: boiler or solar based.
|
||||
|
||||
![](doc/Albatros.jpg)
|
||||
|
||||
You will find some information about the OZW672.01 gateway on the Siemens web site:
|
||||
|
||||
[OZW 672 Page](https://hit.sbt.siemens.com/RWD/app.aspx?rc=FR&lang=fr&module=Catalog&action=ShowProduct&key=BPZ:OZW672.01)
|
||||
|
||||
With this binding, you will be able to:
|
||||
|
||||
- Consult the different parameters of your system, like temperature, current heating mode, water temperature, and many more.
|
||||
- Modify the functioning mode of your device: temperature set point, heating mode, and others.
|
||||
|
||||
The OZW672 gateway supports many different languages (about 16).
|
||||
The binding should work with all language choices, but is currently tested more thoroughly with French and English as configured language.
|
||||
If you use another language, and find some issues, you can report them on the openHAB forum.
|
||||
|
||||
## Discovery
|
||||
|
||||
Discovery of Gateway can be done using UPnP.
|
||||
Just switch off/on your gateway to make it annonce itself on the network.
|
||||
The gateway should appear in the Inbox a few minutes after.
|
||||
Be aware what you will have to modify the password in Gateway parameters just after the discovery to make it work properly.
|
||||
Be also aware that first initialization is a little long because the binding needs to read all the metadata from the device.
|
||||
|
||||
Currently test was done with the OZW672.x series.
|
||||
No test was conducted using the OZW772.x series, the code will currently not handle initialization of an OZW772 gateway.
|
||||
You can request support in the community forum, if you have the gateway model and want it to be supported.
|
||||
|
||||
Discovery of HVAC device inside your PAC (controller of type RVS...) have to be done through the Scan button inside the binding.
|
||||
Go to the Thing page, click on the "+" button, select the SiemensHVAC binding, and then click Scan.
|
||||
Your device should appear on the page after a few seconds.
|
||||
Only test in real conditions with RVS41.813/327 have been done, but it should work with all other types as the API interface is standard.
|
||||
|
||||
## Bridge Configuration
|
||||
|
||||
Parameter | Required | Default | Description
|
||||
----------------|----------------|----------------|------------------
|
||||
baseUrl | yes | | The address of the OZW672 devices
|
||||
userName | yes | Administrator | The user name to log into the OZW672
|
||||
userPass | yes | | The user password to log into the OZW672
|
||||
|
||||
## Channels
|
||||
|
||||
Channels are auto-discovered, you will find them on the RVS things.
|
||||
They are organized the same way as the LCD screen of your PAC device, by top level menu functionality, and sub-functionalities.
|
||||
Each channel is strongly typed, so for example, for heating mode, openHAB will provide you with a list of choices supported by the device.
|
||||
|
||||
Channel | Description | Type | Unit | Security Access Level | ReadOnly | Advanced
|
||||
--------------------------------|---------------------------------------------------------------------------------------------------|-------------------------------|----------|-------------------------|-----------|----------
|
||||
1724#1725-optgmode-hc1 | Set Operating mode heat circuit 1 (`Automatic`, `Comfort`, `Reduced`, `Protection`) | operating-mode-hc | | | R/W | true
|
||||
1724#1726-roomtemp-comfsetp-hc1 | Romm temperature comfort setpoint HC1 | room-temp-comfort-setpoint-hc | | | R/W | true
|
||||
|
||||
## Full Example
|
||||
|
||||
Things file `.things`
|
||||
|
||||
```java
|
||||
Bridge siemenshvac:ozw:ozw672_FF00F445 "Ozw672" [ baseUrl="https://192.168.254.42/", userName="Administrator", userPassword="mypass" ]
|
||||
{
|
||||
Thing rvs41-813-327 00770000756A "RVS41.813/327" [ ]
|
||||
{
|
||||
Type room-temp-comfort-setpoint-hc : testChannelTemperature "TestChannelTemperature" [ id="1726" ]
|
||||
Type operating-mode-hc : testChannelCC1 "TestChannelCC1" [ id="1725" ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Items file `.items`
|
||||
|
||||
```java
|
||||
Contact Boiler_State_Pump_HWSb "HWS Pump State [%s]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:2237#2259-ppechargeecs" }
|
||||
Number Boiler_State_HWS "HWS State [%s]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:2032#2035-etat-ecs" }
|
||||
Number:Temperature Flow_Temperature_Real "Flow Temparature Real [%.1f °C]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:2237#2248-valreelletempdep-cc1" }
|
||||
Number:Temperature Flow_Temperature_Setpoint "Flow Temperature Setpoint [%.1f °C]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:2237#2249-constdepresultcc1" }
|
||||
Number Hour_fct_HWS "HWS Hour function" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:2237#2263-heuresfoncpompeecs" }
|
||||
Number Nb_Start_HWS "HWS Number of start [%.1f]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:2237#2266-comptdemarresel-ecs" }
|
||||
Number:Temperature Thermostat_Temperature "Thermostat tempeature [%.1f °C]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:2237#2246-tambact-cc1" }
|
||||
Number:Temperature Thermostat_Setpoint "Thermostat setpoint [%.1f °C]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:1724#1726-consconfort-ta-cc1" }
|
||||
Number Heat_Mode "Heat mode [%s]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:1724#1725-regime-cc1" }
|
||||
|
||||
Number:Temperature Thermostat_Setpoint_bis "Temperature [%.1f °C]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:testChannelTemperature " }
|
||||
Number Heat_Mode_bis "Heat mode [%s]" { channel = "siemenshvac:rvs41-813-327:ozw672_FF00F445:00770000756A:testChannelCC1" }
|
||||
|
||||
```
|
BIN
bundles/org.openhab.binding.siemenshvac/doc/Albatros.jpg
Normal file
BIN
bundles/org.openhab.binding.siemenshvac/doc/Albatros.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
BIN
bundles/org.openhab.binding.siemenshvac/doc/Diagram.png
Normal file
BIN
bundles/org.openhab.binding.siemenshvac/doc/Diagram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
38
bundles/org.openhab.binding.siemenshvac/pom.xml
Normal file
38
bundles/org.openhab.binding.siemenshvac/pom.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.siemenshvac</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: SiemensHvac Binding</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/3rdparty/java</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copied from
|
||||
* https://raw.githubusercontent.com/google/gson/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
|
||||
* and repackaged here with additional content from
|
||||
* com.google.gson.internal.{Streams,TypeAdapters,LazilyParsedNumber}
|
||||
* to avoid using the internal package.
|
||||
*/
|
||||
package com.google.gson.typeadapters;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.google.gson.stream.MalformedJsonException;
|
||||
|
||||
/**
|
||||
* Adapts values whose runtime type may differ from their declaration type. This
|
||||
* is necessary when a field's type is not the same type that GSON should create
|
||||
* when deserializing that field. For example, consider these types:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* abstract class Shape {
|
||||
* int x;
|
||||
* int y;
|
||||
* }
|
||||
* class Circle extends Shape {
|
||||
* int radius;
|
||||
* }
|
||||
* class Rectangle extends Shape {
|
||||
* int width;
|
||||
* int height;
|
||||
* }
|
||||
* class Diamond extends Shape {
|
||||
* int width;
|
||||
* int height;
|
||||
* }
|
||||
* class Drawing {
|
||||
* Shape bottomShape;
|
||||
* Shape topShape;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Without additional type information, the serialized JSON is ambiguous. Is
|
||||
* the bottom shape in this drawing a rectangle or a diamond?
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* {
|
||||
* "bottomShape": {
|
||||
* "width": 10,
|
||||
* "height": 5,
|
||||
* "x": 0,
|
||||
* "y": 0
|
||||
* },
|
||||
* "topShape": {
|
||||
* "radius": 2,
|
||||
* "x": 4,
|
||||
* "y": 1
|
||||
* }
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* This class addresses this problem by adding type information to the
|
||||
* serialized JSON and honoring that type information when the JSON is
|
||||
* deserialized:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* {
|
||||
* "bottomShape": {
|
||||
* "type": "Diamond",
|
||||
* "width": 10,
|
||||
* "height": 5,
|
||||
* "x": 0,
|
||||
* "y": 0
|
||||
* },
|
||||
* "topShape": {
|
||||
* "type": "Circle",
|
||||
* "radius": 2,
|
||||
* "x": 4,
|
||||
* "y": 1
|
||||
* }
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* Both the type field name ({@code "type"}) and the type labels ({@code
|
||||
* "Rectangle"}) are configurable.
|
||||
*
|
||||
* <h3>Registering Types</h3>
|
||||
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
|
||||
* name to the {@link #of} factory method. If you don't supply an explicit type
|
||||
* field name, {@code "type"} will be used.
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class, "type");
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Next register all of your subtypes. Every subtype must be explicitly
|
||||
* registered. This protects your application from injection attacks. If you
|
||||
* don't supply an explicit type label, the type's simple name will be used.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
|
||||
* shapeAdapter.registerSubtype(Circle.class, "Circle");
|
||||
* shapeAdapter.registerSubtype(Diamond.class, "Diamond");
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Finally, register the type adapter factory in your application's GSON builder:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(shapeAdapterFactory).create();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Like {@code GsonBuilder}, this API supports chaining:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
|
||||
* .registerSubtype(Rectangle.class).registerSubtype(Circle.class).registerSubtype(Diamond.class);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
/**
|
||||
* @author Jesse Wilson (swankjesse) - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
private final Class<?> baseType;
|
||||
private final String typeFieldName;
|
||||
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
|
||||
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
|
||||
|
||||
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
|
||||
this.baseType = baseType;
|
||||
this.typeFieldName = typeFieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime type adapter using for {@code baseType} using {@code
|
||||
* typeFieldName} as the type field name. Type field names are case sensitive.
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
|
||||
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
|
||||
* the type field name.
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
|
||||
return new RuntimeTypeAdapterFactory<T>(baseType, "type");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@code type} identified by {@code label}. Labels are case
|
||||
* sensitive.
|
||||
*
|
||||
* @throws IllegalArgumentException if either {@code type} or {@code label}
|
||||
* have already been registered on this type adapter.
|
||||
*/
|
||||
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
|
||||
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
|
||||
throw new IllegalArgumentException("types and labels must be unique");
|
||||
}
|
||||
labelToSubtype.put(label, type);
|
||||
subtypeToLabel.put(type, label);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@code type} identified by its {@link Class#getSimpleName simple
|
||||
* name}. Labels are case sensitive.
|
||||
*
|
||||
* @throws IllegalArgumentException if either {@code type} or its simple name
|
||||
* have already been registered on this type adapter.
|
||||
*/
|
||||
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
|
||||
return registerSubtype(type, type.getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <R> TypeAdapter<R> create(@Nullable Gson gson, @Nullable TypeToken<R> type) {
|
||||
if (type == null || type.getRawType() != baseType) {
|
||||
return null;
|
||||
}
|
||||
if (gson == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<String, TypeAdapter<?>>();
|
||||
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<Class<?>, TypeAdapter<?>>();
|
||||
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
|
||||
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
|
||||
labelToDelegate.put(entry.getKey(), delegate);
|
||||
subtypeToDelegate.put(entry.getValue(), delegate);
|
||||
}
|
||||
|
||||
return new TypeAdapter<R>() {
|
||||
@Override
|
||||
public @Nullable R read(JsonReader in) throws IOException {
|
||||
JsonElement jsonElement = RuntimeTypeAdapterFactory.parse(in);
|
||||
if (jsonElement != null) {
|
||||
JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
|
||||
if (labelJsonElement == null) {
|
||||
throw new JsonParseException("cannot deserialize " + baseType
|
||||
+ " because it does not define a field named " + typeFieldName);
|
||||
}
|
||||
String label = labelJsonElement.getAsString();
|
||||
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
|
||||
if (delegate == null) {
|
||||
throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + label
|
||||
+ "; did you forget to register a subtype?");
|
||||
}
|
||||
return delegate.fromJsonTree(jsonElement);
|
||||
} else {
|
||||
throw new JsonParseException("cannot deserialize " + baseType + " because jsonElement is null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, @Nullable R value) throws IOException {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> srcType = value.getClass();
|
||||
String label = subtypeToLabel.get(srcType);
|
||||
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
|
||||
if (delegate == null) {
|
||||
throw new JsonParseException(
|
||||
"cannot serialize " + srcType.getName() + "; did you forget to register a subtype?");
|
||||
}
|
||||
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
|
||||
if (jsonObject.has(typeFieldName)) {
|
||||
throw new JsonParseException("cannot serialize " + srcType.getName()
|
||||
+ " because it already defines a field named " + typeFieldName);
|
||||
}
|
||||
JsonObject clone = new JsonObject();
|
||||
clone.add(typeFieldName, new JsonPrimitive(label));
|
||||
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
|
||||
clone.add(e.getKey(), e.getValue());
|
||||
}
|
||||
RuntimeTypeAdapterFactory.write(clone, out);
|
||||
}
|
||||
}.nullSafe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a reader in any state and returns the next value as a JsonElement.
|
||||
*/
|
||||
private static @Nullable JsonElement parse(JsonReader reader) throws JsonParseException {
|
||||
boolean isEmpty = true;
|
||||
try {
|
||||
reader.peek();
|
||||
isEmpty = false;
|
||||
return RuntimeTypeAdapterFactory.JSON_ELEMENT.read(reader);
|
||||
} catch (EOFException e) {
|
||||
/*
|
||||
* For compatibility with JSON 1.5 and earlier, we return a JsonNull for
|
||||
* empty documents instead of throwing.
|
||||
*/
|
||||
if (isEmpty) {
|
||||
return JsonNull.INSTANCE;
|
||||
}
|
||||
// The stream ended prematurely so it is likely a syntax error.
|
||||
throw new JsonSyntaxException(e);
|
||||
} catch (MalformedJsonException e) {
|
||||
throw new JsonSyntaxException(e);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new JsonSyntaxException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the JSON element to the writer, recursively.
|
||||
*/
|
||||
private static void write(JsonElement element, JsonWriter writer) throws IOException {
|
||||
RuntimeTypeAdapterFactory.JSON_ELEMENT.write(writer, element);
|
||||
}
|
||||
|
||||
private static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() {
|
||||
@Override
|
||||
public @Nullable JsonElement read(JsonReader in) throws IOException {
|
||||
switch (in.peek()) {
|
||||
case STRING:
|
||||
return new JsonPrimitive(in.nextString());
|
||||
case NUMBER:
|
||||
String number = in.nextString();
|
||||
return new JsonPrimitive(new LazilyParsedNumber(number));
|
||||
case BOOLEAN:
|
||||
return new JsonPrimitive(in.nextBoolean());
|
||||
case NULL:
|
||||
in.nextNull();
|
||||
return JsonNull.INSTANCE;
|
||||
case BEGIN_ARRAY:
|
||||
JsonArray array = new JsonArray();
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
array.add(read(in));
|
||||
}
|
||||
in.endArray();
|
||||
return array;
|
||||
case BEGIN_OBJECT:
|
||||
JsonObject object = new JsonObject();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
object.add(in.nextName(), read(in));
|
||||
}
|
||||
in.endObject();
|
||||
return object;
|
||||
case END_DOCUMENT:
|
||||
case NAME:
|
||||
case END_OBJECT:
|
||||
case END_ARRAY:
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, @Nullable JsonElement value) throws IOException {
|
||||
if (value == null || value.isJsonNull()) {
|
||||
out.nullValue();
|
||||
} else if (value.isJsonPrimitive()) {
|
||||
JsonPrimitive primitive = value.getAsJsonPrimitive();
|
||||
if (primitive.isNumber()) {
|
||||
out.value(primitive.getAsNumber());
|
||||
} else if (primitive.isBoolean()) {
|
||||
out.value(primitive.getAsBoolean());
|
||||
} else {
|
||||
out.value(primitive.getAsString());
|
||||
}
|
||||
|
||||
} else if (value.isJsonArray()) {
|
||||
out.beginArray();
|
||||
for (JsonElement e : value.getAsJsonArray()) {
|
||||
write(out, e);
|
||||
}
|
||||
out.endArray();
|
||||
|
||||
} else if (value.isJsonObject()) {
|
||||
out.beginObject();
|
||||
for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) {
|
||||
out.name(e.getKey());
|
||||
write(out, e.getValue());
|
||||
}
|
||||
out.endObject();
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Couldn't write " + value.getClass());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class holds a number value that is lazily converted to a specific number type
|
||||
*
|
||||
* @author Inderjeet Singh
|
||||
*/
|
||||
public static final class LazilyParsedNumber extends Number {
|
||||
private final String value;
|
||||
|
||||
public LazilyParsedNumber(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
try {
|
||||
return (int) Long.parseLong(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return new BigDecimal(value).intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return new BigDecimal(value).longValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return Float.parseFloat(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return Double.parseDouble(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If somebody is unlucky enough to have to serialize one of these, serialize
|
||||
* it as a BigDecimal so that they won't need Gson on the other side to
|
||||
* deserialize it.
|
||||
*/
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
return new BigDecimal(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2010-2020 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
|
||||
|
||||
-->
|
||||
<features name="org.openhab.binding.siemenshvac-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-siemenshvac" description="SiemensHvac Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-upnp</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.siemenshvac/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.constants;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SiemensHvacBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "siemenshvac";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_OZW = new ThingTypeUID(BINDING_ID, "ozw");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(SiemensHvacBindingConstants.THING_TYPE_OZW);
|
||||
|
||||
public static final String IP_ADDRESS = "ipAddress";
|
||||
public static final String BASE_URL = "baseUrl";
|
||||
|
||||
public static final String PROPERTY_VENDOR_NAME = "Siemens";
|
||||
|
||||
public static final String CONFIG_DESCRIPTION_URI_THING_PREFIX = "thing-type";
|
||||
|
||||
public static final String DPT_TYPE_ENUM = "Enumeration";
|
||||
public static final String DPT_TYPE_NUMERIC = "Numeric";
|
||||
public static final String DPT_TYPE_RADIO = "RadioButton";
|
||||
public static final String DPT_TYPE_DATE_TIME = "DateTime";
|
||||
public static final String DPT_TYPE_TIMEOFDAY = "TimeOfDay";
|
||||
public static final String DPT_TYPE_STRING = "String";
|
||||
|
||||
public static final String DPT_TYPE_CHECKBOX = "CheckBox";
|
||||
public static final String DPT_TYPE_SCHEDULER = "Scheduler";
|
||||
public static final String DPT_TYPE_CALENDAR = "Calendar";
|
||||
|
||||
public static final String CATEGORY_THING_HVAC = "HVAC";
|
||||
|
||||
public static final String CATEGORY_CHANNEL_NUMBER = "Number";
|
||||
public static final String CATEGORY_CHANNEL_SWITCH = "Switch";
|
||||
public static final String CATEGORY_CHANNEL_TEMP = "Temperature";
|
||||
public static final String CATEGORY_CHANNEL_TIME = "Time";
|
||||
|
||||
public static final String CATEGORY_CHANNEL_CONTROL_HEATING = "Heating";
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception if something goes wrong when converting values between openHAB and the binding.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConverterException extends Exception {
|
||||
private static final long serialVersionUID = 42567425458545L;
|
||||
|
||||
public ConverterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.siemenshvac.internal.constants.SiemensHvacBindingConstants;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.CalendarTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.CheckboxTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.DateTimeTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.EnumTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.NumericTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.RadioTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.SchedulerTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.StringTypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.type.TimeOfDayTypeConverter;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
|
||||
/**
|
||||
* A factory for creating converters based on the itemType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ConverterFactory {
|
||||
private static Map<String, TypeConverter> converterCache = new HashMap<>();
|
||||
|
||||
public static void registerConverter(TimeZoneProvider timeZoneProvider) {
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_DATE_TIME, new DateTimeTypeConverter(timeZoneProvider));
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_ENUM, new EnumTypeConverter());
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_NUMERIC, new NumericTypeConverter());
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_RADIO, new RadioTypeConverter());
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_STRING, new StringTypeConverter());
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_TIMEOFDAY, new TimeOfDayTypeConverter(timeZoneProvider));
|
||||
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_CHECKBOX, new CheckboxTypeConverter());
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_SCHEDULER, new SchedulerTypeConverter());
|
||||
registerConverter(SiemensHvacBindingConstants.DPT_TYPE_CALENDAR, new CalendarTypeConverter());
|
||||
}
|
||||
|
||||
public static void registerConverter(String key, TypeConverter tp) {
|
||||
converterCache.put(key, tp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the converter for an itemType.
|
||||
*/
|
||||
public static TypeConverter getConverter(String itemType) throws ConverterTypeException {
|
||||
TypeConverter converter = converterCache.get(itemType);
|
||||
if (converter == null) {
|
||||
throw new ConverterTypeException("Can't find a converter for type '" + itemType + "'");
|
||||
}
|
||||
|
||||
return converter;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception if converting between two types is not possible due wrong item type or command.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConverterTypeException extends ConverterException {
|
||||
private static final long serialVersionUID = 2546248551752214152L;
|
||||
|
||||
public ConverterTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Converter interface for converting between openHAB states/commands and siemensHvac values.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface TypeConverter {
|
||||
|
||||
/**
|
||||
* Converts an openHAB type to a SiemensHVac value.
|
||||
*/
|
||||
@Nullable
|
||||
Object convertToBinding(Type type, ChannelType tp) throws ConverterException;
|
||||
|
||||
/**
|
||||
* Converts a siemensHvac value to an openHAB type.
|
||||
*/
|
||||
State convertFromBinding(JsonObject dp, ChannelType tp, Locale locale) throws ConverterException;
|
||||
|
||||
/**
|
||||
* get underlying channel type to construct channel type UID
|
||||
*
|
||||
*/
|
||||
String getChannelType(SiemensHvacMetadataDataPoint dpt);
|
||||
|
||||
/**
|
||||
* get underlying item type on openhab side for this SiemensHvac type
|
||||
*
|
||||
*/
|
||||
String getItemType(SiemensHvacMetadataDataPoint dpt);
|
||||
|
||||
/**
|
||||
* tell if this type have different subvariant or not
|
||||
*
|
||||
*/
|
||||
boolean hasVariant();
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterTypeException;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.TypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Base class for all Converters with common methods.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractTypeConverter implements TypeConverter {
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractTypeConverter.class);
|
||||
|
||||
@Override
|
||||
public @Nullable Object convertToBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
if (type == UnDefType.NULL) {
|
||||
return null;
|
||||
} else if (type.getClass().isEnum()) {
|
||||
return commandToBinding((Command) type, tp);
|
||||
} else if (!toBindingValidation(type)) {
|
||||
String errorMessage = String.format("Can't convert type %s with value '%s' to %s value",
|
||||
type.getClass().getSimpleName(), type.toString(), this.getClass().getSimpleName());
|
||||
throw new ConverterTypeException(errorMessage);
|
||||
}
|
||||
|
||||
return toBinding(type, tp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State convertFromBinding(JsonObject dp, ChannelType tp, Locale locale) throws ConverterException {
|
||||
String type = null;
|
||||
String unit = "";
|
||||
JsonElement value = null;
|
||||
|
||||
if (dp.has("Type")) {
|
||||
type = dp.get("Type").getAsString().trim();
|
||||
}
|
||||
if (dp.has("Value")) {
|
||||
value = dp.get("Value");
|
||||
}
|
||||
if (dp.has("EnumValue")) {
|
||||
value = dp.get("EnumValue");
|
||||
}
|
||||
|
||||
if (dp.has("Unit")) {
|
||||
unit = dp.get("Unit").getAsString().trim();
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
logger.debug("siemensHvac:ReadDP:null type {}", dp);
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
if (!fromBindingValidation(value, unit, type)) {
|
||||
logger.debug("Can't convert {} value '{}' with {} for '{}'", type, value, this.getClass().getSimpleName(),
|
||||
dp);
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
return fromBinding(value, unit, type, tp, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an openHAB command to a SiemensHvacValue value.
|
||||
*/
|
||||
protected @Nullable Object commandToBinding(Command command, ChannelType tp) throws ConverterException {
|
||||
throw new ConverterException("Unsupported command " + command.getClass().getSimpleName() + " for "
|
||||
+ this.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if the conversion from openHAB to the binding is possible.
|
||||
*/
|
||||
protected abstract boolean toBindingValidation(Type type);
|
||||
|
||||
/**
|
||||
* Converts the type to a datapoint value.
|
||||
*/
|
||||
protected abstract @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException;
|
||||
|
||||
/**
|
||||
* Returns true, if the conversion from the binding to openHAB is possible.
|
||||
*/
|
||||
protected abstract boolean fromBindingValidation(JsonElement value, String unit, String type);
|
||||
|
||||
/**
|
||||
* Converts the datapoint value to an openHAB type.
|
||||
*/
|
||||
protected abstract State fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException;
|
||||
|
||||
/**
|
||||
* get underlying channel type to construct channel type UID
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public abstract String getChannelType(SiemensHvacMetadataDataPoint dpt);
|
||||
|
||||
/**
|
||||
* get underlying item type on openhab side for this SiemensHvac type
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public abstract String getItemType(SiemensHvacMetadataDataPoint dpt);
|
||||
|
||||
/**
|
||||
* tell if this type have different subvariant or not
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public abstract boolean hasVariant();
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CalendarTypeConverter extends AbstractTypeConverter {
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof DateTimeType dateTime) {
|
||||
valUpdate = dateTime.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DecimalType fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
throw new ConverterException("NIY");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "datetime";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return CoreItemFactory.DATETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CheckboxTypeConverter extends AbstractTypeConverter {
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
throw new ConverterException("NIY");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DecimalType fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
throw new ConverterException("NIY");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "contact";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return CoreItemFactory.CONTACT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DateTimeTypeConverter extends AbstractTypeConverter {
|
||||
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
public DateTimeTypeConverter(final TimeZoneProvider timeZoneProvider) {
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof DateTimeType dateTime) {
|
||||
valUpdate = dateTime.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateTimeType fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
if ("----".equals(value.getAsString())) {
|
||||
return new DateTimeType(ZonedDateTime.now(this.timeZoneProvider.getTimeZone()));
|
||||
} else {
|
||||
String[] formats = { "EEEE, d. LLLL yyyy HH:mm[:ss]", "d. LLLL yyyy HH:mm[:ss]", "d. LLLL yyyy",
|
||||
"d. LLLL" };
|
||||
|
||||
for (int i = 0; i < formats.length; i++) {
|
||||
try {
|
||||
DateTimeFormatterBuilder formatterBuilder = new DateTimeFormatterBuilder().parseCaseInsensitive()
|
||||
.appendPattern(formats[i]).parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
|
||||
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
|
||||
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
|
||||
|
||||
if (i == 3) {
|
||||
formatterBuilder = formatterBuilder.parseDefaulting(ChronoField.YEAR,
|
||||
ZonedDateTime.now(this.timeZoneProvider.getTimeZone()).getYear());
|
||||
}
|
||||
|
||||
LocalDateTime parsedDate = LocalDateTime.parse(value.getAsString(),
|
||||
formatterBuilder.toFormatter(locale));
|
||||
|
||||
ZonedDateTime zdt = parsedDate.atZone(this.timeZoneProvider.getTimeZone());
|
||||
|
||||
return new DateTimeType(zdt);
|
||||
} catch (DateTimeParseException ex) {
|
||||
// Silently ignore, we are proceeding to next format.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ConverterException("Can't parse the date for:" + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "datetime";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return CoreItemFactory.DATETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnumTypeConverter extends AbstractTypeConverter {
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof DecimalType decimalValue) {
|
||||
valUpdate = decimalValue.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DecimalType fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
return new DecimalType(value.getAsInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "number";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return CoreItemFactory.NUMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,250 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
import javax.measure.quantity.ElectricPotential;
|
||||
import javax.measure.quantity.Temperature;
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
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.thing.type.ChannelType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NumericTypeConverter extends AbstractTypeConverter {
|
||||
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof QuantityType quantityType) {
|
||||
Number num = (quantityType);
|
||||
valUpdate = num.doubleValue();
|
||||
} else if (type instanceof DecimalType decimalValue) {
|
||||
valUpdate = decimalValue.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
if ("----".equals(value.getAsString())) {
|
||||
return new DecimalType(0);
|
||||
} else {
|
||||
double dValue = value.getAsDouble();
|
||||
|
||||
String itemType = tp.getItemType();
|
||||
|
||||
if (itemType != null) {
|
||||
if ("Number:Temperature".equals(itemType)) {
|
||||
Unit<Temperature> targetUnit = null;
|
||||
|
||||
if ("°C".equals(unit)) {
|
||||
targetUnit = SIUnits.CELSIUS;
|
||||
} else if ("°F".equals(unit)) {
|
||||
targetUnit = ImperialUnits.FAHRENHEIT;
|
||||
}
|
||||
|
||||
if (targetUnit != null) {
|
||||
return new QuantityType<>(dValue, targetUnit);
|
||||
}
|
||||
} else if ("Number:ElectricPotential".equals(itemType)) {
|
||||
Unit<ElectricPotential> targetUnit = null;
|
||||
|
||||
if ("V".equals(unit)) {
|
||||
targetUnit = Units.VOLT;
|
||||
}
|
||||
|
||||
if (targetUnit != null) {
|
||||
return new QuantityType<>(dValue, targetUnit);
|
||||
}
|
||||
} else if ("Number:Time".equals(itemType)) {
|
||||
Unit<Time> targetUnit = null;
|
||||
|
||||
switch (unit) {
|
||||
case "s":
|
||||
case "sek":
|
||||
targetUnit = Units.SECOND;
|
||||
break;
|
||||
case "m":
|
||||
case "min":
|
||||
case "perc":
|
||||
case "dak":
|
||||
case "мин":
|
||||
targetUnit = Units.MINUTE;
|
||||
break;
|
||||
case "h":
|
||||
case "sa":
|
||||
targetUnit = Units.HOUR;
|
||||
break;
|
||||
case "Months":
|
||||
case "Monate":
|
||||
case "Mois":
|
||||
case "Mesi":
|
||||
case "Maanden":
|
||||
case "mies.":
|
||||
case "Měsíce":
|
||||
case "hónap":
|
||||
case "Meses":
|
||||
case "mdr.":
|
||||
case "Månader":
|
||||
case "kk":
|
||||
case "месяцы":
|
||||
case "Aylar":
|
||||
case "mesiac":
|
||||
targetUnit = Units.MONTH;
|
||||
break;
|
||||
case "d":
|
||||
case "Jours":
|
||||
case "giorni":
|
||||
case "Dny":
|
||||
case "nap":
|
||||
case "dage":
|
||||
case "dag":
|
||||
case "vrk":
|
||||
case "д":
|
||||
targetUnit = Units.DAY;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetUnit != null) {
|
||||
return new QuantityType<>(dValue, targetUnit);
|
||||
}
|
||||
} else if ("Number:Dimensionless".equals(itemType)) {
|
||||
Unit<Dimensionless> targetUnit = null;
|
||||
|
||||
if ("%".equals(unit)) {
|
||||
targetUnit = Units.PERCENT;
|
||||
}
|
||||
|
||||
if (targetUnit != null) {
|
||||
return new QuantityType<>(dValue, targetUnit);
|
||||
}
|
||||
} else if ("Number".equals(itemType)) {
|
||||
return new DecimalType(dValue);
|
||||
}
|
||||
} else {
|
||||
return new DecimalType(dValue);
|
||||
}
|
||||
|
||||
return new DecimalType(dValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "number";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
String unit = dpt.getDptUnit();
|
||||
|
||||
if (unit == null) {
|
||||
return CoreItemFactory.NUMBER;
|
||||
}
|
||||
|
||||
if ("".equals(unit)) {
|
||||
return CoreItemFactory.NUMBER;
|
||||
}
|
||||
|
||||
switch (unit) {
|
||||
case "°F":
|
||||
case "°C":
|
||||
return CoreItemFactory.NUMBER + ":Temperature";
|
||||
case "°F*min":
|
||||
case "°C*min":
|
||||
case "°Cmin":
|
||||
case "°Cdak":
|
||||
return CoreItemFactory.NUMBER;
|
||||
case "V":
|
||||
return CoreItemFactory.NUMBER + ":ElectricPotential";
|
||||
case "%":
|
||||
return CoreItemFactory.NUMBER + ":Dimensionless";
|
||||
case "d":
|
||||
case "Jours":
|
||||
case "giorni":
|
||||
case "Dny":
|
||||
case "nap":
|
||||
case "dage":
|
||||
case "dag":
|
||||
case "vrk":
|
||||
case "д":
|
||||
case "h":
|
||||
case "sa":
|
||||
case "m":
|
||||
case "s":
|
||||
case "сек":
|
||||
case "min":
|
||||
case "perc":
|
||||
case "мин":
|
||||
case "dak":
|
||||
case "Months":
|
||||
case "Monate":
|
||||
case "Mois":
|
||||
case "Mesi":
|
||||
case "Maanden":
|
||||
case "mies.":
|
||||
case "Měsíce":
|
||||
case "hónap":
|
||||
case "Meses":
|
||||
case "mdr.":
|
||||
case "Månader":
|
||||
case "kk":
|
||||
case "месяцы":
|
||||
case "Aylar":
|
||||
case "mesiac":
|
||||
return CoreItemFactory.NUMBER + ":Time";
|
||||
default:
|
||||
return CoreItemFactory.NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioTypeConverter extends AbstractTypeConverter {
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof DecimalType decimalValue) {
|
||||
valUpdate = decimalValue.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object commandToBinding(Command command, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (command instanceof DecimalType decimalValue) {
|
||||
valUpdate = decimalValue.toString();
|
||||
} else if (command instanceof OnOffType onOffValue) {
|
||||
if (onOffValue.equals(OnOffType.OFF)) {
|
||||
valUpdate = 0;
|
||||
} else if (onOffValue.equals(OnOffType.ON)) {
|
||||
valUpdate = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
State updateVal = UnDefType.UNDEF;
|
||||
String valueSt = value.getAsString();
|
||||
|
||||
StateDescription sd = tp.getState();
|
||||
|
||||
if (sd != null) {
|
||||
List<StateOption> options = sd.getOptions();
|
||||
StateOption offOpt = options.get(0);
|
||||
StateOption onOpt = options.get(1);
|
||||
|
||||
if (valueSt.equals(onOpt.getLabel())) {
|
||||
updateVal = new DecimalType(1);
|
||||
} else if (valueSt.equals(offOpt.getLabel())) {
|
||||
updateVal = new DecimalType(0);
|
||||
}
|
||||
}
|
||||
|
||||
return updateVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
if (dpt.getWriteAccess()) {
|
||||
return "switch";
|
||||
} else {
|
||||
return "contact";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
if (dpt.getWriteAccess()) {
|
||||
return CoreItemFactory.SWITCH;
|
||||
} else {
|
||||
return CoreItemFactory.CONTACT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SchedulerTypeConverter extends AbstractTypeConverter {
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof DateTimeType dateTime) {
|
||||
valUpdate = dateTime.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DecimalType fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
throw new ConverterException("NIY");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "datetime";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return CoreItemFactory.DATETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StringTypeConverter extends AbstractTypeConverter {
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof PercentType percentValue) {
|
||||
valUpdate = percentValue.toString();
|
||||
} else if (type instanceof DecimalType decimalValue) {
|
||||
valUpdate = decimalValue.toString();
|
||||
} else if (type instanceof StringType stringValue) {
|
||||
valUpdate = stringValue.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StringType fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
return new StringType(value.getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "string";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return CoreItemFactory.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.converter.type;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Converts between a SiemensHvac datapoint value and an openHAB DecimalType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TimeOfDayTypeConverter extends AbstractTypeConverter {
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
public TimeOfDayTypeConverter(final TimeZoneProvider timeZoneProvider) {
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean toBindingValidation(Type type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object toBinding(Type type, ChannelType tp) throws ConverterException {
|
||||
Object valUpdate = null;
|
||||
|
||||
if (type instanceof DateTimeType dateTime) {
|
||||
valUpdate = dateTime.toString();
|
||||
}
|
||||
|
||||
return valUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fromBindingValidation(JsonElement value, String unit, String type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State fromBinding(JsonElement value, String unit, String type, ChannelType tp, Locale locale)
|
||||
throws ConverterException {
|
||||
if ("----".equals(value.getAsString())) {
|
||||
return new DateTimeType(ZonedDateTime.now(this.timeZoneProvider.getTimeZone()));
|
||||
} else {
|
||||
|
||||
if (unit.equals("h:m")) {
|
||||
String st = value.getAsString();
|
||||
String[] parts = st.split(":");
|
||||
int h = Integer.parseInt(parts[0]);
|
||||
int m = Integer.parseInt(parts[1]);
|
||||
|
||||
Unit<Time> targetUnit = Units.MINUTE;
|
||||
return new QuantityType<>(h * 60 + m, targetUnit);
|
||||
|
||||
} else if (unit.equals("m:s")) {
|
||||
String st = value.getAsString();
|
||||
String[] parts = st.split(":");
|
||||
int m = Integer.parseInt(parts[0]);
|
||||
int s = Integer.parseInt(parts[1]);
|
||||
|
||||
Unit<Time> targetUnit = Units.SECOND;
|
||||
return new QuantityType<>(m * 60 + s, targetUnit);
|
||||
|
||||
} else if (unit.equals("h")) {
|
||||
int val = Integer.parseInt(value.getAsString());
|
||||
|
||||
Unit<Time> targetUnit = Units.HOUR;
|
||||
return new QuantityType<>(val, targetUnit);
|
||||
|
||||
} else {
|
||||
throw new ConverterException("unsupported unit type:" + unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return "number";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType(SiemensHvacMetadataDataPoint dpt) {
|
||||
return CoreItemFactory.NUMBER + ":Time";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVariant() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.discovery;
|
||||
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.jupnp.model.meta.DeviceDetails;
|
||||
import org.jupnp.model.meta.ModelDetails;
|
||||
import org.jupnp.model.meta.RemoteDevice;
|
||||
import org.openhab.binding.siemenshvac.internal.constants.SiemensHvacBindingConstants;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
|
||||
import org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
|
||||
/**
|
||||
* The {@link SiemensHvacDiscoveryParticipant} is responsible for discovering new and
|
||||
* removed siemensHvac bridges. It uses the central {@link UpnpDiscoveryService}.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "discovery.siemenshvac", immediate = true)
|
||||
public class SiemenesHvacDiscoveryParticipant implements UpnpDiscoveryParticipant {
|
||||
|
||||
@Activate
|
||||
public void activate(@Nullable Map<String, Object> configProperties) {
|
||||
}
|
||||
|
||||
@Modified
|
||||
public void modified(@Nullable Map<String, Object> configProperties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SiemensHvacBindingConstants.SUPPORTED_THING_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
|
||||
ThingUID uid = getThingUID(device);
|
||||
if (uid != null) {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
String ipAddress = device.getDetails().getPresentationURI().getHost();
|
||||
properties.put(SiemensHvacBindingConstants.BASE_URL, "https://" + ipAddress + "/");
|
||||
|
||||
String label = "";
|
||||
|
||||
if (uid.getAsString().contains("ozw672")) {
|
||||
label = "OZW672 IP Gateway";
|
||||
} else if (uid.getAsString().contains("ozw772")) {
|
||||
label = "OZW772 IP Gateway";
|
||||
}
|
||||
|
||||
String serialNumber = device.getDetails().getSerialNumber();
|
||||
DiscoveryResult result;
|
||||
if (serialNumber != null && !serialNumber.isBlank()) {
|
||||
properties.put(PROPERTY_SERIAL_NUMBER, serialNumber.toLowerCase());
|
||||
|
||||
result = DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label)
|
||||
.withRepresentationProperty(PROPERTY_SERIAL_NUMBER).build();
|
||||
} else {
|
||||
result = DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(RemoteDevice device) {
|
||||
DeviceDetails details = device.getDetails();
|
||||
if (details != null) {
|
||||
ModelDetails modelDetails = details.getModelDetails();
|
||||
String serialNumber = details.getSerialNumber();
|
||||
if (modelDetails != null && serialNumber != null && !serialNumber.isBlank()) {
|
||||
String modelName = modelDetails.getModelName();
|
||||
if (modelName != null) {
|
||||
if (modelName.startsWith("Web Server OZW672")) {
|
||||
return new ThingUID(SiemensHvacBindingConstants.THING_TYPE_OZW, "ozw672-" + serialNumber);
|
||||
} else if (modelName.startsWith("Web Server OZW772")) {
|
||||
return new ThingUID(SiemensHvacBindingConstants.THING_TYPE_OZW, "ozw772-" + serialNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.discovery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.constants.SiemensHvacBindingConstants;
|
||||
import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacBridgeThingHandler;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDevice;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataRegistry;
|
||||
import org.openhab.binding.siemenshvac.internal.type.SiemensHvacException;
|
||||
import org.openhab.binding.siemenshvac.internal.type.UidUtils;
|
||||
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;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SiemensHvacDeviceDiscoveryService} tracks for Siemens Hvac device connected to the bus.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacDeviceDiscoveryService extends AbstractDiscoveryService
|
||||
implements DiscoveryService, ThingHandlerService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SiemensHvacDeviceDiscoveryService.class);
|
||||
|
||||
private @Nullable SiemensHvacMetadataRegistry metadataRegistry;
|
||||
private @Nullable SiemensHvacBridgeThingHandler siemensHvacBridgeHandler;
|
||||
|
||||
private static final int SEARCH_TIME = 10;
|
||||
|
||||
public SiemensHvacDeviceDiscoveryService() {
|
||||
super(SiemensHvacBindingConstants.SUPPORTED_THING_TYPES, SEARCH_TIME);
|
||||
}
|
||||
|
||||
@Reference
|
||||
public void setSiemensHvacMetadataRegistry(@Nullable SiemensHvacMetadataRegistry metadataRegistry) {
|
||||
this.metadataRegistry = metadataRegistry;
|
||||
}
|
||||
|
||||
public void unsetSiemensHvacMetadataRegistry(SiemensHvacMetadataRegistry metadataRegistry) {
|
||||
this.metadataRegistry = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
}
|
||||
|
||||
private @Nullable ThingUID getThingUID(ThingTypeUID thingTypeUID, String serial) {
|
||||
final SiemensHvacBridgeThingHandler lcSiemensHvacBridgeHandler = siemensHvacBridgeHandler;
|
||||
if (lcSiemensHvacBridgeHandler != null) {
|
||||
ThingUID localBridgeUID = lcSiemensHvacBridgeHandler.getThing().getUID();
|
||||
return new ThingUID(thingTypeUID, localBridgeUID, serial);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
final SiemensHvacMetadataRegistry lcMetadataRegistry = metadataRegistry;
|
||||
final SiemensHvacBridgeThingHandler lcSiemensHvacBridgeHandler = siemensHvacBridgeHandler;
|
||||
logger.debug("call startScan()");
|
||||
|
||||
if (lcMetadataRegistry != null) {
|
||||
try {
|
||||
lcMetadataRegistry.readMeta();
|
||||
} catch (SiemensHvacException ex) {
|
||||
logger.debug("Exception occurred during execution: {}", ex.getMessage(), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<SiemensHvacMetadataDevice> devices = lcMetadataRegistry.getDevices();
|
||||
|
||||
if (devices == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (SiemensHvacMetadataDevice device : devices) {
|
||||
|
||||
String name = device.getName();
|
||||
String type = device.getType();
|
||||
String addr = device.getAddr();
|
||||
String serialNr = device.getSerialNr();
|
||||
|
||||
logger.debug("Find devices: {} / {} / {} / {}", name, type, addr, serialNr);
|
||||
|
||||
String typeSn = UidUtils.sanetizeId(type);
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(SiemensHvacBindingConstants.BINDING_ID, typeSn);
|
||||
|
||||
ThingUID thingUID = getThingUID(thingTypeUID, serialNr);
|
||||
|
||||
if (lcSiemensHvacBridgeHandler != null) {
|
||||
ThingUID bridgeUID = lcSiemensHvacBridgeHandler.getThing().getUID();
|
||||
|
||||
if (thingUID != null) {
|
||||
Map<String, Object> properties = new HashMap<>(4);
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, name);
|
||||
properties.put("type", type);
|
||||
properties.put("addr", addr);
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNr);
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperties(properties).withBridge(bridgeUID).withLabel(name).build();
|
||||
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof SiemensHvacBridgeThingHandler siemensHvacBridgeHandler) {
|
||||
this.siemensHvacBridgeHandler = siemensHvacBridgeHandler;
|
||||
this.siemensHvacBridgeHandler.registerDiscoveryListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return siemensHvacBridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.factory;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.constants.SiemensHvacBindingConstants;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterFactory;
|
||||
import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacBridgeThingHandler;
|
||||
import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacHandlerImpl;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataRegistry;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
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.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link SiemensHvacHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Laurent ARNAL - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.siemenshvac")
|
||||
public class SiemensHvacHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final NetworkAddressService networkAddressService;
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final SiemensHvacMetadataRegistry metaDataRegistry;
|
||||
private final ChannelTypeRegistry channelTypeRegistry;
|
||||
private final TranslationProvider translationProvider;
|
||||
|
||||
@Activate
|
||||
public SiemensHvacHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference SiemensHvacMetadataRegistry metaDataRegistry,
|
||||
final @Reference NetworkAddressService networkAddressService,
|
||||
final @Reference ChannelTypeRegistry channelTypeRegistry,
|
||||
final @Reference TimeZoneProvider timeZoneProvider,
|
||||
final @Reference TranslationProvider translationProvider) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.metaDataRegistry = metaDataRegistry;
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
this.translationProvider = translationProvider;
|
||||
|
||||
ConverterFactory.registerConverter(timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SiemensHvacBindingConstants.BINDING_ID.equals(thingTypeUID.getBindingId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
||||
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||
if (SiemensHvacBindingConstants.THING_TYPE_OZW.equals(thingTypeUID)) {
|
||||
return super.createThing(thingTypeUID, configuration, thingUID, null);
|
||||
} else if (SiemensHvacBindingConstants.BINDING_ID.equals(thingTypeUID.getBindingId())) {
|
||||
return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"The thing type " + thingTypeUID + " is not supported by the SiemensHvac binding.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
if (thing.getThingTypeUID().equals(SiemensHvacBindingConstants.THING_TYPE_OZW)) {
|
||||
return new SiemensHvacBridgeThingHandler((Bridge) thing, networkAddressService, httpClientFactory,
|
||||
metaDataRegistry, translationProvider);
|
||||
} else if (SiemensHvacBindingConstants.BINDING_ID.equals(thing.getThingTypeUID().getBindingId())) {
|
||||
SiemensHvacHandlerImpl handler = new SiemensHvacHandlerImpl(thing,
|
||||
metaDataRegistry.getSiemensHvacConnector(), metaDataRegistry, channelTypeRegistry);
|
||||
return handler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacBridgeConfig {
|
||||
|
||||
public String baseUrl = "";
|
||||
public String userName = "Administrator";
|
||||
public String userPassword = "password";
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.handler;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.discovery.SiemensHvacDeviceDiscoveryService;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataRegistry;
|
||||
import org.openhab.binding.siemenshvac.internal.network.SiemensHvacConnector;
|
||||
import org.openhab.binding.siemenshvac.internal.type.SiemensHvacException;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
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.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SiemensHvacBridgeBaseThingHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution and API
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacBridgeThingHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SiemensHvacBridgeThingHandler.class);
|
||||
private @Nullable SiemensHvacDeviceDiscoveryService discoveryService;
|
||||
private final @Nullable HttpClientFactory httpClientFactory;
|
||||
private final SiemensHvacMetadataRegistry metaDataRegistry;
|
||||
private @Nullable SiemensHvacBridgeConfig config;
|
||||
private final TranslationProvider translationProvider;
|
||||
|
||||
public SiemensHvacBridgeThingHandler(Bridge bridge, @Nullable NetworkAddressService networkAddressService,
|
||||
@Nullable HttpClientFactory httpClientFactory, SiemensHvacMetadataRegistry metaDataRegistry,
|
||||
TranslationProvider translationProvider) {
|
||||
super(bridge);
|
||||
SiemensHvacConnector lcConnector = null;
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.metaDataRegistry = metaDataRegistry;
|
||||
this.translationProvider = translationProvider;
|
||||
|
||||
lcConnector = this.metaDataRegistry.getSiemensHvacConnector();
|
||||
if (lcConnector != null) {
|
||||
lcConnector.setSiemensHvacBridgeBaseThingHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
metaDataRegistry.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
SiemensHvacBridgeConfig lcConfig = getConfigAs(SiemensHvacBridgeConfig.class);
|
||||
String baseUrl = null;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Initialize() bridge: {}", getBuildDate());
|
||||
}
|
||||
|
||||
baseUrl = lcConfig.baseUrl;
|
||||
|
||||
if (baseUrl.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.error-gateway-init");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
|
||||
baseUrl = "http://" + baseUrl;
|
||||
}
|
||||
|
||||
if (!baseUrl.endsWith("/")) {
|
||||
baseUrl = baseUrl + "/";
|
||||
}
|
||||
|
||||
config = lcConfig;
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/offline.waiting-bridge-initialization");
|
||||
|
||||
// Will read metadata in background to not block initialize for a long period !
|
||||
scheduler.schedule(this::initializeCode, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected String getBuildDate() {
|
||||
try {
|
||||
ClassLoader cl = getClass().getClassLoader();
|
||||
if (cl != null) {
|
||||
URL res = cl.getResource(getClass().getCanonicalName().replace('.', '/') + ".class");
|
||||
if (res != null) {
|
||||
URLConnection cnx = res.openConnection();
|
||||
LocalDate dt = LocalDate.ofEpochDay(cnx.getLastModified());
|
||||
DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
|
||||
return df.format(dt);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
public static String getStackTrace(final Throwable throwable) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
Throwable current = throwable;
|
||||
while (current != null) {
|
||||
sb.append(current.getLocalizedMessage());
|
||||
sb.append(",\r\n");
|
||||
|
||||
Throwable cause = throwable.getCause();
|
||||
if (cause != null) {
|
||||
if (!cause.equals(throwable)) {
|
||||
current = current.getCause();
|
||||
} else {
|
||||
current = null;
|
||||
}
|
||||
} else {
|
||||
current = null;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void initializeCode() {
|
||||
try {
|
||||
metaDataRegistry.readMeta();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (SiemensHvacException ex) {
|
||||
Locale local = metaDataRegistry.getUserLocale();
|
||||
BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
|
||||
String text = translationProvider.getText(bundleContext.getBundle(), "offline.error-gateway-init",
|
||||
"DefaultValue", local);
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
MessageFormat.format(text, ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable SiemensHvacBridgeConfig getBridgeConfiguration() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
|
||||
super.updateStatus(status, statusDetail, description);
|
||||
}
|
||||
|
||||
public boolean registerDiscoveryListener(SiemensHvacDeviceDiscoveryService listener) {
|
||||
SiemensHvacDeviceDiscoveryService lcDiscoveryService = discoveryService;
|
||||
if (lcDiscoveryService == null) {
|
||||
lcDiscoveryService = listener;
|
||||
lcDiscoveryService.setSiemensHvacMetadataRegistry(metaDataRegistry);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean unregisterDiscoveryListener() {
|
||||
SiemensHvacDeviceDiscoveryService lcDiscoveryService = discoveryService;
|
||||
if (lcDiscoveryService != null) {
|
||||
discoveryService = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public @Nullable HttpClientFactory getHttpClientFactory() {
|
||||
return this.httpClientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(SiemensHvacDeviceDiscoveryService.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,451 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.handler;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterException;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterFactory;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterTypeException;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.TypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataRegistry;
|
||||
import org.openhab.binding.siemenshvac.internal.network.SiemensHvacCallback;
|
||||
import org.openhab.binding.siemenshvac.internal.network.SiemensHvacConnector;
|
||||
import org.openhab.binding.siemenshvac.internal.network.SiemensHvacRequestListener.ErrorSource;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
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;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link SiemensHvacHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Laurent ARNAL - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacHandlerImpl extends BaseThingHandler {
|
||||
|
||||
private Lock lockObj = new ReentrantLock();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SiemensHvacHandlerImpl.class);
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob = null;
|
||||
|
||||
private final @Nullable SiemensHvacConnector hvacConnector;
|
||||
private final @Nullable SiemensHvacMetadataRegistry metaDataRegistry;
|
||||
private final ChannelTypeRegistry channelTypeRegistry;
|
||||
|
||||
private long lastWrite = 0;
|
||||
|
||||
public SiemensHvacHandlerImpl(Thing thing, @Nullable SiemensHvacConnector hvacConnector,
|
||||
@Nullable SiemensHvacMetadataRegistry metaDataRegistry, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing);
|
||||
|
||||
this.hvacConnector = hvacConnector;
|
||||
this.metaDataRegistry = metaDataRegistry;
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::pollingCode, 0, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> lcPollingJob = pollingJob;
|
||||
if (lcPollingJob != null) {
|
||||
lcPollingJob.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void pollingCode() {
|
||||
Bridge lcBridge = getBridge();
|
||||
|
||||
if (lcBridge == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lcBridge.getStatus() == ThingStatus.OFFLINE) {
|
||||
if (!ThingStatusDetail.COMMUNICATION_ERROR.equals(lcBridge.getStatusInfo().getStatusDetail())) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (lcBridge.getStatus() != ThingStatus.ONLINE) {
|
||||
if (!ThingStatusDetail.COMMUNICATION_ERROR.equals(lcBridge.getStatusInfo().getStatusDetail())) {
|
||||
logger.debug("Bridge is not ready, don't enter polling for now!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
var chList = this.getThing().getChannels();
|
||||
|
||||
SiemensHvacConnector lcHvacConnector = hvacConnector;
|
||||
if (lcHvacConnector != null) {
|
||||
int previousRequestCount = lcHvacConnector.getRequestCount();
|
||||
int previousErrorCount = lcHvacConnector.getErrorCount();
|
||||
|
||||
logger.debug("readChannels:");
|
||||
for (Channel channel : chList) {
|
||||
readChannel(channel);
|
||||
}
|
||||
|
||||
logger.debug("WaitAllPendingRequest:Start waiting()");
|
||||
lcHvacConnector.waitAllPendingRequest();
|
||||
long end = System.currentTimeMillis();
|
||||
logger.debug("WaitAllPendingRequest:All request done(): {}", (end - start) / 1000.00);
|
||||
|
||||
int newRequestCount = lcHvacConnector.getRequestCount();
|
||||
int newErrorCount = lcHvacConnector.getErrorCount();
|
||||
|
||||
int requestCount = newRequestCount - previousRequestCount;
|
||||
int errorCount = newErrorCount - previousErrorCount;
|
||||
|
||||
double errorRate = (double) errorCount / requestCount * 100.00;
|
||||
|
||||
if (errorRate > 50) {
|
||||
SiemensHvacBridgeThingHandler bridgeHandler = (SiemensHvacBridgeThingHandler) lcBridge.getHandler();
|
||||
|
||||
if (lcHvacConnector.getErrorSource() == ErrorSource.ErrorBridge) {
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
String.format("Communication ErrorRate to gateway is too high: %f", errorRate));
|
||||
}
|
||||
} else if (lcHvacConnector.getErrorSource() == ErrorSource.ErrorThings) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
String.format("Communication ErrorRate to thing is too high: %f", errorRate));
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
SiemensHvacBridgeThingHandler bridgeHandler = (SiemensHvacBridgeThingHandler) lcBridge.getHandler();
|
||||
|
||||
// Automatically recover from communication errors if errorRate is ok.
|
||||
if (bridgeHandler != null) {
|
||||
if (ThingStatusDetail.COMMUNICATION_ERROR
|
||||
.equals(bridgeHandler.getThing().getStatusInfo().getStatusDetail())) {
|
||||
bridgeHandler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lcHvacConnector.displayRequestStats();
|
||||
}
|
||||
}
|
||||
|
||||
private void readChannel(Channel channel) {
|
||||
ThingHandlerCallback cb = this.getCallback();
|
||||
boolean isLink = false;
|
||||
|
||||
if (cb != null) {
|
||||
isLink = cb.isChannelLinked(channel.getUID());
|
||||
}
|
||||
|
||||
if (!isLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("readChannel: {}", channel.getDescription());
|
||||
|
||||
ChannelType tp = channelTypeRegistry.getChannelType(channel.getChannelTypeUID());
|
||||
|
||||
if (tp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String id = channel.getProperties().get("id");
|
||||
String uid = channel.getUID().getId();
|
||||
String type = tp.getItemType();
|
||||
|
||||
if (id == null) {
|
||||
id = (String) channel.getConfiguration().getProperties().get("id");
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
logger.debug("pollingCode: Id is null {} ", channel);
|
||||
return;
|
||||
}
|
||||
if (type == null) {
|
||||
logger.debug("pollingCode: type is null {} ", channel);
|
||||
return;
|
||||
}
|
||||
|
||||
readDp(id, uid, tp, type, true);
|
||||
}
|
||||
|
||||
public void decodeReadDp(@Nullable JsonObject response, @Nullable String uid, @Nullable String dp, ChannelType tp,
|
||||
@Nullable String type) {
|
||||
SiemensHvacMetadataRegistry lcMetaDataRegistry = metaDataRegistry;
|
||||
if (lcMetaDataRegistry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response != null && response.has("Data")) {
|
||||
JsonObject subResult = (JsonObject) response.get("Data");
|
||||
|
||||
String updateKey = "" + uid;
|
||||
|
||||
String typer = null;
|
||||
|
||||
if (subResult.has("Type")) {
|
||||
typer = subResult.get("Type").getAsString().trim();
|
||||
}
|
||||
|
||||
try {
|
||||
if (typer != null) {
|
||||
TypeConverter converter = ConverterFactory.getConverter(typer);
|
||||
|
||||
Locale local = lcMetaDataRegistry.getUserLocale();
|
||||
if (local == null) {
|
||||
local = Locale.getDefault();
|
||||
}
|
||||
|
||||
State state = converter.convertFromBinding(subResult, tp, local);
|
||||
updateState(updateKey, state);
|
||||
}
|
||||
} catch (ConverterTypeException ex) {
|
||||
logger.warn("{}, for uid : {}, please check the item type", ex.getMessage(), uid);
|
||||
} catch (ConverterException ex) {
|
||||
logger.warn("{}, for uid: {}, please check the item type", ex.getMessage(), uid);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void readDp(String dp, String uid, ChannelType tp, String type, boolean async) {
|
||||
SiemensHvacConnector lcHvacConnector = hvacConnector;
|
||||
|
||||
if ("-1".equals(dp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
lockObj.lock();
|
||||
|
||||
logger.trace("Start read: {}", dp);
|
||||
String request = "api/menutree/read_datapoint.json?Id=" + dp;
|
||||
|
||||
logger.debug("siemensHvac:ReadDp:DoRequest(): {}", request);
|
||||
|
||||
if (async) {
|
||||
if (lcHvacConnector != null) {
|
||||
lcHvacConnector.doRequest(request, new SiemensHvacCallback() {
|
||||
|
||||
@Override
|
||||
public void execute(java.net.URI uri, int status, @Nullable Object response) {
|
||||
// prevent async read if we just write so we have no overlaps
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastWrite < 5000) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.trace("End read: {}", dp);
|
||||
|
||||
if (response instanceof JsonObject jsonResponse) {
|
||||
decodeReadDp(jsonResponse, uid, dp, tp, type);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (lcHvacConnector != null) {
|
||||
JsonObject js = lcHvacConnector.doRequest(request);
|
||||
decodeReadDp(js, uid, dp, tp, type);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
logger.trace("End read: {}", dp);
|
||||
lockObj.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDp(String dp, Type dpVal, ChannelType tp, String type) {
|
||||
SiemensHvacConnector lcHvacConnector = hvacConnector;
|
||||
|
||||
if (lcHvacConnector != null) {
|
||||
lcHvacConnector.displayRequestStats();
|
||||
}
|
||||
|
||||
if ("-1".equals(dp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
lockObj.lock();
|
||||
logger.trace("Start write: {}", dp);
|
||||
lastWrite = System.currentTimeMillis();
|
||||
|
||||
Object valUpdate = "0";
|
||||
|
||||
try {
|
||||
TypeConverter converter = ConverterFactory.getConverter(type);
|
||||
|
||||
valUpdate = converter.convertToBinding(dpVal, tp);
|
||||
if (valUpdate != null) {
|
||||
String request = String.format("api/menutree/write_datapoint.json?Id=%s&Value=%s&Type=%s", dp,
|
||||
valUpdate, type);
|
||||
|
||||
if (lcHvacConnector != null) {
|
||||
logger.trace("Write request for: {} ", valUpdate);
|
||||
JsonObject response = lcHvacConnector.doRequest(request);
|
||||
|
||||
logger.trace("Write request response: {} ", response);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.debug("Failed to get converted state from datapoint '{}'", dp);
|
||||
}
|
||||
} catch (ConverterTypeException ex) {
|
||||
logger.warn("{}, please check the item type and the commands in your scripts", ex.getMessage());
|
||||
} catch (ConverterException ex) {
|
||||
logger.warn("{}, please check the item type and the commands in your scripts", ex.getMessage());
|
||||
}
|
||||
} finally {
|
||||
logger.debug("End write: {}", dp);
|
||||
lockObj.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private Command applyState(ChannelType tp, Command command) {
|
||||
StateDescription sd = tp.getState();
|
||||
Command result = command;
|
||||
|
||||
if (sd != null) {
|
||||
BigDecimal maxb = sd.getMaximum();
|
||||
BigDecimal minb = sd.getMinimum();
|
||||
BigDecimal step = sd.getStep();
|
||||
boolean doMods = false;
|
||||
|
||||
if (command instanceof DecimalType decimalCommand) {
|
||||
double v1 = decimalCommand.doubleValue();
|
||||
|
||||
if (step != null) {
|
||||
doMods = true;
|
||||
int divider = 1;
|
||||
|
||||
if (step.doubleValue() == 0.5) {
|
||||
divider = 2;
|
||||
} else if (step.doubleValue() == 0.1) {
|
||||
divider = 10;
|
||||
} else if (step.doubleValue() == 0.02) {
|
||||
divider = 50;
|
||||
} else if (step.doubleValue() == 0.01) {
|
||||
divider = 100;
|
||||
}
|
||||
v1 = v1 * divider;
|
||||
v1 = Math.floor(v1);
|
||||
v1 = v1 / divider;
|
||||
}
|
||||
|
||||
if (minb != null && v1 < minb.floatValue()) {
|
||||
doMods = true;
|
||||
v1 = minb.floatValue();
|
||||
}
|
||||
if (maxb != null && v1 > maxb.floatValue()) {
|
||||
doMods = true;
|
||||
v1 = maxb.floatValue();
|
||||
}
|
||||
|
||||
if (doMods) {
|
||||
result = new DecimalType(v1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
SiemensHvacMetadataRegistry lcMetaDataRegistry = metaDataRegistry;
|
||||
logger.debug("handleCommand");
|
||||
if (command instanceof RefreshType) {
|
||||
var channel = this.getThing().getChannel(channelUID);
|
||||
if (channel != null) {
|
||||
readChannel(channel);
|
||||
}
|
||||
} else {
|
||||
Channel channel = getThing().getChannel(channelUID);
|
||||
if (channel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Command commandVar = command;
|
||||
ChannelType tp = channelTypeRegistry.getChannelType(channel.getChannelTypeUID());
|
||||
|
||||
if (tp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String type = tp.getItemType();
|
||||
String dptType = "";
|
||||
String id = channel.getProperties().get("id");
|
||||
SiemensHvacMetadataDataPoint md = null;
|
||||
|
||||
if (id == null) {
|
||||
id = (String) channel.getConfiguration().getProperties().get("id");
|
||||
}
|
||||
|
||||
if (lcMetaDataRegistry != null) {
|
||||
md = (SiemensHvacMetadataDataPoint) lcMetaDataRegistry.getDptMap(id);
|
||||
if (md != null) {
|
||||
id = "" + md.getId();
|
||||
dptType = md.getDptType();
|
||||
}
|
||||
}
|
||||
|
||||
if (command instanceof State commandState) {
|
||||
commandVar = applyState(tp, commandVar);
|
||||
this.updateState(channelUID, commandState);
|
||||
}
|
||||
|
||||
if (id != null && type != null) {
|
||||
writeDp(id, commandVar, tp, dptType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacMetadata {
|
||||
private int id = -1;
|
||||
private int subId = -1;
|
||||
private int groupId = -1;
|
||||
private int catId = -1;
|
||||
private String shortDescEn = "";
|
||||
private String longDescEn = "";
|
||||
private String shortDesc = "";
|
||||
private String longDesc = "";
|
||||
@Nullable
|
||||
private transient SiemensHvacMetadata parent;
|
||||
|
||||
public SiemensHvacMetadata() {
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int Id) {
|
||||
this.id = Id;
|
||||
}
|
||||
|
||||
public int getSubId() {
|
||||
return subId;
|
||||
}
|
||||
|
||||
public void setSubId(int subId) {
|
||||
this.subId = subId;
|
||||
}
|
||||
|
||||
public int getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(int groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public int getCatId() {
|
||||
return catId;
|
||||
}
|
||||
|
||||
public void setCatId(int catId) {
|
||||
this.catId = catId;
|
||||
}
|
||||
|
||||
public String getShortDescEn() {
|
||||
return shortDescEn;
|
||||
}
|
||||
|
||||
public void setShortDescEn(String shortDesc) {
|
||||
this.shortDescEn = shortDesc;
|
||||
}
|
||||
|
||||
public String getLongDescEn() {
|
||||
return longDescEn;
|
||||
}
|
||||
|
||||
public void setLongDescEn(String longDesc) {
|
||||
this.longDescEn = longDesc;
|
||||
}
|
||||
|
||||
public String getShortDesc() {
|
||||
return shortDesc;
|
||||
}
|
||||
|
||||
public void setShortDesc(String shortDesc) {
|
||||
this.shortDesc = shortDesc;
|
||||
}
|
||||
|
||||
public String getLongDesc() {
|
||||
return longDesc;
|
||||
}
|
||||
|
||||
public void setLongDesc(String longDesc) {
|
||||
this.longDesc = longDesc;
|
||||
}
|
||||
|
||||
public @Nullable SiemensHvacMetadata getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(@Nullable SiemensHvacMetadata parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.constants.SiemensHvacBindingConstants;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacMetadataDataPoint extends SiemensHvacMetadata {
|
||||
private String dptId = "-1";
|
||||
private String dptType = "";
|
||||
private @Nullable String dptUnit = null;
|
||||
private @Nullable String min = null;
|
||||
private @Nullable String max = null;
|
||||
private @Nullable String resolution = null;
|
||||
private @Nullable String fieldWitdh = null;
|
||||
private @Nullable String decimalDigits = null;
|
||||
private boolean detailsResolved = false;
|
||||
private @Nullable String dialogType = null;
|
||||
private @Nullable String maxLength = null;
|
||||
private @Nullable String address = null;
|
||||
private int dptSubKey = -1;
|
||||
private boolean writeAccess = false;
|
||||
|
||||
private @NotNull List<SiemensHvacMetadataPointChild> child = List.of();
|
||||
|
||||
public SiemensHvacMetadataDataPoint() {
|
||||
child = new ArrayList<SiemensHvacMetadataPointChild>();
|
||||
}
|
||||
|
||||
public String getDptType() {
|
||||
return dptType;
|
||||
}
|
||||
|
||||
public void setDptType(String dptType) {
|
||||
this.dptType = dptType;
|
||||
}
|
||||
|
||||
public List<SiemensHvacMetadataPointChild> getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(List<SiemensHvacMetadataPointChild> child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public String getDptId() {
|
||||
return dptId;
|
||||
}
|
||||
|
||||
public void setDptId(String dptId) {
|
||||
this.dptId = dptId;
|
||||
}
|
||||
|
||||
public int getDptSubKey() {
|
||||
return dptSubKey;
|
||||
}
|
||||
|
||||
public void setDptSubKey(int dptSubKey) {
|
||||
this.dptSubKey = dptSubKey;
|
||||
}
|
||||
|
||||
public @Nullable String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setWriteAccess(boolean writeAccess) {
|
||||
this.writeAccess = writeAccess;
|
||||
}
|
||||
|
||||
public boolean getWriteAccess() {
|
||||
return writeAccess;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public @Nullable String getDptUnit() {
|
||||
return dptUnit;
|
||||
}
|
||||
|
||||
public void setDptUnit(String dptUnit) {
|
||||
this.dptUnit = dptUnit;
|
||||
}
|
||||
|
||||
public @Nullable String getMaxLength() {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
public void setMaxLength(String maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
public @Nullable String getDialogType() {
|
||||
return dialogType;
|
||||
}
|
||||
|
||||
public void setDialogType(String dialogType) {
|
||||
this.dialogType = dialogType;
|
||||
}
|
||||
|
||||
public @Nullable String getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public void setMin(String min) {
|
||||
this.min = min;
|
||||
}
|
||||
|
||||
public @Nullable String getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(String max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public @Nullable String getResolution() {
|
||||
return resolution;
|
||||
}
|
||||
|
||||
public void setResolution(String resolution) {
|
||||
this.resolution = resolution;
|
||||
}
|
||||
|
||||
public @Nullable String getFieldWitdh() {
|
||||
return fieldWitdh;
|
||||
}
|
||||
|
||||
public void setFieldWitdh(String fieldWitdh) {
|
||||
this.fieldWitdh = fieldWitdh;
|
||||
}
|
||||
|
||||
public @Nullable String getDecimalDigits() {
|
||||
return decimalDigits;
|
||||
}
|
||||
|
||||
public void setDecimalDigits(String decimalDigits) {
|
||||
this.decimalDigits = decimalDigits;
|
||||
}
|
||||
|
||||
public Boolean getDetailsResolved() {
|
||||
return detailsResolved;
|
||||
}
|
||||
|
||||
public void setDetailsResolved(Boolean detailsResolved) {
|
||||
this.detailsResolved = detailsResolved;
|
||||
}
|
||||
|
||||
public void resolveDptDetails(JsonObject result) {
|
||||
JsonObject subResultObj = result.getAsJsonObject("Result");
|
||||
JsonObject desc = result.getAsJsonObject("Description");
|
||||
|
||||
if (subResultObj.has("Success")) {
|
||||
JsonObject error = subResultObj.getAsJsonObject("Error");
|
||||
String errorMsg = "";
|
||||
if (error != null) {
|
||||
errorMsg = error.get("Txt").getAsString();
|
||||
}
|
||||
|
||||
if (("datatype not supported").equals(errorMsg)) {
|
||||
detailsResolved = true;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (desc != null) {
|
||||
this.dptType = desc.get("Type").getAsString();
|
||||
|
||||
if (SiemensHvacBindingConstants.DPT_TYPE_ENUM.equals(dptType)) {
|
||||
JsonArray enums = desc.getAsJsonArray("Enums");
|
||||
|
||||
for (Object obj : enums) {
|
||||
JsonObject entry = (JsonObject) obj;
|
||||
|
||||
SiemensHvacMetadataPointChild ch = new SiemensHvacMetadataPointChild();
|
||||
ch.setText(entry.get("Text").getAsString());
|
||||
ch.setValue(entry.get("Value").getAsString());
|
||||
ch.setIsActive(entry.get("IsCurrentValue").getAsString());
|
||||
child.add(ch);
|
||||
}
|
||||
} else if (SiemensHvacBindingConstants.DPT_TYPE_NUMERIC.equals(dptType)) {
|
||||
this.dptUnit = desc.get("Unit").getAsString();
|
||||
this.min = desc.get("Min").getAsString();
|
||||
this.max = desc.get("Max").getAsString();
|
||||
this.resolution = desc.get("Resolution").getAsString();
|
||||
this.fieldWitdh = desc.get("FieldWitdh").getAsString();
|
||||
this.decimalDigits = desc.get("DecimalDigits").getAsString();
|
||||
} else if (SiemensHvacBindingConstants.DPT_TYPE_STRING.equals(dptType)) {
|
||||
this.dialogType = desc.get("DialogType").getAsString();
|
||||
this.maxLength = desc.get("MaxLength").getAsString();
|
||||
} else if (SiemensHvacBindingConstants.DPT_TYPE_RADIO.equals(dptType)) {
|
||||
JsonArray buttons = desc.getAsJsonArray("Buttons");
|
||||
|
||||
child = new ArrayList<SiemensHvacMetadataPointChild>();
|
||||
|
||||
for (Object obj : buttons) {
|
||||
JsonObject button = (JsonObject) obj;
|
||||
|
||||
SiemensHvacMetadataPointChild ch = new SiemensHvacMetadataPointChild();
|
||||
ch.setOpt0(button.get("TextOpt0").getAsString());
|
||||
ch.setOpt1(button.get("TextOpt1").getAsString());
|
||||
ch.setIsActive(button.get("IsActive").getAsString());
|
||||
child.add(ch);
|
||||
}
|
||||
}
|
||||
|
||||
detailsResolved = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacMetadataDevice {
|
||||
|
||||
private String name = "";
|
||||
|
||||
private String addr = "";
|
||||
|
||||
private String type = "unknown";
|
||||
|
||||
private String serialNr = "";
|
||||
|
||||
private @Nullable String treeDate;
|
||||
|
||||
private @Nullable String treeTime;
|
||||
|
||||
private boolean treeGenerated;
|
||||
private int treeId;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAddr() {
|
||||
return addr;
|
||||
}
|
||||
|
||||
public void setAddr(String addr) {
|
||||
this.addr = addr;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getSerialNr() {
|
||||
return serialNr;
|
||||
}
|
||||
|
||||
public void setSerialNr(String serialNr) {
|
||||
this.serialNr = serialNr;
|
||||
}
|
||||
|
||||
public @Nullable String getTreeDate() {
|
||||
return treeDate;
|
||||
}
|
||||
|
||||
public void setTreeDate(String treeDate) {
|
||||
this.treeDate = treeDate;
|
||||
}
|
||||
|
||||
public @Nullable String getTreeTime() {
|
||||
return treeTime;
|
||||
}
|
||||
|
||||
public void setTreeTime(String treeTime) {
|
||||
this.treeTime = treeTime;
|
||||
}
|
||||
|
||||
public boolean getTreeGenerated() {
|
||||
return treeGenerated;
|
||||
}
|
||||
|
||||
public void setTreeGenerated(boolean treeGenerated) {
|
||||
this.treeGenerated = treeGenerated;
|
||||
}
|
||||
|
||||
public int getTreeId() {
|
||||
return treeId;
|
||||
}
|
||||
|
||||
public void setTreeId(int treeId) {
|
||||
this.treeId = treeId;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacMetadataLanguage {
|
||||
private String name;
|
||||
private int id;
|
||||
private String language;
|
||||
private int languageId;
|
||||
|
||||
public SiemensHvacMetadataLanguage() {
|
||||
name = "";
|
||||
language = "";
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public int getLanguageId() {
|
||||
return languageId;
|
||||
}
|
||||
|
||||
public void setLanguageId(int languageId) {
|
||||
this.languageId = languageId;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacMetadataMenu extends SiemensHvacMetadata {
|
||||
private LinkedHashMap<Integer, SiemensHvacMetadata> childList;
|
||||
|
||||
public SiemensHvacMetadataMenu() {
|
||||
childList = new LinkedHashMap<Integer, SiemensHvacMetadata>();
|
||||
}
|
||||
|
||||
public void addChild(SiemensHvacMetadata information) {
|
||||
childList.put(information.getId(), information);
|
||||
}
|
||||
|
||||
public HashMap<Integer, SiemensHvacMetadata> getChilds() {
|
||||
return this.childList;
|
||||
}
|
||||
|
||||
public boolean hasChild(int Id) {
|
||||
return this.childList.containsKey(Id);
|
||||
}
|
||||
|
||||
public @NotNull SiemensHvacMetadata getChild(int Id) {
|
||||
return this.childList.get(Id);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacMetadataPointChild {
|
||||
|
||||
private String text = "";
|
||||
private String value = "";
|
||||
private String opt0 = "";
|
||||
private String opt1 = "";
|
||||
private String isActive = "";
|
||||
|
||||
public String getText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getOpt0() {
|
||||
return this.opt0;
|
||||
}
|
||||
|
||||
public void setOpt0(String opt0) {
|
||||
this.opt0 = opt0;
|
||||
}
|
||||
|
||||
public String getOpt1() {
|
||||
return this.opt1;
|
||||
}
|
||||
|
||||
public void setOpt1(String opt1) {
|
||||
this.opt1 = opt1;
|
||||
}
|
||||
|
||||
public String getIsActive() {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
public void setIsActive(String isActive) {
|
||||
this.isActive = isActive;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.siemenshvac.internal.network.SiemensHvacConnector;
|
||||
import org.openhab.binding.siemenshvac.internal.type.SiemensHvacChannelTypeProvider;
|
||||
import org.openhab.binding.siemenshvac.internal.type.SiemensHvacException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SiemensHvacMetadataRegistry {
|
||||
|
||||
/**
|
||||
* Initializes the type generator.
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
void readMeta() throws SiemensHvacException;
|
||||
|
||||
@Nullable
|
||||
SiemensHvacMetadataMenu getRoot();
|
||||
|
||||
@Nullable
|
||||
ArrayList<SiemensHvacMetadataDevice> getDevices();
|
||||
|
||||
@Nullable
|
||||
SiemensHvacMetadata getDptMap(@Nullable String key);
|
||||
|
||||
@Nullable
|
||||
SiemensHvacChannelTypeProvider getChannelTypeProvider();
|
||||
|
||||
@Nullable
|
||||
SiemensHvacConnector getSiemensHvacConnector();
|
||||
|
||||
void invalidate();
|
||||
|
||||
@Nullable
|
||||
SiemensHvacMetadataUser getUser();
|
||||
|
||||
@Nullable
|
||||
Locale getUserLocale();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.metadata;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacMetadataUser {
|
||||
private String name;
|
||||
private int id;
|
||||
private String language;
|
||||
private int languageId;
|
||||
|
||||
public SiemensHvacMetadataUser() {
|
||||
name = "";
|
||||
language = "";
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public int getLanguageId() {
|
||||
return languageId;
|
||||
}
|
||||
|
||||
public void setLanguageId(int languageId) {
|
||||
this.languageId = languageId;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.network;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SiemensHvacCallback {
|
||||
/**
|
||||
* Runs callback code after response completion.
|
||||
*/
|
||||
void execute(URI uri, int status, @Nullable Object response);
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.network;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacBridgeConfig;
|
||||
import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacBridgeThingHandler;
|
||||
import org.openhab.binding.siemenshvac.internal.type.SiemensHvacException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SiemensHvacConnector {
|
||||
|
||||
@Nullable
|
||||
String doBasicRequest(String uri) throws SiemensHvacException;
|
||||
|
||||
@Nullable
|
||||
JsonObject doRequest(String req);
|
||||
|
||||
@Nullable
|
||||
JsonObject doRequest(String req, @Nullable SiemensHvacCallback callback);
|
||||
|
||||
void waitAllPendingRequest();
|
||||
|
||||
void waitNoNewRequest();
|
||||
|
||||
void onComplete(Request request, SiemensHvacRequestHandler reqListener) throws SiemensHvacException;
|
||||
|
||||
void onError(Request request, SiemensHvacRequestHandler reqListener,
|
||||
SiemensHvacRequestListener.ErrorSource errorSource, boolean mayRetry) throws SiemensHvacException;
|
||||
|
||||
void setSiemensHvacBridgeBaseThingHandler(SiemensHvacBridgeThingHandler hvacBridgeBaseThingHandler);
|
||||
|
||||
@Nullable
|
||||
SiemensHvacBridgeConfig getBridgeConfiguration();
|
||||
|
||||
void resetSessionId(@Nullable String sessionIdToInvalidate, boolean web);
|
||||
|
||||
void displayRequestStats();
|
||||
|
||||
Gson getGson();
|
||||
|
||||
Gson getGsonWithAdapter();
|
||||
|
||||
int getRequestCount();
|
||||
|
||||
int getErrorCount();
|
||||
|
||||
SiemensHvacRequestListener.ErrorSource getErrorSource();
|
||||
|
||||
void invalidate();
|
||||
|
||||
void setTimeOut(int timeout);
|
||||
}
|
@ -0,0 +1,669 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.network;
|
||||
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacBridgeConfig;
|
||||
import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacBridgeThingHandler;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadata;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataMenu;
|
||||
import org.openhab.binding.siemenshvac.internal.type.SiemensHvacException;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.types.Type;
|
||||
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;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true)
|
||||
public class SiemensHvacConnectorImpl implements SiemensHvacConnector {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SiemensHvacConnectorImpl.class);
|
||||
|
||||
private Map<SiemensHvacRequestHandler, SiemensHvacRequestHandler> currentHandlerRegistry = new ConcurrentHashMap<>();
|
||||
private Map<SiemensHvacRequestHandler, SiemensHvacRequestHandler> handlerInErrorRegistry = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<String, Boolean> oldSessionId = new HashMap<>();
|
||||
|
||||
private final Gson gson;
|
||||
private final Gson gsonWithAdapter;
|
||||
|
||||
private @Nullable String sessionId = null;
|
||||
private @Nullable String sessionIdHttp = null;
|
||||
private @Nullable SiemensHvacBridgeConfig config = null;
|
||||
|
||||
protected final HttpClientFactory httpClientFactory;
|
||||
|
||||
protected HttpClient httpClient;
|
||||
|
||||
private Map<String, Type> updateCommand;
|
||||
|
||||
private int requestCount = 0;
|
||||
private int errorCount = 0;
|
||||
private int timeout = 10;
|
||||
private SiemensHvacRequestListener.ErrorSource errorSource = SiemensHvacRequestListener.ErrorSource.ErrorBridge;
|
||||
|
||||
private @Nullable SiemensHvacBridgeThingHandler hvacBridgeBaseThingHandler;
|
||||
|
||||
@Activate
|
||||
public SiemensHvacConnectorImpl(@Reference HttpClientFactory httpClientFactory) {
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
gson = builder.setPrettyPrinting().create();
|
||||
|
||||
RuntimeTypeAdapterFactory<SiemensHvacMetadata> adapter = RuntimeTypeAdapterFactory
|
||||
.of(SiemensHvacMetadata.class);
|
||||
adapter.registerSubtype(SiemensHvacMetadataMenu.class);
|
||||
adapter.registerSubtype(SiemensHvacMetadataDataPoint.class);
|
||||
|
||||
gsonWithAdapter = new GsonBuilder().setPrettyPrinting().registerTypeAdapterFactory(adapter).create();
|
||||
|
||||
this.updateCommand = new Hashtable<String, Type>();
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
|
||||
SslContextFactory ctxFactory = new SslContextFactory.Client(true);
|
||||
ctxFactory.setRenegotiationAllowed(false);
|
||||
ctxFactory.setEnableCRLDP(false);
|
||||
ctxFactory.setEnableOCSP(false);
|
||||
ctxFactory.setTrustAll(true);
|
||||
ctxFactory.setValidateCerts(false);
|
||||
ctxFactory.setValidatePeerCerts(false);
|
||||
ctxFactory.setEndpointIdentificationAlgorithm(null);
|
||||
|
||||
this.httpClient = new HttpClient(ctxFactory);
|
||||
this.httpClient.setMaxConnectionsPerDestination(10);
|
||||
this.httpClient.setMaxRequestsQueuedPerDestination(10000);
|
||||
this.httpClient.setConnectTimeout(10000);
|
||||
this.httpClient.setFollowRedirects(false);
|
||||
|
||||
try {
|
||||
this.httpClient.start();
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start http client: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiemensHvacBridgeBaseThingHandler(
|
||||
@Nullable SiemensHvacBridgeThingHandler hvacBridgeBaseThingHandler) {
|
||||
this.hvacBridgeBaseThingHandler = hvacBridgeBaseThingHandler;
|
||||
}
|
||||
|
||||
public void unsetSiemensHvacBridgeBaseThingHandler(SiemensHvacBridgeThingHandler hvacBridgeBaseThingHandler) {
|
||||
this.hvacBridgeBaseThingHandler = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Request request, SiemensHvacRequestHandler reqHandler)
|
||||
throws SiemensHvacException {
|
||||
unregisterRequestHandler(reqHandler);
|
||||
}
|
||||
|
||||
public static String extractSessionId(String query) {
|
||||
int idx1 = query.indexOf("SessionId=");
|
||||
int idx2 = query.indexOf("&", idx1 + 1);
|
||||
if (idx2 < 0) {
|
||||
idx2 = query.length();
|
||||
}
|
||||
|
||||
String sessionId = query.substring(idx1 + 10, idx2);
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable Request request, @Nullable SiemensHvacRequestHandler reqHandler,
|
||||
SiemensHvacRequestListener.ErrorSource errorSource, boolean mayRetry) throws SiemensHvacException {
|
||||
if (reqHandler == null || request == null) {
|
||||
throw new SiemensHvacException("internalError: onError call with reqHandler == null");
|
||||
}
|
||||
|
||||
boolean doRetry = mayRetry;
|
||||
// Don't retry if we have do it multiple time
|
||||
if (reqHandler.getRetryCount() >= 5) {
|
||||
doRetry = false;
|
||||
}
|
||||
|
||||
// Don't retry if we lost session, just abort the request, and wait next loop
|
||||
if (sessionIdHttp == null || sessionId == null) {
|
||||
doRetry = false;
|
||||
}
|
||||
|
||||
if (!doRetry) {
|
||||
logger.debug("unable to handle request, doRetry = false, cancel it");
|
||||
unregisterRequestHandler(reqHandler);
|
||||
registerHandlerError(reqHandler);
|
||||
errorCount++;
|
||||
this.errorSource = errorSource;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Wait one second before retrying the request to avoid flooding the gateway
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ex) {
|
||||
// We can silently ignore this one
|
||||
}
|
||||
|
||||
if (sessionIdHttp == null) {
|
||||
doAuth(true);
|
||||
}
|
||||
|
||||
if (sessionId == null) {
|
||||
doAuth(false);
|
||||
}
|
||||
|
||||
try {
|
||||
URI uri = request.getURI();
|
||||
String query = uri.toString();
|
||||
|
||||
String sessionIdInQuery = extractSessionId(query);
|
||||
if (query.indexOf("main.app") >= 0) {
|
||||
String sessionIdHttpLc = sessionIdHttp;
|
||||
|
||||
if (sessionIdHttpLc != null && !sessionIdHttpLc.equals(sessionIdInQuery)) {
|
||||
uri = new URI(query.replace(sessionIdInQuery, sessionIdHttpLc));
|
||||
}
|
||||
} else {
|
||||
String sessionIdLc = sessionId;
|
||||
|
||||
if (sessionIdLc != null && !sessionIdLc.equals(sessionIdInQuery)) {
|
||||
uri = new URI(query.replace(sessionIdInQuery, sessionIdLc));
|
||||
}
|
||||
}
|
||||
|
||||
final Request retryRequest = httpClient.newRequest(uri);
|
||||
request.method(HttpMethod.GET);
|
||||
reqHandler.setRequest(retryRequest);
|
||||
reqHandler.incrementRetryCount();
|
||||
|
||||
if (retryRequest != null) {
|
||||
executeRequest(retryRequest, reqHandler);
|
||||
}
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new SiemensHvacException("Error during gateway request", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable ContentResponse executeRequest(final Request request) throws SiemensHvacException {
|
||||
return executeRequest(request, (SiemensHvacCallback) null);
|
||||
}
|
||||
|
||||
private @Nullable ContentResponse executeRequest(final Request request, @Nullable SiemensHvacCallback callback)
|
||||
throws SiemensHvacException {
|
||||
requestCount++;
|
||||
|
||||
// For asynchronous request, we create a RequestHandler that will enable us to follow request state
|
||||
SiemensHvacRequestHandler requestHandler = null;
|
||||
if (callback != null) {
|
||||
requestHandler = new SiemensHvacRequestHandler(callback, this);
|
||||
requestHandler.setRequest(request);
|
||||
currentHandlerRegistry.put(requestHandler, requestHandler);
|
||||
}
|
||||
|
||||
return executeRequest(request, requestHandler);
|
||||
}
|
||||
|
||||
private void unregisterRequestHandler(SiemensHvacRequestHandler handler) throws SiemensHvacException {
|
||||
synchronized (currentHandlerRegistry) {
|
||||
if (currentHandlerRegistry.containsKey(handler)) {
|
||||
currentHandlerRegistry.remove(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerHandlerError(SiemensHvacRequestHandler handler) {
|
||||
synchronized (handlerInErrorRegistry) {
|
||||
handlerInErrorRegistry.put(handler, handler);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable ContentResponse executeRequest(final Request request,
|
||||
@Nullable SiemensHvacRequestHandler requestHandler) throws SiemensHvacException {
|
||||
// Give a high timeout because we queue a lot of async request,
|
||||
// so enqueued them will take some times ...
|
||||
request.timeout(timeout, TimeUnit.SECONDS);
|
||||
|
||||
ContentResponse response = null;
|
||||
|
||||
try {
|
||||
if (requestHandler != null) {
|
||||
SiemensHvacRequestListener requestListener = new SiemensHvacRequestListener(requestHandler);
|
||||
request.send(requestListener);
|
||||
} else {
|
||||
response = request.send();
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new SiemensHvacException("siemensHvac:Exception by executing request: "
|
||||
+ anominized(request.getURI().toString()) + " ; " + e.getLocalizedMessage());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private void initConfig() throws SiemensHvacException {
|
||||
SiemensHvacBridgeThingHandler lcHvacBridgeBaseThingHandler = hvacBridgeBaseThingHandler;
|
||||
|
||||
if (lcHvacBridgeBaseThingHandler != null) {
|
||||
config = lcHvacBridgeBaseThingHandler.getBridgeConfiguration();
|
||||
} else {
|
||||
throw new SiemensHvacException(
|
||||
"siemensHvac:Exception unable to get config because hvacBridgeBaseThingHandler is null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SiemensHvacBridgeConfig getBridgeConfiguration() {
|
||||
return config;
|
||||
}
|
||||
|
||||
private void doAuth(boolean http) throws SiemensHvacException {
|
||||
synchronized (this) {
|
||||
logger.debug("siemensHvac:doAuth()");
|
||||
|
||||
initConfig();
|
||||
|
||||
SiemensHvacBridgeConfig config = this.config;
|
||||
if (config == null) {
|
||||
throw new SiemensHvacException("Missing SiemensHvacOZW Bridge configuration");
|
||||
}
|
||||
|
||||
String baseUri = config.baseUrl;
|
||||
String uri = "";
|
||||
|
||||
if (http) {
|
||||
uri = "main.app";
|
||||
} else {
|
||||
uri = String.format("api/auth/login.json?user=%s&pwd=%s", config.userName, config.userPassword);
|
||||
}
|
||||
|
||||
final Request request = httpClient.newRequest(baseUri + uri);
|
||||
if (http) {
|
||||
request.method(HttpMethod.POST).param("user", config.userName).param("pwd", config.userPassword);
|
||||
} else {
|
||||
request.method(HttpMethod.GET);
|
||||
}
|
||||
|
||||
logger.debug("siemensHvac:doAuth:connect()");
|
||||
|
||||
ContentResponse response = executeRequest(request);
|
||||
if (response != null) {
|
||||
int statusCode = response.getStatus();
|
||||
|
||||
if (statusCode == HttpStatus.OK_200) {
|
||||
String result = response.getContentAsString();
|
||||
|
||||
if (http) {
|
||||
CookieStore cookieStore = httpClient.getCookieStore();
|
||||
List<HttpCookie> cookies = cookieStore.getCookies();
|
||||
|
||||
for (HttpCookie httpCookie : cookies) {
|
||||
if (httpCookie.getName().equals("SessionId")) {
|
||||
sessionIdHttp = httpCookie.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (sessionIdHttp == null) {
|
||||
logger.debug("Session request auth was unsuccessful in _doAuth()");
|
||||
}
|
||||
} else {
|
||||
if (result != null) {
|
||||
JsonObject resultObj = getGson().fromJson(result, JsonObject.class);
|
||||
|
||||
if (resultObj != null && resultObj.has("Result")) {
|
||||
JsonElement resultVal = resultObj.get("Result");
|
||||
JsonObject resultObj2 = resultVal.getAsJsonObject();
|
||||
|
||||
if (resultObj2.has("Success")) {
|
||||
boolean successVal = resultObj2.get("Success").getAsBoolean();
|
||||
|
||||
if (successVal) {
|
||||
if (resultObj.has("SessionId")) {
|
||||
sessionId = resultObj.get("SessionId").getAsString();
|
||||
logger.debug("Have new SessionId: {} ", sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("siemensHvac:doAuth:decodeResponse:()");
|
||||
|
||||
if (sessionId == null) {
|
||||
throw new SiemensHvacException(
|
||||
"Session request auth was unsuccessful in _doAuth(), please verify login parameters");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("siemensHvac:doAuth:connect()");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String doBasicRequest(String uri) throws SiemensHvacException {
|
||||
return doBasicRequest(uri, null);
|
||||
}
|
||||
|
||||
public @Nullable String doBasicRequestAsync(String uri, @Nullable SiemensHvacCallback callback)
|
||||
throws SiemensHvacException {
|
||||
return doBasicRequest(uri, callback);
|
||||
}
|
||||
|
||||
public @Nullable String doBasicRequest(String uri, @Nullable SiemensHvacCallback callback)
|
||||
throws SiemensHvacException {
|
||||
if (sessionIdHttp == null) {
|
||||
doAuth(true);
|
||||
}
|
||||
|
||||
if (sessionId == null) {
|
||||
doAuth(false);
|
||||
}
|
||||
|
||||
SiemensHvacBridgeConfig config = this.config;
|
||||
if (config == null) {
|
||||
throw new SiemensHvacException("Missing SiemensHvac OZW Bridge configuration");
|
||||
}
|
||||
|
||||
String baseUri = config.baseUrl;
|
||||
|
||||
String mUri = uri;
|
||||
if (!mUri.endsWith("?")) {
|
||||
mUri = mUri + "&";
|
||||
}
|
||||
if (mUri.indexOf("main.app") >= 0) {
|
||||
mUri = mUri + "SessionId=" + sessionIdHttp;
|
||||
} else {
|
||||
mUri = mUri + "SessionId=" + sessionId;
|
||||
}
|
||||
|
||||
CookieStore c = httpClient.getCookieStore();
|
||||
java.net.HttpCookie cookie = new HttpCookie("SessionId", sessionIdHttp);
|
||||
cookie.setPath("/");
|
||||
cookie.setVersion(0);
|
||||
|
||||
try {
|
||||
c.add(new URI(baseUri), cookie);
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new SiemensHvacException(String.format("URI is not correctly formatted: %s", baseUri), ex);
|
||||
}
|
||||
|
||||
logger.debug("Execute request: {}", uri);
|
||||
final Request request = httpClient.newRequest(baseUri + mUri);
|
||||
request.method(HttpMethod.GET);
|
||||
|
||||
ContentResponse response = executeRequest(request, callback);
|
||||
if (callback == null && response != null) {
|
||||
int statusCode = response.getStatus();
|
||||
|
||||
if (statusCode == HttpStatus.OK_200) {
|
||||
return response.getContentAsString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonObject doRequest(String req) {
|
||||
return doRequest(req, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonObject doRequest(String req, @Nullable SiemensHvacCallback callback) {
|
||||
try {
|
||||
String response = doBasicRequest(req, callback);
|
||||
|
||||
if (response != null) {
|
||||
JsonObject resultObj = getGson().fromJson(response, JsonObject.class);
|
||||
|
||||
if (resultObj != null && resultObj.has("Result")) {
|
||||
JsonObject subResultObj = resultObj.getAsJsonObject("Result");
|
||||
|
||||
if (subResultObj.has("Success")) {
|
||||
boolean result = subResultObj.get("Success").getAsBoolean();
|
||||
if (result) {
|
||||
return resultObj;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (SiemensHvacException e) {
|
||||
logger.warn("siemensHvac:DoRequest:Exception by executing jsonRequest: {} ; {} ", req,
|
||||
e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayRequestStats() {
|
||||
logger.debug("DisplayRequestStats: ");
|
||||
logger.debug(" currentRuning : {}", getCurrentHandlerRegistryCount());
|
||||
logger.debug(" errors : {}", getHandlerInErrorRegistryCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitAllPendingRequest() {
|
||||
logger.debug("WaitAllPendingRequest:start");
|
||||
try {
|
||||
boolean allRequestDone = false;
|
||||
int idx = 0;
|
||||
|
||||
while (!allRequestDone) {
|
||||
allRequestDone = false;
|
||||
int currentRequestCount = getCurrentHandlerRegistryCount();
|
||||
|
||||
logger.debug("WaitAllPendingRequest:waitAllRequestDone {}: {}", idx, currentRequestCount);
|
||||
|
||||
if (currentRequestCount == 0) {
|
||||
allRequestDone = true;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
|
||||
if ((idx % 50) == 0) {
|
||||
checkStaleRequest();
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
logger.debug("WaitAllPendingRequest:interrupted in WaitAllRequest");
|
||||
}
|
||||
|
||||
logger.debug("WaitAllPendingRequest:end WaitAllPendingRequest");
|
||||
}
|
||||
|
||||
public void checkStaleRequest() {
|
||||
synchronized (currentHandlerRegistry) {
|
||||
logger.debug("check stale request::begin");
|
||||
int staleRequest = 0;
|
||||
|
||||
for (SiemensHvacRequestHandler handler : currentHandlerRegistry.keySet()) {
|
||||
long elapseTime = handler.getElapsedTime();
|
||||
if (elapseTime > 150) {
|
||||
String uri = "";
|
||||
Request request = handler.getRequest();
|
||||
if (request != null) {
|
||||
uri = request.getURI().toString();
|
||||
}
|
||||
logger.debug("find stale request: {} {}", elapseTime, anominized(uri));
|
||||
staleRequest++;
|
||||
|
||||
try {
|
||||
unregisterRequestHandler(handler);
|
||||
registerHandlerError(handler);
|
||||
} catch (SiemensHvacException ex) {
|
||||
logger.debug("error unregistring handler: {}", handler);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("check stale request::end: {}", staleRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public String anominized(String uri) {
|
||||
int p0 = uri.indexOf("pwd=");
|
||||
if (p0 > 0) {
|
||||
return uri.substring(0, p0) + "pwd=xxxxx";
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private int getCurrentHandlerRegistryCount() {
|
||||
synchronized (currentHandlerRegistry) {
|
||||
return currentHandlerRegistry.keySet().size();
|
||||
}
|
||||
}
|
||||
|
||||
private int getHandlerInErrorRegistryCount() {
|
||||
synchronized (handlerInErrorRegistry) {
|
||||
return handlerInErrorRegistry.keySet().size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitNoNewRequest() {
|
||||
logger.debug("WaitNoNewRequest:start");
|
||||
try {
|
||||
int lastRequestCount = getCurrentHandlerRegistryCount();
|
||||
boolean newRequest = true;
|
||||
while (newRequest) {
|
||||
Thread.sleep(5000);
|
||||
int newRequestCount = getCurrentHandlerRegistryCount();
|
||||
if (newRequestCount != lastRequestCount) {
|
||||
logger.debug("waitNoNewRequest {}/{})", newRequestCount, lastRequestCount);
|
||||
lastRequestCount = newRequestCount;
|
||||
} else {
|
||||
newRequest = false;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
logger.debug("WaitAllPendingRequest:interrupted in WaitAllRequest");
|
||||
}
|
||||
|
||||
logger.debug("WaitNoNewRequest:end WaitAllStartingRequest");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gson getGson() {
|
||||
return gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gson getGsonWithAdapter() {
|
||||
return gsonWithAdapter;
|
||||
}
|
||||
|
||||
public void addDpUpdate(String itemName, Type dp) {
|
||||
synchronized (updateCommand) {
|
||||
updateCommand.put(itemName, dp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetSessionId(@Nullable String sessionIdToInvalidate, boolean web) {
|
||||
if (web) {
|
||||
if (sessionIdToInvalidate == null) {
|
||||
sessionIdHttp = null;
|
||||
} else {
|
||||
if (!oldSessionId.containsKey(sessionIdToInvalidate) && sessionIdToInvalidate.equals(sessionIdHttp)) {
|
||||
oldSessionId.put(sessionIdToInvalidate, true);
|
||||
|
||||
logger.debug("Invalidate sessionIdHttp: {}", sessionIdToInvalidate);
|
||||
sessionIdHttp = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sessionIdToInvalidate == null) {
|
||||
sessionId = null;
|
||||
} else {
|
||||
if (!oldSessionId.containsKey(sessionIdToInvalidate) && sessionIdToInvalidate.equals(sessionId)) {
|
||||
oldSessionId.put(sessionIdToInvalidate, true);
|
||||
|
||||
logger.debug("Invalidate sessionId: {}", sessionIdToInvalidate);
|
||||
sessionId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestCount() {
|
||||
return requestCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SiemensHvacRequestListener.ErrorSource getErrorSource() {
|
||||
return errorSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
sessionId = null;
|
||||
sessionIdHttp = null;
|
||||
|
||||
synchronized (currentHandlerRegistry) {
|
||||
currentHandlerRegistry.clear();
|
||||
handlerInErrorRegistry.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeOut(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.network;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
|
||||
/**
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacRequestHandler {
|
||||
private SiemensHvacConnector hvacConnector;
|
||||
|
||||
private int retryCount = 0;
|
||||
|
||||
@Nullable
|
||||
private Request request = null;
|
||||
|
||||
@Nullable
|
||||
private Response response = null;
|
||||
|
||||
@Nullable
|
||||
private Result result = null;
|
||||
|
||||
private Instant startRequest;
|
||||
|
||||
/**
|
||||
* Callback to execute on complete response
|
||||
*/
|
||||
private final SiemensHvacCallback callback;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param callback Callback which execute method has to be called.
|
||||
*/
|
||||
public SiemensHvacRequestHandler(SiemensHvacCallback callback, SiemensHvacConnector hvacConnector) {
|
||||
this.callback = callback;
|
||||
this.hvacConnector = hvacConnector;
|
||||
startRequest = Instant.now();
|
||||
}
|
||||
|
||||
public SiemensHvacConnector getHvacConnector() {
|
||||
return hvacConnector;
|
||||
}
|
||||
|
||||
public SiemensHvacCallback getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
public void incrementRetryCount() {
|
||||
retryCount++;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Response getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Request getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Result getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResponse(@Nullable Response response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public void setRequest(@Nullable Request request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public void setResult(@Nullable Result result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public long getElapsedTime() {
|
||||
Instant finish = Instant.now();
|
||||
return Duration.between(startRequest, finish).toSeconds();
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.network;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Request.BeginListener;
|
||||
import org.eclipse.jetty.client.api.Request.QueuedListener;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Response.CompleteListener;
|
||||
import org.eclipse.jetty.client.api.Response.ContentListener;
|
||||
import org.eclipse.jetty.client.api.Response.FailureListener;
|
||||
import org.eclipse.jetty.client.api.Response.SuccessListener;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.openhab.binding.siemenshvac.internal.type.SiemensHvacException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacRequestListener extends BufferingResponseListener
|
||||
implements SuccessListener, FailureListener, ContentListener, CompleteListener, QueuedListener, BeginListener {
|
||||
|
||||
public enum ErrorSource {
|
||||
ErrorBridge,
|
||||
ErrorThings
|
||||
}
|
||||
|
||||
private static int onSuccessCount = 0;
|
||||
private static int onBeginCount = 0;
|
||||
private static int onQueuedCount = 0;
|
||||
private static int onCompleteCount = 0;
|
||||
private static int onFailureCount = 0;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SiemensHvacRequestListener.class);
|
||||
|
||||
private SiemensHvacRequestHandler requestHandler;
|
||||
private SiemensHvacConnector hvacConnector;
|
||||
|
||||
/**
|
||||
* Callback to execute on complete response
|
||||
*/
|
||||
private final SiemensHvacCallback callback;
|
||||
|
||||
public static int getQueuedCount() {
|
||||
return onQueuedCount;
|
||||
}
|
||||
|
||||
public static int getStartedCount() {
|
||||
return onBeginCount;
|
||||
}
|
||||
|
||||
public static int getCompleteCount() {
|
||||
return onCompleteCount;
|
||||
}
|
||||
|
||||
public static int getFailureCount() {
|
||||
return onFailureCount;
|
||||
}
|
||||
|
||||
public static int getSuccessCount() {
|
||||
return onSuccessCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param callback Callback which execute method has to be called.
|
||||
*/
|
||||
public SiemensHvacRequestListener(SiemensHvacRequestHandler requestHandler) {
|
||||
this.requestHandler = requestHandler;
|
||||
this.hvacConnector = requestHandler.getHvacConnector();
|
||||
this.callback = requestHandler.getCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable Response response) {
|
||||
onSuccessCount++;
|
||||
requestHandler.setResponse(response);
|
||||
|
||||
if (response != null) {
|
||||
logger.debug("{} response: {}", response.getRequest().getURI(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nullable Response response, @Nullable Throwable failure) {
|
||||
onFailureCount++;
|
||||
requestHandler.setResponse(response);
|
||||
|
||||
if (response != null && failure != null) {
|
||||
Throwable cause = failure.getCause();
|
||||
if (cause == null) {
|
||||
cause = failure;
|
||||
}
|
||||
|
||||
String msg = cause.getLocalizedMessage();
|
||||
|
||||
if (cause instanceof ConnectException e) {
|
||||
logger.debug("ConnectException during request: {} {}", response.getRequest().getURI(), msg, e);
|
||||
} else if (cause instanceof SocketException e) {
|
||||
logger.debug("SocketException during request: {} {}", response.getRequest().getURI(), msg, e);
|
||||
} else if (cause instanceof SocketTimeoutException e) {
|
||||
logger.debug("SocketTimeoutException during request: {} {}", response.getRequest().getURI(), msg, e);
|
||||
} else if (cause instanceof EOFException e) {
|
||||
logger.debug("EOFException during request: {} {}", response.getRequest().getURI(), msg, e);
|
||||
} else if (cause instanceof TimeoutException e) {
|
||||
logger.debug("TimeoutException during request: {} {}", response.getRequest().getURI(), msg, e);
|
||||
} else {
|
||||
logger.debug("Response failed: {} {}", response.getRequest().getURI(), msg, failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueued(@Nullable Request request) {
|
||||
onQueuedCount++;
|
||||
requestHandler.setRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(@Nullable Request request) {
|
||||
onBeginCount++;
|
||||
requestHandler.setRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
onCompleteCount++;
|
||||
requestHandler.setResult(result);
|
||||
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String content = getContentAsString();
|
||||
logger.trace("response complete: {}", content);
|
||||
boolean mayRetry = true;
|
||||
|
||||
if (result.getResponse().getStatus() != 200) {
|
||||
logger.debug("Error requesting gateway, non success code: {}", result.getResponse().getStatus());
|
||||
hvacConnector.onError(result.getRequest(), requestHandler, ErrorSource.ErrorBridge, mayRetry);
|
||||
return;
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
if (content.indexOf("<!DOCTYPE html>") >= 0) {
|
||||
hvacConnector.onComplete(result.getRequest(), requestHandler);
|
||||
callback.execute(result.getRequest().getURI(), result.getResponse().getStatus(), content);
|
||||
} else {
|
||||
JsonObject resultObj = null;
|
||||
try {
|
||||
Gson gson = hvacConnector.getGson();
|
||||
resultObj = gson.fromJson(content, JsonObject.class);
|
||||
} catch (JsonSyntaxException ex) {
|
||||
logger.debug("error(1): {}", ex.toString());
|
||||
}
|
||||
|
||||
if (resultObj != null && resultObj.has("Result")) {
|
||||
JsonObject subResultObj = resultObj.getAsJsonObject("Result");
|
||||
|
||||
if (subResultObj.has("Success")) {
|
||||
boolean resultVal = subResultObj.get("Success").getAsBoolean();
|
||||
JsonObject error = subResultObj.getAsJsonObject("Error");
|
||||
String errorMsg = "";
|
||||
if (error != null) {
|
||||
errorMsg = error.get("Txt").getAsString();
|
||||
}
|
||||
|
||||
if (errorMsg.indexOf("session") >= 0) {
|
||||
String query = result.getRequest().getURI().getQuery();
|
||||
String sessionId = SiemensHvacConnectorImpl.extractSessionId(query);
|
||||
|
||||
hvacConnector.resetSessionId(sessionId, false);
|
||||
hvacConnector.resetSessionId(sessionId, true);
|
||||
mayRetry = false;
|
||||
}
|
||||
|
||||
if (resultVal) {
|
||||
hvacConnector.onComplete(result.getRequest(), requestHandler);
|
||||
callback.execute(result.getRequest().getURI(), result.getResponse().getStatus(),
|
||||
resultObj);
|
||||
|
||||
return;
|
||||
} else if (("datatype not supported").equals(errorMsg)) {
|
||||
hvacConnector.onComplete(result.getRequest(), requestHandler);
|
||||
callback.execute(result.getRequest().getURI(), result.getResponse().getStatus(),
|
||||
resultObj);
|
||||
return;
|
||||
} else if (("read failed").equals(errorMsg)) {
|
||||
logger.debug("error(2): {}", subResultObj);
|
||||
hvacConnector.onError(result.getRequest(), requestHandler, ErrorSource.ErrorThings,
|
||||
mayRetry);
|
||||
} else {
|
||||
logger.debug("error(3): {}", subResultObj);
|
||||
hvacConnector.onError(result.getRequest(), requestHandler, ErrorSource.ErrorBridge,
|
||||
mayRetry);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
logger.debug("error(4): invalid response from gateway, missing subResultObj:Success entry");
|
||||
hvacConnector.onError(result.getRequest(), requestHandler, ErrorSource.ErrorBridge,
|
||||
mayRetry);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.debug("error(5): invalid response from gateway, missing Result entry");
|
||||
hvacConnector.onError(result.getRequest(), requestHandler, ErrorSource.ErrorBridge, mayRetry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("error: content == null");
|
||||
hvacConnector.onError(result.getRequest(), requestHandler, ErrorSource.ErrorBridge, mayRetry);
|
||||
return;
|
||||
}
|
||||
} catch (SiemensHvacException ex) {
|
||||
logger.debug("An error occurred", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.type.ChannelGroupType;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
|
||||
/**
|
||||
* Extends the ChannelGroupTypeProvider to manually add a ChannelGroupType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SiemensHvacChannelGroupTypeProvider extends ChannelGroupTypeProvider {
|
||||
|
||||
/**
|
||||
* Adds the ChannelGroupType to this provider.
|
||||
*/
|
||||
void addChannelGroupType(ChannelGroupType channelGroupType);
|
||||
|
||||
/**
|
||||
* Use this method to lookup a ChannelGroupType which was generated by the siemensHvac binding.
|
||||
*/
|
||||
@Nullable
|
||||
ChannelGroupType getInternalChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID);
|
||||
|
||||
void invalidate();
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.type.ChannelGroupType;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* Provides all ChannelGroupTypes from all SiemensHvac bridges.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { SiemensHvacChannelGroupTypeProvider.class, ChannelGroupTypeProvider.class })
|
||||
public class SiemensHvacChannelGroupTypeProviderImpl implements SiemensHvacChannelGroupTypeProvider {
|
||||
|
||||
private final Map<ChannelGroupTypeUID, ChannelGroupType> channelGroupTypesByUID = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelGroupType getInternalChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID) {
|
||||
return channelGroupTypesByUID.get(channelGroupTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChannelGroupType(ChannelGroupType channelGroupType) {
|
||||
channelGroupTypesByUID.put(channelGroupType.getUID(), channelGroupType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID,
|
||||
@Nullable Locale locale) {
|
||||
return channelGroupTypesByUID.get(channelGroupTypeUID);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see ChannelTypeRegistr#getChannelGroupTypes(Locale)
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public Collection<ChannelGroupType> getChannelGroupTypes(@Nullable Locale locale) {
|
||||
Collection<ChannelGroupType> result = new ArrayList<>();
|
||||
for (ChannelGroupTypeUID uid : channelGroupTypesByUID.keySet()) {
|
||||
ChannelGroupType groupType = channelGroupTypesByUID.get(uid);
|
||||
if (groupType != null) {
|
||||
result.add(groupType);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
channelGroupTypesByUID.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* Extends the ChannelTypeProvider to manually add a ChannelType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SiemensHvacChannelTypeProvider extends ChannelTypeProvider {
|
||||
|
||||
/**
|
||||
* Adds the ChannelType to this provider.
|
||||
*/
|
||||
void addChannelType(ChannelType channelType);
|
||||
|
||||
/**
|
||||
* Use this method to lookup a ChannelType which was generated by the siemensHvac binding.
|
||||
*
|
||||
* @param channelTypeUID
|
||||
* @return ChannelType that was added to SiemensHvacChannelTypeProvider, identified by its
|
||||
* config-description-uri<br>
|
||||
* <i>null</i> if no ChannelType with the given UID was added
|
||||
* before
|
||||
*/
|
||||
@Nullable
|
||||
ChannelType getInternalChannelType(@Nullable ChannelTypeUID channelTypeUID);
|
||||
|
||||
void invalidate();
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* Provides all ChannelTypes from SiemensHvac bridges.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { SiemensHvacChannelTypeProvider.class, ChannelTypeProvider.class })
|
||||
public class SiemensHvacChannelTypeProviderImpl implements SiemensHvacChannelTypeProvider {
|
||||
private final Map<ChannelTypeUID, ChannelType> channelTypesByUID = new HashMap<>();
|
||||
|
||||
public SiemensHvacChannelTypeProviderImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChannelType(ChannelType channelType) {
|
||||
channelTypesByUID.put(channelType.getUID(), channelType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
|
||||
Collection<ChannelType> result = new ArrayList<>();
|
||||
|
||||
for (ChannelTypeUID uid : channelTypesByUID.keySet()) {
|
||||
ChannelType tp = channelTypesByUID.get(uid);
|
||||
if (tp != null) {
|
||||
result.add(tp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ChannelTypeRegistr#getChannelType(ChannelTypeUID, Locale)
|
||||
*/
|
||||
@Override
|
||||
public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
|
||||
return channelTypesByUID.get(channelTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelType getInternalChannelType(@Nullable ChannelTypeUID channelTypeUID) {
|
||||
return channelTypesByUID.get(channelTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
channelTypesByUID.clear();
|
||||
}
|
||||
}
|
@ -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.siemenshvac.internal.type;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionProvider;
|
||||
|
||||
/**
|
||||
* Extends the ConfigDescriptionProvider to manually add a ConfigDescription.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SiemensHvacConfigDescriptionProvider extends ConfigDescriptionProvider {
|
||||
|
||||
/**
|
||||
* Adds the ConfigDescription to this provider.
|
||||
*/
|
||||
void addConfigDescription(ConfigDescription configDescription);
|
||||
|
||||
/**
|
||||
* Provides a {@link ConfigDescription} for the given URI.
|
||||
*
|
||||
* @param uri uri of the config description
|
||||
* @param locale locale
|
||||
*
|
||||
* @return config description or null if no config description could be found
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
ConfigDescription getConfigDescription(URI uri, @Nullable Locale locale);
|
||||
|
||||
/**
|
||||
* Use this method to lookup a ConfigDescription which was generated by the
|
||||
* siemenshvac binding.
|
||||
*
|
||||
*/
|
||||
@Nullable
|
||||
ConfigDescription getInternalConfigDescription(URI uri);
|
||||
|
||||
void invalidate();
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { SiemensHvacConfigDescriptionProvider.class, ConfigDescriptionProvider.class })
|
||||
public class SiemensHvacConfigDescriptionProviderImpl implements SiemensHvacConfigDescriptionProvider {
|
||||
private Map<URI, ConfigDescription> configDescriptionsByURI = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Collection<ConfigDescription> getConfigDescriptions(@Nullable Locale locale) {
|
||||
Collection<ConfigDescription> result = new ArrayList<>();
|
||||
for (URI configDescriptionURI : configDescriptionsByURI.keySet()) {
|
||||
ConfigDescription desc = configDescriptionsByURI.get(configDescriptionURI);
|
||||
if (desc != null) {
|
||||
result.add(desc);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConfigDescription getConfigDescription(URI uri, @Nullable Locale locale) {
|
||||
return configDescriptionsByURI.get(uri);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ConfigDescription getInternalConfigDescription(URI uri) {
|
||||
return configDescriptionsByURI.get(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigDescription(ConfigDescription configDescription) {
|
||||
configDescriptionsByURI.put(configDescription.getUID(), configDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
configDescriptionsByURI.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* An exception that occurred while operating the binding
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class SiemensHvacException extends Exception {
|
||||
private static final long serialVersionUID = -3398100220952729816L;
|
||||
|
||||
public SiemensHvacException(String message, Exception e) {
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
public SiemensHvacException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
|
||||
/**
|
||||
* Extends the ThingTypeProvider to manually add a ThingType.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SiemensHvacThingTypeProvider extends ThingTypeProvider {
|
||||
|
||||
/**
|
||||
* Adds the ThingType to this provider.
|
||||
*/
|
||||
void addThingType(ThingType thingType);
|
||||
|
||||
/**
|
||||
* Use this method to lookup a ThingType which was generated by the
|
||||
* binding. Other than {@link #getThingType(ThingTypeUID)}
|
||||
* of this provider, it will return also those {@link ThingType}s which are
|
||||
* excluded by {@link ThingTypeExcluder}
|
||||
*
|
||||
* @param thingTypeUID
|
||||
* @return ThingType that was added to SiemensHvacThingTypeProvider, identified
|
||||
* by its thingTypeUID<br>
|
||||
* <i>null</i> if no ThingType with the given thingTypeUID was added
|
||||
* before
|
||||
*/
|
||||
@Nullable
|
||||
ThingType getInternalThingType(ThingTypeUID thingTypeUID);
|
||||
|
||||
void invalidate();
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* Provides all ThingTypes from SiemensHvac bridges.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { SiemensHvacThingTypeProvider.class, ThingTypeProvider.class }, immediate = true)
|
||||
public class SiemensHvacThingTypeProviderImpl implements SiemensHvacThingTypeProvider {
|
||||
|
||||
private Map<ThingTypeUID, ThingType> thingTypesByUID = new HashMap<>();
|
||||
|
||||
public SiemensHvacThingTypeProviderImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addThingType(ThingType thingType) {
|
||||
thingTypesByUID.put(thingType.getUID(), thingType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingType getInternalThingType(ThingTypeUID thingTypeUID) {
|
||||
return thingTypesByUID.get(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
|
||||
Map<ThingTypeUID, ThingType> copy = new HashMap<>(thingTypesByUID);
|
||||
return copy.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) {
|
||||
return thingTypesByUID.get(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
thingTypesByUID.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import java.text.Normalizer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.siemenshvac.internal.constants.SiemensHvacBindingConstants;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterFactory;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.ConverterTypeException;
|
||||
import org.openhab.binding.siemenshvac.internal.converter.TypeConverter;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDataPoint;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataDevice;
|
||||
import org.openhab.binding.siemenshvac.internal.metadata.SiemensHvacMetadataMenu;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* Utility class for generating some UIDs.
|
||||
*
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UidUtils {
|
||||
/**
|
||||
* The methods remove specific local character (like 'é'/'ê','â') so we have a correctly formated UID from a
|
||||
* localize item label
|
||||
*
|
||||
* @param label
|
||||
* @return the label without invalid character
|
||||
*/
|
||||
public static String sanetizeId(String label) {
|
||||
String result = label;
|
||||
|
||||
if (!Normalizer.isNormalized(label, Normalizer.Form.NFKD)) {
|
||||
result = Normalizer.normalize(label, Normalizer.Form.NFKD);
|
||||
result = result.replaceAll("\\p{M}", "");
|
||||
}
|
||||
|
||||
result = result.replaceAll("[^a-zA-Z0-9_]", "-").toLowerCase();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the ThingTypeUID for the given device. If it's a Homegear device, add a prefix because a Homegear
|
||||
* device has more datapoints.
|
||||
*/
|
||||
public static ThingTypeUID generateThingTypeUID(SiemensHvacMetadataDevice device) {
|
||||
String type = sanetizeId(device.getType());
|
||||
return new ThingTypeUID(SiemensHvacBindingConstants.BINDING_ID, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a more user friendly description from English short descriptor
|
||||
*
|
||||
* @param descriptor
|
||||
* @return
|
||||
*/
|
||||
private static String normalizeDescriptor(String descriptor) {
|
||||
String result = descriptor.trim();
|
||||
|
||||
if (result.indexOf("CC") >= 0 || result.indexOf("HC") >= 0) {
|
||||
for (int idx = 0; idx < 4; idx++) {
|
||||
result = result.replace("CC" + idx, "CC");
|
||||
result = result.replace("HC" + idx, "HC");
|
||||
}
|
||||
}
|
||||
|
||||
result = result.toLowerCase();
|
||||
|
||||
if (result.indexOf("history") >= 0) {
|
||||
for (int idx = 0; idx < 20; idx++) {
|
||||
result = result.replace("history " + idx, "history");
|
||||
}
|
||||
}
|
||||
|
||||
result = result.replace(" mon", "");
|
||||
result = result.replace(" tue", "");
|
||||
result = result.replace(" wed", "");
|
||||
result = result.replace(" thu", "");
|
||||
result = result.replace(" fri", "");
|
||||
result = result.replace(" sat", "");
|
||||
result = result.replace(" sun", "");
|
||||
result = result.replace(" mo", "");
|
||||
result = result.replace(" tu", "");
|
||||
result = result.replace(" we", "");
|
||||
result = result.replace(" th", "");
|
||||
result = result.replace(" fr", "");
|
||||
result = result.replace(" sa", "");
|
||||
result = result.replace(" su", "");
|
||||
|
||||
if (result.indexOf("holidays") >= 0) {
|
||||
if (result.indexOf("firstd") >= 0) {
|
||||
result = "holidays-hc-firstd";
|
||||
}
|
||||
if (result.indexOf("lastd") >= 0) {
|
||||
result = "holidays-hc-lastd";
|
||||
}
|
||||
}
|
||||
|
||||
result = result.replace("---", "-");
|
||||
result = result.replace("--", "-");
|
||||
result = result.replace('\'', '-');
|
||||
result = result.replace('/', '-');
|
||||
result = result.replace(' ', '-');
|
||||
result = result.replace("+", "-");
|
||||
|
||||
result = result.replace("standard-tsp-hc", "time-switch-program-standard");
|
||||
result = result.replace("standard-tsp-4", "time-switch-program-standard");
|
||||
result = result.replace("tsp-3", "time-switch-program-day");
|
||||
result = result.replace("tsp-4", "time-switch-program-day");
|
||||
result = result.replace("setpointtemp", "setpoint-temp-");
|
||||
result = result.replace("rmtmp", "roomtemp");
|
||||
result = result.replace("roomtempfrostprot", "room-temp-frostprot-");
|
||||
result = result.replace("-setp", "-setpoint");
|
||||
result = result.replace("optg", "operating-");
|
||||
result = result.replace("-comf", "-comfort");
|
||||
result = result.replace("-red", "-reduce");
|
||||
result = result.replace("setp-", "-setpoint");
|
||||
result = result.replace("roomtemp-", "room-temp-");
|
||||
result = result.replace("-setpointhc", "-setpoint-hc");
|
||||
result = result.replace("setphc", "-setpoint-hc");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the ChannelTypeUID for the given datapoint with deviceType, channelNumber and datapointName.
|
||||
*/
|
||||
public static ChannelTypeUID generateChannelTypeUID(SiemensHvacMetadataDataPoint dpt) throws SiemensHvacException {
|
||||
String type = dpt.getDptType();
|
||||
String shortDesc = dpt.getShortDescEn();
|
||||
String result = normalizeDescriptor(shortDesc);
|
||||
|
||||
try {
|
||||
TypeConverter tp = ConverterFactory.getConverter(type);
|
||||
if (!tp.hasVariant()) {
|
||||
result = tp.getChannelType(dpt);
|
||||
}
|
||||
} catch (ConverterTypeException ex) {
|
||||
throw new SiemensHvacException(String.format("Can't find converter for type: %s", type), ex);
|
||||
}
|
||||
|
||||
return new ChannelTypeUID(SiemensHvacBindingConstants.BINDING_ID, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the ChannelTypeUID for the given datapoint with deviceType and channelNumber.
|
||||
*/
|
||||
public static ChannelGroupTypeUID generateChannelGroupTypeUID(SiemensHvacMetadataMenu menu) {
|
||||
return new ChannelGroupTypeUID(SiemensHvacBindingConstants.BINDING_ID, String.valueOf(menu.getId()));
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon xmlns:addon="https://openhab.org/schemas/addon/v1.0.0" id="siemenshvac"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<type>binding</type>
|
||||
<name>SiemensHvac Binding</name>
|
||||
<description>This is the binding for SiemensHvac.</description>
|
||||
<connection>local</connection>
|
||||
<discovery-methods>
|
||||
<discovery-method>
|
||||
<service-type>upnp</service-type>
|
||||
<match-properties>
|
||||
<match-property>
|
||||
<name>manufacturer</name>
|
||||
<regex>Siemens.*</regex>
|
||||
</match-property>
|
||||
<match-property>
|
||||
<name>modelName</name>
|
||||
<regex>Web Server OZW.*</regex>
|
||||
</match-property>
|
||||
</match-properties>
|
||||
</discovery-method>
|
||||
</discovery-methods>
|
||||
</addon:addon>
|
@ -0,0 +1,26 @@
|
||||
# add-on
|
||||
|
||||
addon.siemenshvac.name = SiemensHvac Binding
|
||||
addon.siemenshvac.description = This is the binding for SiemensHvac.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.siemenshvac.ozw.label = OZW IP Gateway
|
||||
thing-type.siemenshvac.ozw.description = This is a OZW IP interface
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.siemenshvac.ozw.baseUrl.label = Base URL
|
||||
thing-type.config.siemenshvac.ozw.baseUrl.description = The URL of the Siemens Hvac IP gateway. Must be in format http://hostname/ or https://hostname/. Don't forget the trailing '/'
|
||||
thing-type.config.siemenshvac.ozw.userName.label = User Name
|
||||
thing-type.config.siemenshvac.ozw.userName.description = User name of the Siemens Hvac gateway
|
||||
thing-type.config.siemenshvac.ozw.userPassword.label = User Password
|
||||
thing-type.config.siemenshvac.ozw.userPassword.description = User password of the Siemens Hvac gateway
|
||||
|
||||
# offline message
|
||||
|
||||
offline.baseurl-mandatory = baseUrl is mandatory on configuration.
|
||||
offline.error-gateway-init = Error occurred during gateway initialization [{0}]
|
||||
offline.config-not-init = Config not initialize during reading metadata, aborting.
|
||||
offline.user-not-find = Cannot find user during reading metadata, aborting.
|
||||
offline.waiting-bridge-initialization = Waiting bridge initialization, reading metadata in background
|
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="siemenshvac"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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 Type -->
|
||||
<bridge-type id="ozw">
|
||||
<label>OZW IP Gateway</label>
|
||||
<description>This is a OZW IP interface</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="baseUrl" type="text" pattern="(http|https):\/\/(.+)\/">
|
||||
<label>Base URL</label>
|
||||
<context>url</context>
|
||||
<description>The URL of the Siemens Hvac IP gateway. Must be in format http://hostname/ or https://hostname/. Don't
|
||||
forget the trailing '/'</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="userName" type="text">
|
||||
<description>User name of the Siemens Hvac gateway</description>
|
||||
<required>false</required>
|
||||
<label>User Name</label>
|
||||
<default>Administrator</default>
|
||||
</parameter>
|
||||
<parameter name="userPassword" type="text">
|
||||
<context>password</context>
|
||||
<description>User password of the Siemens Hvac gateway</description>
|
||||
<required>false</required>
|
||||
<label>User Password</label>
|
||||
<default>password</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.siemenshvac.internal.type;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Laurent Arnal - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UidUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testSanetizeId() throws Exception {
|
||||
assertEquals(UidUtils.sanetizeId("Début heure été"), "debut-heure-ete");
|
||||
assertEquals(UidUtils.sanetizeId("App.Ambiance 1"), "app-ambiance-1");
|
||||
assertEquals(UidUtils.sanetizeId("Appareil d'ambiance P"), "appareil-d-ambiance-p");
|
||||
}
|
||||
}
|
@ -362,6 +362,7 @@
|
||||
<module>org.openhab.binding.serialbutton</module>
|
||||
<module>org.openhab.binding.shelly</module>
|
||||
<module>org.openhab.binding.silvercrestwifisocket</module>
|
||||
<module>org.openhab.binding.siemenshvac</module>
|
||||
<module>org.openhab.binding.siemensrds</module>
|
||||
<module>org.openhab.binding.sinope</module>
|
||||
<module>org.openhab.binding.sleepiq</module>
|
||||
|
Loading…
Reference in New Issue
Block a user