import org.openhab.binding.tacmi.internal.message.MessageType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
if (analog) {
final TACmiMeasureType measureType = TACmiMeasureType
.values()[((TACmiChannelConfigurationAnalog) channelConfig).type];
- final DecimalType dt = (DecimalType) command;
+ final Number dt = (Number) command;
final double val = dt.doubleValue() * measureType.getOffset();
modified = message.setValue(outputIdx, (short) val, measureType.ordinal());
} else {
if (message.getType() == MessageType.ANALOG) {
final AnalogValue value = ((AnalogMessage) message).getAnalogValue(output);
- updateState(channel.getUID(), new DecimalType(value.value));
+ State newState;
+ switch (value.measureType) {
+ case TEMPERATURE:
+ newState = new QuantityType<>(value.value, SIUnits.CELSIUS);
+ break;
+ case KILOWATT:
+ // TA uses kW, in OH we use W
+ newState = new QuantityType<>(value.value * 1000, Units.WATT);
+ break;
+ case KILOWATTHOURS:
+ newState = new QuantityType<>(value.value, Units.KILOWATT_HOUR);
+ break;
+ case MEGAWATTHOURS:
+ newState = new QuantityType<>(value.value, Units.MEGAWATT_HOUR);
+ break;
+ case SECONDS:
+ newState = new QuantityType<>(value.value, Units.SECOND);
+ break;
+ default:
+ newState = new DecimalType(value.value);
+ break;
+ }
+ updateState(channel.getUID(), newState);
} else {
final boolean state = ((DigitalMessage) message).getPortState(output);
updateState(channel.getUID(), state ? OnOffType.ON : OnOffType.OFF);
import java.math.BigDecimal;
import java.net.URI;
+import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
private Map<String, ApiPageEntry> entries;
private Set<String> seenNames = new HashSet<>();
private List<Channel> channels = new ArrayList<>();
+ // Time stamp when status request was started.
+ private final long statusRequestStartTS;
+ private static @Nullable URI configDescriptionUriAPISchemaDefaults;
public ApiPageParser(TACmiSchemaHandler taCmiSchemaHandler, Map<String, ApiPageEntry> entries,
TACmiChannelTypeProvider channelTypeProvider) {
this.taCmiSchemaHandler = taCmiSchemaHandler;
this.entries = entries;
this.channelTypeProvider = channelTypeProvider;
+ this.statusRequestStartTS = System.currentTimeMillis();
+ if (configDescriptionUriAPISchemaDefaults == null) {
+ try {
+ configDescriptionUriAPISchemaDefaults = new URI(
+ TACmiBindingConstants.CONFIG_DESCRIPTION_API_SCHEMA_DEFAULTS);
+ } catch (URISyntaxException ex) {
+ logger.warn("Can't create ConfigDescription URI '{}', ConfigDescription for channels not avilable!",
+ TACmiBindingConstants.CONFIG_DESCRIPTION_API_SCHEMA_DEFAULTS);
+ }
+ }
}
@Override
private void getApiPageEntry(@Nullable String id2, int line, int col, String shortName, String description,
Object value) {
- if (logger.isDebugEnabled()) {
- logger.debug("Found parameter {}:{}:{} [{}] : {} \"{}\" = {}", id, line, col, this.fieldType, shortName,
+ if (logger.isTraceEnabled()) {
+ logger.trace("Found parameter {}:{}:{} [{}] : {} \"{}\" = {}", id, line, col, this.fieldType, shortName,
description, value);
}
if (!this.seenNames.add(shortName)) {
return;
}
ApiPageEntry e = this.entries.get(shortName);
+ boolean isNewEntry;
if (e == null || e.type != type || !channelType.equals(e.channel.getAcceptedItemType())) {
@Nullable
Channel channel = this.taCmiSchemaHandler.getThing().getChannel(shortName);
logger.warn("Error loading API Scheme: {} ", ex.getMessage());
}
}
- if (channel == null) {
+ 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);
if (ctuid != null) {
channelBuilder.withType(ctuid);
} else if (cx2e != null) {
- StateDescriptionFragmentBuilder sdb = StateDescriptionFragmentBuilder.create()
- .withReadOnly(type.readOnly);
- String itemType;
- switch (cx2e.optionType) {
- case NUMBER:
- itemType = "Number";
- String min = cx2e.options.get(ChangerX2Entry.NUMBER_MIN);
- if (min != null && !min.trim().isEmpty()) {
- sdb.withMinimum(new BigDecimal(min));
- }
- String max = cx2e.options.get(ChangerX2Entry.NUMBER_MAX);
- if (max != null && !max.trim().isEmpty()) {
- sdb.withMaximum(new BigDecimal(max));
- }
- String step = cx2e.options.get(ChangerX2Entry.NUMBER_STEP);
- if (step != null && !step.trim().isEmpty()) {
- sdb.withStep(new BigDecimal(step));
- }
- break;
- case SELECT:
- itemType = "String";
- for (Entry<String, @Nullable String> entry : cx2e.options.entrySet()) {
- String val = entry.getValue();
- if (val != null) {
- sdb.withOption(new StateOption(val, entry.getKey()));
- }
- }
- break;
- default:
- throw new IllegalStateException();
- }
- ChannelType ct = ChannelTypeBuilder
- .state(new ChannelTypeUID(TACmiBindingConstants.BINDING_ID, shortName), shortName, itemType)
- .withDescription("Auto-created for " + shortName).withStateDescriptionFragment(sdb.build())
- .build();
- channelTypeProvider.addChannelType(ct);
+ ChannelType ct = buildAndRegisterChannelType(shortName, type, cx2e);
+
channelBuilder.withType(ct.getUID());
} else {
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);
this.entries.put(shortName, e);
+ isNewEntry = true;
+ } else {
+ isNewEntry = false;
}
this.channels.add(e.channel);
- e.setLastState(state);
- this.taCmiSchemaHandler.updateState(e.channel.getUID(), state);
+ // only update the state when there was no state change sent to C.M.I. after we started
+ // polling the state. It might deliver the previous / old state.
+ if (e.getLastCommandTS() < this.statusRequestStartTS) {
+ Number updatePolicyI = (Number) e.channel.getConfiguration().get("updatePolicy");
+ int updatePolicy = updatePolicyI == null ? 0 : updatePolicyI.intValue();
+ switch (updatePolicy) {
+ case 0: // 'default'
+ default:
+ // we do 'On-Fetch' update when channel is changeable, otherwise 'On-Change'
+ switch (e.type) {
+ case NUMERIC_FORM:
+ case STATE_FORM:
+ case SWITCH_BUTTON:
+ case SWITCH_FORM:
+ if (isNewEntry || !state.equals(e.getLastState())) {
+ e.setLastState(state);
+ this.taCmiSchemaHandler.updateState(e.channel.getUID(), state);
+ }
+ break;
+ case READ_ONLY_NUMERIC:
+ case READ_ONLY_STATE:
+ case READ_ONLY_SWITCH:
+ e.setLastState(state);
+ this.taCmiSchemaHandler.updateState(e.channel.getUID(), state);
+ break;
+ }
+ break;
+ case 1: // On-Fetch
+ e.setLastState(state);
+ this.taCmiSchemaHandler.updateState(e.channel.getUID(), state);
+ break;
+ case 2: // On-Change
+ if (isNewEntry || !state.equals(e.getLastState())) {
+ e.setLastState(state);
+ this.taCmiSchemaHandler.updateState(e.channel.getUID(), state);
+ }
+ break;
+ }
+ }
+ }
+
+ private ChannelType buildAndRegisterChannelType(String shortName, Type type, ChangerX2Entry cx2e) {
+ StateDescriptionFragmentBuilder sdb = StateDescriptionFragmentBuilder.create().withReadOnly(type.readOnly);
+ String itemType;
+ switch (cx2e.optionType) {
+ case NUMBER:
+ itemType = "Number";
+ String min = cx2e.options.get(ChangerX2Entry.NUMBER_MIN);
+ if (min != null && !min.trim().isEmpty()) {
+ sdb.withMinimum(new BigDecimal(min));
+ }
+ String max = cx2e.options.get(ChangerX2Entry.NUMBER_MAX);
+ if (max != null && !max.trim().isEmpty()) {
+ sdb.withMaximum(new BigDecimal(max));
+ }
+ String step = cx2e.options.get(ChangerX2Entry.NUMBER_STEP);
+ if (step != null && !step.trim().isEmpty()) {
+ sdb.withStep(new BigDecimal(step));
+ }
+ break;
+ case SELECT:
+ itemType = "String";
+ for (Entry<String, @Nullable String> entry : cx2e.options.entrySet()) {
+ String val = entry.getValue();
+ if (val != null) {
+ sdb.withOption(new StateOption(val, entry.getKey()));
+ }
+ }
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ ChannelTypeBuilder<?> ctb = ChannelTypeBuilder
+ .state(new ChannelTypeUID(TACmiBindingConstants.BINDING_ID, shortName), shortName, itemType)
+ .withDescription("Auto-created for " + shortName).withStateDescriptionFragment(sdb.build());
+
+ // add config description URI
+ URI cdu = configDescriptionUriAPISchemaDefaults;
+ if (cdu != null) {
+ ctb = ctb.withConfigDescriptionURI(cdu);
+ }
+
+ ChannelType ct = ctb.build();
+ channelTypeProvider.addChannelType(ct);
+ return ct;
}
protected boolean isConfigChanged() {