]> git.basschouten.com Git - openhab-addons.git/commitdiff
[tacmi] Unit of Measurement fixes, added missing DateTime support (#17481)
authorChristian Niessner <marvkis@users.noreply.github.com>
Sat, 5 Oct 2024 12:56:49 +0000 (14:56 +0200)
committerGitHub <noreply@github.com>
Sat, 5 Oct 2024 12:56:49 +0000 (14:56 +0200)
* [tacmi] Use US Locale to format float numbers.

The German locale uses a comma as a separator for decimal numbers,
which means that the C.M.I. only uses the full number.

Signed-off-by: Christian Niessner <github-marvkis@christian-niessner.de>
bundles/org.openhab.binding.tacmi/src/main/java/org/openhab/binding/tacmi/internal/schema/ApiPageParser.java
bundles/org.openhab.binding.tacmi/src/main/java/org/openhab/binding/tacmi/internal/schema/ChangerX2Entry.java
bundles/org.openhab.binding.tacmi/src/main/java/org/openhab/binding/tacmi/internal/schema/ChangerX2Parser.java
bundles/org.openhab.binding.tacmi/src/main/java/org/openhab/binding/tacmi/internal/schema/TACmiSchemaHandler.java

index 9fa37d8541319905f98b7aa2d587afc5baba3a12..d4b8d6175af1d47501ae6f9b6f99f31bb593e11b 100644 (file)
@@ -15,6 +15,10 @@ package org.openhab.binding.tacmi.internal.schema;
 import java.math.BigDecimal;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -33,6 +37,7 @@ import org.eclipse.jetty.util.StringUtil;
 import org.openhab.binding.tacmi.internal.TACmiBindingConstants;
 import org.openhab.binding.tacmi.internal.TACmiChannelTypeProvider;
 import org.openhab.binding.tacmi.internal.schema.ApiPageEntry.Type;
+import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.QuantityType;
@@ -196,7 +201,18 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
                     sb = sb.delete(0, 0);
                 }
                 if (this.fieldType == FieldType.READ_ONLY || this.fieldType == FieldType.FORM_VALUE) {
+                    int len = sb.length();
                     int lids = sb.lastIndexOf(":");
+                    if (len - lids == 3) {
+                        int lids2 = sb.lastIndexOf(":", lids - 1);
+                        if (lids2 > 0 && (lids - lids2 >= 3 && lids - lids2 <= 7)) {
+                            // the given value might be a time. validate it
+                            String timeCandidate = sb.substring(lids2 + 1).trim();
+                            if (timeCandidate.length() == 5 && timeCandidate.matches("[0-9]{2}:[0-9]{2}")) {
+                                lids = lids2;
+                            }
+                        }
+                    }
                     int fsp = sb.indexOf(" ");
                     if (fsp < 0 || lids < 0 || fsp > lids) {
                         logger.debug("Invalid format for setting {}:{}:{} [{}] : {}", id, line, col, this.fieldType,
@@ -350,7 +366,7 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
                         // for the older pre-X2 devices (i.e. the UVR 1611) we get a comma. So we
                         // we replace all ',' with '.' to check if it's a valid number...
                         String val = valParts[0].replace(',', '.');
-                        BigDecimal bd = new BigDecimal(val);
+                        float bd = Float.parseFloat(val);
                         if (valParts.length == 2) {
                             if ("°C".equals(valParts[1])) {
                                 channelType = "Number:Temperature";
@@ -374,15 +390,14 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
                                 state = new QuantityType<>(bd, Units.HERTZ);
                             } else if ("kW".equals(valParts[1])) {
                                 channelType = "Number:Power";
-                                bd = bd.multiply(new BigDecimal(1000));
+                                bd = bd *= 1000;
                                 state = new QuantityType<>(bd, Units.WATT);
                             } else if ("kWh".equals(valParts[1])) {
-                                channelType = "Number:Power";
-                                bd = bd.multiply(new BigDecimal(1000));
+                                channelType = "Number:Energy";
                                 state = new QuantityType<>(bd, Units.KILOWATT_HOUR);
                             } else if ("l/h".equals(valParts[1])) {
-                                channelType = "Number:Volume";
-                                bd = bd.divide(new BigDecimal(60));
+                                channelType = "Number:VolumetricFlowRate";
+                                bd = bd /= 60;
                                 state = new QuantityType<>(bd, Units.LITRE_PER_MINUTE);
                             } else {
                                 channelType = "Number";
@@ -402,16 +417,27 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
                             type = Type.NUMERIC_FORM;
                         }
                     } catch (NumberFormatException nfe) {
-                        // not a number...
-                        channelType = "String";
+                        ctuid = null;
+                        // check for time....
+                        String[] valParts = vs.split(":");
+                        if (valParts.length == 2) {
+                            channelType = "DateTime";
+                            // convert it to zonedDateTime with today as date and the
+                            // default timezone.
+                            var zdt = LocalTime.parse(vs, DateTimeFormatter.ofPattern("HH:mm")).atDate(LocalDate.now())
+                                    .atZone(ZoneId.systemDefault());
+                            state = new DateTimeType(zdt);
+                            type = Type.NUMERIC_FORM;
+                        } else {
+                            // not a number and not time...
+                            channelType = "String";
+                            state = new StringType(vs);
+                            type = Type.STATE_FORM;
+                        }
                         if (this.fieldType == FieldType.READ_ONLY || this.address == null) {
                             ctuid = TACmiBindingConstants.CHANNEL_TYPE_SCHEME_STATE_RO_UID;
                             type = Type.READ_ONLY_STATE;
-                        } else {
-                            ctuid = null;
-                            type = Type.STATE_FORM;
                         }
-                        state = new StringType(vs);
                     }
                 }
                 break;
@@ -440,7 +466,29 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
                     logger.warn("Error loading API Scheme: {} ", ex.getMessage());
                 }
             }
