Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tacmi] Unit of Measurement fixes, added missing DateTime support #17481

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
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;
Expand All @@ -33,6 +37,7 @@
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;
Expand Down Expand Up @@ -196,7 +201,18 @@ public void handleCloseElement(final @Nullable String elementName, final int lin
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,
Expand Down Expand Up @@ -350,7 +366,7 @@ private void getApiPageEntry(@Nullable String id2, int line, int col, String sho
// 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";
Expand All @@ -374,15 +390,14 @@ private void getApiPageEntry(@Nullable String id2, int line, int col, String sho
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";
Expand All @@ -402,16 +417,27 @@ private void getApiPageEntry(@Nullable String id2, int line, int col, String sho
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;
Expand Down Expand Up @@ -440,7 +466,29 @@ private void getApiPageEntry(@Nullable String id2, int line, int col, String sho
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);
Expand All @@ -456,15 +504,6 @@ private void getApiPageEntry(@Nullable String id2, int line, int col, String sho
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);
Expand Down Expand Up @@ -543,8 +582,11 @@ private ChannelType buildAndRegisterChannelType(String shortName, Type type, Cha
}
}
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class ChangerX2Entry {
enum OptionType {
NUMBER,
SELECT,
TIME,
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void handleOpenElement(final String elementName, final Map<String, String
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));
Expand All @@ -120,6 +120,45 @@ public void handleOpenElement(final String elementName, final Map<String, String
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;
Expand All @@ -136,6 +175,8 @@ public void handleCloseElement(final @Nullable String elementName, final int lin
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)) {
Expand All @@ -159,6 +200,8 @@ public void handleCloseElement(final @Nullable String elementName, final int lin
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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;
Expand All @@ -38,6 +39,7 @@
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;
Expand Down Expand Up @@ -253,8 +255,18 @@ public void handleCommand(final ChannelUID channelUID, final Command command) {
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);
Expand Down