[siemenshvac] Initial contribution (#14263)

Signed-off-by: Laurent ARNAL <laurent@clae.net>
This commit is contained in:
lo92fr 2024-06-29 12:25:00 +02:00 committed by GitHub
parent 0786bfde9a
commit 80bd5bfcb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 7087 additions and 0 deletions

View File

@ -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>

View 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

View 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" }
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View 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>

View File

@ -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>
* {
* &#64;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>
* {
* &#64;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>
* {
* &#64;code
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(shapeAdapterFactory).create();
* }
* </pre>
*
* Like {@code GsonBuilder}, this API supports chaining:
*
* <pre>
* {
* &#64;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);
}
}
}

View File

@ -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>

View File

@ -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";
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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";
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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()));
}
}

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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");
}
}

View File

@ -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>