-            if (channel == null || !Objects.equals(ctuid, channel.getChannelTypeUID())) {
+            if (e != null && !channelType.equals(e.channel.getAcceptedItemType())) {
+                // channel type has changed. we have to rebuild the channel.
+                this.channels.remove(channel);
+                channel = null;
+            }
+            if (channel != null && ctuid == null && cx2e != null) {
+                // custom channel type - check if it already exists and recreate when needed...
+                ChannelTypeUID curCtuid = channel.getChannelTypeUID();
+                if (curCtuid == null) {
+                    // we have to re-create and re-register the channel uuid
+                    logger.debug("Re-Registering channel type UUID for: {} ", shortName);
+                    var ct = buildAndRegisterChannelType(shortName, type, cx2e);
+                    var channelBuilder = ChannelBuilder.create(channel);
+                    channelBuilder.withType(ct.getUID());
+                    channel = channelBuilder.build(); // update channel
+                } else {
+                    // check if channel uuid still exists and re-carete when needed
+                    ChannelType ct = channelTypeProvider.getChannelType(curCtuid, null);
+                    if (ct == null) {
+                        buildAndRegisterChannelType(shortName, type, cx2e);
+                    }
+                }
+            } else if (channel == null || !Objects.equals(ctuid, channel.getChannelTypeUID())) {
                 logger.debug("Creating / updating channel {} of type {} for '{}'", shortName, channelType, description);
                 this.configChanged = true;
                 ChannelUID channelUID = new ChannelUID(this.taCmiSchemaHandler.getThing().getUID(), shortName);
@@ -456,15 +504,6 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
                     logger.warn("Error configurating channel for {}: channeltype cannot be determined!", shortName);
                 }
                 channel = channelBuilder.build(); // add configuration property...
-            } else if (ctuid == null && cx2e != null) {
-                // custom channel type - check if it already exists and recreate when needed...
-                ChannelTypeUID curCtuid = channel.getChannelTypeUID();
-                if (curCtuid != null) {
-                    ChannelType ct = channelTypeProvider.getChannelType(curCtuid, null);
-                    if (ct == null) {
-                        buildAndRegisterChannelType(shortName, type, cx2e);
-                    }
-                }
             }
             this.configChanged = true;
             e = new ApiPageEntry(type, channel, address, cx2e, state);
@@ -543,8 +582,11 @@ public class ApiPageParser extends AbstractSimpleMarkupHandler {
                     }
                 }
                 break;
+            case TIME:
+                itemType = "DateTime";
+                break;
             default:
-                throw new IllegalStateException();
+                throw new IllegalStateException("Unhandled OptionType: " + cx2e.optionType);
         }
         ChannelTypeBuilder<?> ctb = ChannelTypeBuilder
                 .state(new ChannelTypeUID(TACmiBindingConstants.BINDING_ID, shortName), shortName, itemType)
