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;
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;
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,
// 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";
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";
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;
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);
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);
}
}
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)
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));
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;
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)) {
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);
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;
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;
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);