diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index a8f06cc587bab..25689ba33061c 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1626,6 +1626,11 @@ org.openhab.binding.shelly ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.siemenshvac + ${project.version} + org.openhab.addons.bundles org.openhab.binding.siemensrds diff --git a/bundles/org.openhab.binding.siemenshvac/NOTICE b/bundles/org.openhab.binding.siemenshvac/NOTICE new file mode 100644 index 0000000000000..451e1f04ab2bf --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/NOTICE @@ -0,0 +1,20 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons + +== Third-party Content + +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 diff --git a/bundles/org.openhab.binding.siemenshvac/README.md b/bundles/org.openhab.binding.siemenshvac/README.md new file mode 100644 index 0000000000000..43bb340914edd --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/README.md @@ -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" } + +``` diff --git a/bundles/org.openhab.binding.siemenshvac/doc/Albatros.jpg b/bundles/org.openhab.binding.siemenshvac/doc/Albatros.jpg new file mode 100644 index 0000000000000..4fc744432f7fe Binary files /dev/null and b/bundles/org.openhab.binding.siemenshvac/doc/Albatros.jpg differ diff --git a/bundles/org.openhab.binding.siemenshvac/doc/Diagram.png b/bundles/org.openhab.binding.siemenshvac/doc/Diagram.png new file mode 100644 index 0000000000000..300cc2f858387 Binary files /dev/null and b/bundles/org.openhab.binding.siemenshvac/doc/Diagram.png differ diff --git a/bundles/org.openhab.binding.siemenshvac/pom.xml b/bundles/org.openhab.binding.siemenshvac/pom.xml new file mode 100644 index 0000000000000..cae4551ca5d30 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.2.0-SNAPSHOT + + + org.openhab.binding.siemenshvac + + openHAB Add-ons :: Bundles :: SiemensHvac Binding + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + add-source + + generate-sources + + + src/3rdparty/java + + + + + + + + diff --git a/bundles/org.openhab.binding.siemenshvac/src/3rdparty/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/bundles/org.openhab.binding.siemenshvac/src/3rdparty/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java new file mode 100644 index 0000000000000..a716848fb7412 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/3rdparty/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -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: + * + *
+ * {
+ *     @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;
+ *     }
+ * }
+ * 
+ *

+ * Without additional type information, the serialized JSON is ambiguous. Is + * the bottom shape in this drawing a rectangle or a diamond? + * + *

+ *    {@code
+ *   {
+ *     "bottomShape": {
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * 
+ * + * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized: + * + *
+ *    {@code
+ *   {
+ *     "bottomShape": {
+ *       "type": "Diamond",
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "type": "Circle",
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * 
+ * + * Both the type field name ({@code "type"}) and the type labels ({@code + * "Rectangle"}) are configurable. + * + *

Registering Types

+ * 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. + * + *
+ * {
+ *     @code
+ *     RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * 
+ * + * 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. + * + *
+ *    {@code
+ * shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
+ * shapeAdapter.registerSubtype(Circle.class, "Circle");
+ * shapeAdapter.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * 
+ * + * Finally, register the type adapter factory in your application's GSON builder: + * + *
+ * {
+ *     @code
+ *     Gson gson = new GsonBuilder().registerTypeAdapterFactory(shapeAdapterFactory).create();
+ * }
+ * 
+ * + * Like {@code GsonBuilder}, this API supports chaining: + * + *
+ * {
+ *     @code
+ *     RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ *             .registerSubtype(Rectangle.class).registerSubtype(Circle.class).registerSubtype(Diamond.class);
+ * }
+ * 
+ */ +/** + * @author Jesse Wilson (swankjesse) - Initial contribution + */ +@NonNullByDefault +public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap>(); + private final Map, String> subtypeToLabel = new LinkedHashMap, 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 RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as + * the type field name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory(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 registerSubtype(Class 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 registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + @Override + public @Nullable TypeAdapter create(@Nullable Gson gson, @Nullable TypeToken type) { + if (type == null || type.getRawType() != baseType) { + return null; + } + if (gson == null) { + return null; + } + + final Map> labelToDelegate = new LinkedHashMap>(); + final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap, TypeAdapter>(); + for (Map.Entry> 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() { + @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 delegate = (TypeAdapter) 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 delegate = (TypeAdapter) 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 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 JSON_ELEMENT = new TypeAdapter() { + @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 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); + } + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/feature/feature.xml b/bundles/org.openhab.binding.siemenshvac/src/main/feature/feature.xml new file mode 100644 index 0000000000000..a253c87858518 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/feature/feature.xml @@ -0,0 +1,25 @@ + + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + openhab-transport-upnp + openhab-transport-mdns + mvn:org.openhab.addons.bundles/org.openhab.binding.siemenshvac/${project.version} + + diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/constants/SiemensHvacBindingConstants.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/constants/SiemensHvacBindingConstants.java new file mode 100644 index 0000000000000..c4b6acee70f68 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/constants/SiemensHvacBindingConstants.java @@ -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 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"; +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterException.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterException.java new file mode 100644 index 0000000000000..d9b73d81070fb --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterException.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterFactory.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterFactory.java new file mode 100644 index 0000000000000..757c8eed4cd89 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterFactory.java @@ -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 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; + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterTypeException.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterTypeException.java new file mode 100644 index 0000000000000..3c3c6ba6db8bb --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/ConverterTypeException.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/TypeConverter.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/TypeConverter.java new file mode 100644 index 0000000000000..fbaf5efe0c43b --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/TypeConverter.java @@ -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(); +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/AbstractTypeConverter.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/AbstractTypeConverter.java new file mode 100644 index 0000000000000..69b4485e14d08 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/AbstractTypeConverter.java @@ -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(); +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/CalendarTypeConverter.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/CalendarTypeConverter.java new file mode 100644 index 0000000000000..b6576f74680a4 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/CalendarTypeConverter.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/CheckboxTypeConverter.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/CheckboxTypeConverter.java new file mode 100644 index 0000000000000..f6537b407b57b --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/CheckboxTypeConverter.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/DateTimeTypeConverter.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/DateTimeTypeConverter.java new file mode 100644 index 0000000000000..9d6ae69e3655b --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/DateTimeTypeConverter.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/EnumTypeConverter.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/EnumTypeConverter.java new file mode 100644 index 0000000000000..b23bfb1cad8c5 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/EnumTypeConverter.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/NumericTypeConverter.java b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/NumericTypeConverter.java new file mode 100644 index 0000000000000..0b9c895c35a98 --- /dev/null +++ b/bundles/org.openhab.binding.siemenshvac/src/main/java/org/openhab/binding/siemenshvac/internal/converter/type/NumericTypeConverter.java @@ -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 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 targetUnit = null; + + if ("V".equals(unit)) { + targetUnit = Units.VOLT; + } + + if (targetUnit != null) { + return new QuantityType<>(dValue, targetUnit); + } + } else if ("Number:Time".equals(itemType)) { + Unit