index a4f9e7494767bc723d5a0696aea8c5e4fcfe23ef..1b78d42119997ea39b4249fa12400938b644e4b4 100644 (file)
@@ -33,6 +33,7 @@ public class ChangerX2Entry {
     enum OptionType {
         NUMBER,
         SELECT,
+        TIME,
     }
 
     /**
index fc06ae5468e7b7f7a47f8597a76629c17b78d9e7..a084ed3d9909889fe21990c9c4f251f040267352 100644 (file)
@@ -111,7 +111,7 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
                 String type = attributes.get("type");
                 if ("number".equals(type)) {
                     this.optionType = OptionType.NUMBER;
-                    // we transfer the limits from the input elemnt...
+                    // we transfer the limits from the input element...
                     this.options.put(ChangerX2Entry.NUMBER_MIN, attributes.get(ChangerX2Entry.NUMBER_MIN));
                     this.options.put(ChangerX2Entry.NUMBER_MAX, attributes.get(ChangerX2Entry.NUMBER_MAX));
                     this.options.put(ChangerX2Entry.NUMBER_STEP, attributes.get(ChangerX2Entry.NUMBER_STEP));
@@ -120,6 +120,45 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
                             col, attributes);
                 }
             }
+        } else if ((this.parserState == ParserState.INIT || this.parserState == ParserState.INPUT)
+                && "input".equals(elementName) && "changetotimeh".equals(id)) {
+            this.parserState = ParserState.INPUT_DATA;
+            if (attributes != null) {
+                this.optionFieldName = attributes.get("name");
+                String type = attributes.get("type");
+                if ("number".equals(attributes.get("type"))) {
+                    this.optionType = OptionType.TIME;
+                    // validate hour limits
+                    if (!"0".equals(attributes.get(ChangerX2Entry.NUMBER_MIN))
+                            || !"24".equals(attributes.get(ChangerX2Entry.NUMBER_MAX))) {
+                        logger.warn(
+                                "Error parsing options for {}: Unexpected MIN/MAX values for hour input field in {}:{}: {}",
+                                channelName, line, col, attributes);
+                    }
+                    ;
+                } else {
+                    logger.warn("Error parsing options for {}: Unhandled input field in {}:{}: {}", channelName, line,
+                            col, attributes);
+                }
+            }
+        } else if ((this.parserState == ParserState.INPUT_DATA || this.parserState == ParserState.INPUT)
+                && "input".equals(elementName) && "changetotimem".equals(id)) {
+            this.parserState = ParserState.INPUT_DATA;
+            if (attributes != null) {
+                if ("number".equals(attributes.get("type"))) {
+                    this.optionType = OptionType.TIME;
+                    if (!"0".equals(attributes.get(ChangerX2Entry.NUMBER_MIN))
+                            || !"59".equals(attributes.get(ChangerX2Entry.NUMBER_MAX))) {
+                        logger.warn(
+                                "Error parsing options for {}: Unexpected MIN/MAX values for minute input field in {}:{}: {}",
+                                channelName, line, col, attributes);
+                    }
+                    ;
+                } else {
+                    logger.warn("Error parsing options for {}: Unhandled input field in {}:{}: {}", channelName, line,
+                            col, attributes);
+                }
+            }
         } else if (this.parserState == ParserState.SELECT && "option".equals(elementName)) {
             this.parserState = ParserState.SELECT_OPTION;
             this.optionType = OptionType.SELECT;
@@ -136,6 +175,8 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
             throws ParseException {
         if (this.parserState == ParserState.INPUT && "input".equals(elementName)) {
             this.parserState = ParserState.INIT;
+        } else if (this.parserState == ParserState.INPUT_DATA && "input".equals(elementName)) {
+            this.parserState = ParserState.INPUT;
         } else if (this.parserState == ParserState.SELECT && "select".equals(elementName)) {
             this.parserState = ParserState.INIT;
         } else if (this.parserState == ParserState.SELECT_OPTION && "option".equals(elementName)) {
@@ -159,6 +200,8 @@ public class ChangerX2Parser extends AbstractSimpleMarkupHandler {
                             channelName, line, col, value, prev, id);
                 }
             }
+        } else if (this.parserState == ParserState.INPUT && "span".equals(elementName)) {
+            // span's are ignored...
         } else {
             logger.debug("Error parsing options for {}: Unexpected CloseElement in {}:{}: {}", channelName, line, col,
                     elementName);
index 933d77aad3d51875b395b5762f7d2d6b9bf78b18..8146151150e110d72c00acc3934880a2a30b6c8c 100644 (file)
@@ -17,6 +17,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ScheduledFuture;
@@ -38,6 +39,7 @@ import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpMethod;
 import org.openhab.binding.tacmi.internal.TACmiChannelTypeProvider;
+import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.thing.Channel;
@@ -253,8 +255,18 @@ public class TACmiSchemaHandler extends BaseThingHandler {
             case NUMERIC_FORM:
                 ChangerX2Entry cx2en = e.changerX2Entry;
                 if (cx2en != null) {
-                    reqUpdate = prepareRequest(buildUri("INCLUDE/change.cgi?changeadrx2=" + cx2en.address
-                            + "&changetox2=" + command.format("%.2f")));
+                    String val;
+                    if (command instanceof Number qt) {
+                        val = String.format(Locale.US, "%.2f", qt.floatValue());
+                    } else if (command instanceof DateTimeType dtt) {
+                        // time is transferred as minutes since midnight...
+                        var zdt = dtt.getZonedDateTime();
+                        val = Integer.toString(zdt.getHour() * 60 + zdt.getMinute());
+                    } else {
+                        val = command.format("%.2f");
+                    }
+                    reqUpdate = prepareRequest(
+                            buildUri("INCLUDE/change.cgi?changeadrx2=" + cx2en.address + "&changetox2=" + val));
                     reqUpdate.header(HttpHeader.REFERER, this.serverBase + "schema.html"); // required...
                 } else {
                     logger.debug("Got command for uninitalized channel {}: {}", channelUID, command);