*/
package org.openhab.binding.lcn.internal;
+import java.util.regex.Pattern;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group");
/** Regex for address in PCK protocol */
public static final String ADDRESS_REGEX = "[:=%]M(?<segId>\\d{3})(?<modId>\\d{3})";
+ public static final Pattern MEASUREMENT_PATTERN_BEFORE_2013 = Pattern
+ .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.(?<value>\\d{5})");
/** LCN coding for ACK */
public static final int CODE_ACK = -1;
}
@NonNullByDefault
public class LcnModuleHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(LcnModuleHandler.class);
+ private static final int FIRMWARE_VERSION_LENGTH = 6;
private static final Map<String, Converter> VALUE_CONVERTERS = new HashMap<>();
private static final InversionConverter INVERSION_CONVERTER = new InversionConverter();
private @Nullable LcnAddrMod moduleAddress;
LcnAddrMod localModuleAddress = moduleAddress = new LcnAddrMod(localConfig.segmentId, localConfig.moduleId);
try {
- // Determine serial number of manually added modules
+ ModInfo info = getPckGatewayHandler().getModInfo(localModuleAddress);
+ readFirmwareVersionFromProperty().ifPresent(info::setFirmwareVersion);
requestFirmwareVersionAndSerialNumberIfNotSet();
// create sub handlers
- ModInfo info = getPckGatewayHandler().getModInfo(localModuleAddress);
for (LcnChannelGroup type : LcnChannelGroup.values()) {
subHandlers.put(type, type.createSubHandler(this, info));
}
* @throws LcnException when the handler is not initialized
*/
protected void requestFirmwareVersionAndSerialNumberIfNotSet() throws LcnException {
- String serialNumber = getThing().getProperties().get(Thing.PROPERTY_SERIAL_NUMBER);
- if (serialNumber == null || serialNumber.isEmpty()) {
+ if (readFirmwareVersionFromProperty().isEmpty()) {
LcnAddrMod localModuleAddress = moduleAddress;
if (localModuleAddress != null) {
getPckGatewayHandler().getModInfo(localModuleAddress).requestFirmwareVersion();
}
}
+ private Optional<Integer> readFirmwareVersionFromProperty() {
+ String prop = getThing().getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION);
+
+ if (prop == null || prop.length() != FIRMWARE_VERSION_LENGTH) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(Integer.parseInt(prop, 16));
+ } catch (NumberFormatException e) {
+ logger.warn("{}: Serial number property invalid", moduleAddress);
+ return Optional.empty();
+ }
+ }
+
@Override
public void handleCommand(ChannelUID channelUid, Command command) {
try {
* @param pck the message without line termination
*/
public void handleStatusMessage(String pck) {
- for (AbstractLcnModuleSubHandler handler : subHandlers.values()) {
- if (handler.tryParse(pck)) {
- break;
- }
- }
+ subHandlers.values().forEach(h -> h.tryParse(pck));
metadataSubHandlers.forEach(h -> h.tryParse(pck));
}
updateProperty(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
}
+ /**
+ * Updates the LCN module's serial number property.
+ *
+ * @param serialNumber the new serial number
+ */
+ public void updateFirmwareVersionProperty(String firmwareVersion) {
+ updateProperty(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
+ }
+
/**
* Invoked when an trigger for this LCN module should be fired to openHAB.
*
LcnAddrMod localModuleAddress = moduleAddress;
if (connection != null && localModuleAddress != null) {
getPckGatewayHandler().getModInfo(localModuleAddress).onAck(LcnBindingConstants.CODE_ACK, connection,
- getPckGatewayHandler().getTimeoutMs(), System.nanoTime());
+ getPckGatewayHandler().getTimeoutMs(), System.currentTimeMillis());
}
} catch (LcnException e) {
logger.warn("Connection or module address not set");
if (modData.containsKey(addr)) {
ModInfo modInfo = modData.get(addr);
if (modInfo != null) {
- modInfo.onAck(code, this, this.settings.getTimeout(), System.nanoTime());
+ modInfo.onAck(code, this, this.settings.getTimeout(), System.currentTimeMillis());
}
}
}
logger.warn("Data loss while writing to channel: {}", settings.getAddress());
} else {
if (logger.isTraceEnabled()) {
- logger.trace("Sent: {}", new String(data, 0, data.length));
+ logger.trace("Sent: {}",
+ new String(data, 0, data.length, LcnDefs.LCN_ENCODING).trim());
}
}
void queueDirectly(LcnAddr addr, boolean wantsAck, byte[] data) {
if (!addr.isGroup() && wantsAck) {
this.updateModuleData((LcnAddrMod) addr).queuePckCommandWithAck(data, this, this.settings.getTimeout(),
- System.nanoTime());
+ System.currentTimeMillis());
} else {
this.queueAndSend(new SendDataPck(addr, false, data));
}
*/
public void updateModInfos() {
synchronized (modData) {
- modData.values().forEach(i -> i.update(this, settings.getTimeout(), System.nanoTime()));
+ modData.values().forEach(i -> i.update(this, settings.getTimeout(), System.currentTimeMillis()));
}
}
}
private void update() {
- long currTime = System.nanoTime();
+ long currTime = System.currentTimeMillis();
try {
if (statusSegmentScan.shouldSendNextRequest(connection.getSettings().getTimeout(), currTime)) {
connection.queueDirectly(new LcnAddrGrp(3, 3), false, PckGenerator.segmentCouplerScan());
/** The LCN module's address. */
private final LcnAddr addr;
- /** Firmware date of the LCN module. -1 means "unknown". */
- private int firmwareVersion = -1;
+ /** Firmware date of the LCN module. */
+ private Optional<Integer> firmwareVersion = Optional.empty();
/** Firmware version request status. */
private final RequestStatus requestFirmwareVersion = new RequestStatus(-1, NUM_TRIES, "Firmware Version");
for (Variable var : Variable.values()) {
if (var != Variable.UNKNOWN) {
this.requestStatusVars.put(var, new RequestStatus(MAX_STATUS_POLLED_VALUEAGE_MSEC, NUM_TRIES,
- var.getType() + " " + (var.getNumber() + 1)));
+ addr + " " + var.getType() + " " + (var.getNumber() + 1)));
}
}
}
* Triggers a request to retrieve the firmware version of the LCN module, if it is not known, yet.
*/
public void requestFirmwareVersion() {
- if (firmwareVersion == -1) {
+ if (firmwareVersion.isEmpty()) {
requestFirmwareVersion.refresh();
}
}
* @return if the module has at least 4 threshold registers and 12 variables
*/
public boolean hasExtendedMeasurementProcessing() {
- if (firmwareVersion == -1) {
+ if (firmwareVersion.isEmpty()) {
logger.warn("LCN module firmware version unknown");
return false;
}
- return firmwareVersion >= LcnBindingConstants.FIRMWARE_2013;
+ return firmwareVersion.map(v -> v >= LcnBindingConstants.FIRMWARE_2013).orElse(false);
}
private boolean update(Connection conn, long timeoutMSec, long currTime, RequestStatus requestStatus, String pck)
if (update(conn, timeoutMSec, currTime, requestStatusRelays, PckGenerator.requestRelaysStatus())) {
return;
}
+
if (update(conn, timeoutMSec, currTime, requestStatusBinSensors, PckGenerator.requestBinSensorsStatus())) {
return;
}
+ if (update(conn, timeoutMSec, currTime, requestStatusLedsAndLogicOps,
+ PckGenerator.requestLedsAndLogicOpsStatus())) {
+ return;
+ }
+
+ if (update(conn, timeoutMSec, currTime, requestStatusLockedKeys, PckGenerator.requestKeyLocksStatus())) {
+ return;
+ }
+
// Variable requests
- if (this.firmwareVersion != -1) { // Firmware version is required
+ firmwareVersion.ifPresent(firmwareVersion -> { // Firmware version is required
// Use the chance to remove a failed "typeless variable" request
if (lastRequestedVarWithoutTypeInResponse != Variable.UNKNOWN) {
RequestStatus requestStatus = requestStatusVars.get(lastRequestedVarWithoutTypeInResponse);
// Variables
for (Map.Entry<Variable, RequestStatus> kv : this.requestStatusVars.entrySet()) {
RequestStatus requestStatus = kv.getValue();
- if (requestStatus.shouldSendNextRequest(timeoutMSec, currTime)) {
- // Detect if we can send immediately or if we have to wait for a "typeless" request first
- boolean hasTypeInResponse = kv.getKey().hasTypeInResponse(this.firmwareVersion);
- if (hasTypeInResponse || this.lastRequestedVarWithoutTypeInResponse == Variable.UNKNOWN) {
- try {
- conn.queue(this.addr, false,
- PckGenerator.requestVarStatus(kv.getKey(), this.firmwareVersion));
- requestStatus.onRequestSent(currTime);
- if (!hasTypeInResponse) {
- this.lastRequestedVarWithoutTypeInResponse = kv.getKey();
+ try {
+ if (requestStatus.shouldSendNextRequest(timeoutMSec, currTime)) {
+ // Detect if we can send immediately or if we have to wait for a "typeless" request first
+ boolean hasTypeInResponse = kv.getKey().hasTypeInResponse(firmwareVersion);
+ if (hasTypeInResponse || this.lastRequestedVarWithoutTypeInResponse == Variable.UNKNOWN) {
+ try {
+ conn.queue(this.addr, false,
+ PckGenerator.requestVarStatus(kv.getKey(), firmwareVersion));
+ requestStatus.onRequestSent(currTime);
+ if (!hasTypeInResponse) {
+ this.lastRequestedVarWithoutTypeInResponse = kv.getKey();
+ }
+ return;
+ } catch (LcnException ex) {
+ logger.warn("{}: Failed to generate PCK message: {}: {}", addr, kv.getKey(),
+ ex.getMessage());
+ requestStatus.reset();
+ lastRequestedVarWithoutTypeInResponse = Variable.UNKNOWN;
}
- return;
- } catch (LcnException ex) {
- requestStatus.reset();
}
}
+ } catch (LcnException e) {
+ logger.warn("{}: Failed to receive measurement value: {}", addr, e.getMessage());
}
}
- }
-
- if (update(conn, timeoutMSec, currTime, requestStatusLedsAndLogicOps,
- PckGenerator.requestLedsAndLogicOpsStatus())) {
- return;
- }
-
- if (update(conn, timeoutMSec, currTime, requestStatusLockedKeys, PckGenerator.requestKeyLocksStatus())) {
- return;
- }
+ });
// Try to send next acknowledged command. Will also detect failed ones.
this.tryProcessNextCommandWithAck(conn, timeoutMSec, currTime);
*
* @return the date
*/
- public int getFirmwareVersion() {
- return this.firmwareVersion;
+ public Optional<Integer> getFirmwareVersion() {
+ return firmwareVersion;
}
/**
* @param firmwareVersion the date
*/
public void setFirmwareVersion(int firmwareVersion) {
- this.firmwareVersion = firmwareVersion;
+ this.firmwareVersion = Optional.of(firmwareVersion);
requestFirmwareVersion.onResponseReceived();
* Requests the current value of all LEDs and logic operations, after a LED has been changed by openHAB.
*/
public void refreshStatusLedsAnLogicAfterChange() {
- requestStatusLedsAndLogicOps.nextRequestIn(STATUS_REQUEST_DELAY_AFTER_COMMAND_MSEC, System.nanoTime());
+ requestStatusLedsAndLogicOps.nextRequestIn(STATUS_REQUEST_DELAY_AFTER_COMMAND_MSEC, System.currentTimeMillis());
}
/**
* Requests the current locking states of all keys, after a lock state has been changed by openHAB.
*/
public void refreshStatusStatusLockedKeysAfterChange() {
- requestStatusLockedKeys.nextRequestIn(STATUS_REQUEST_DELAY_AFTER_COMMAND_MSEC, System.nanoTime());
+ requestStatusLockedKeys.nextRequestIn(STATUS_REQUEST_DELAY_AFTER_COMMAND_MSEC, System.currentTimeMillis());
}
/**
if (requestStatus != null) {
requestStatus.onResponseReceived();
}
+
+ if (variable == lastRequestedVarWithoutTypeInResponse) {
+ lastRequestedVarWithoutTypeInResponse = Variable.UNKNOWN; // Reset
+ }
}
/**
public void onLockedKeysResponseReceived() {
requestStatusLockedKeys.onResponseReceived();
}
+
+ /**
+ * Returns the module's bus address.
+ */
+ public LcnAddr getAddress() {
+ return addr;
+ }
}
* @return true if request timed out
*/
synchronized boolean isTimeout(long timeoutMSec, long currTime) {
- return this.isPending() && currTime - this.currRequestTimeStamp >= timeoutMSec * 1000000L;
+ return this.isPending() && currTime - this.currRequestTimeStamp >= timeoutMSec;
}
/**
*/
public synchronized void nextRequestIn(long delayMSec, long currTime) {
this.isActive = true;
- this.nextRequestTimeStamp = currTime + delayMSec * 1000000L;
+ this.nextRequestTimeStamp = currTime + delayMSec;
}
/**
* Schedules a request to retrieve the current value.
*/
public synchronized void refresh() {
- nextRequestIn(0, System.nanoTime());
+ nextRequestIn(0, System.currentTimeMillis());
this.numRetriesLeft = this.numTries;
}
public synchronized void onResponseReceived() {
if (this.isActive) {
this.currRequestTimeStamp = 0; // Mark request (if any) as successful
+
+ // Reset timer for next transmission
+ if (this.maxAgeMSec != -1) {
+ this.nextRequestIn(this.maxAgeMSec, System.currentTimeMillis());
+ }
}
}
* @param pck the message to process
* @return true, if the message could be processed successfully
*/
- public boolean tryParse(String pck) {
+ public void tryParse(String pck) {
Optional<Matcher> firstSuccessfulMatcher = getPckStatusMessagePatterns().stream().map(p -> p.matcher(pck))
.filter(Matcher::matches).filter(m -> handler.isMyAddress(m.group("segId"), m.group("modId")))
.findAny();
logger.warn("Parse error: {}", e.getMessage());
}
});
-
- return firstSuccessfulMatcher.isPresent();
}
/**
@Override
public void handleRefresh(LcnChannelGroup channelGroup, int number) {
requestVariable(info, channelGroup, number);
- info.requestFirmwareVersion();
}
/**
public void handleStatusMessage(Matcher matcher) {
info.setFirmwareVersion(Integer.parseInt(matcher.group("firmwareVersion"), 16));
handler.updateSerialNumberProperty(matcher.group("sn"));
+ handler.updateFirmwareVersionProperty(matcher.group("firmwareVersion"));
}
@Override
currentColor = hsbType;
handler.updateChannel(LcnChannelGroup.OUTPUT, OUTPUT_COLOR, currentColor);
- if (info.getFirmwareVersion() >= LcnBindingConstants.FIRMWARE_2014) {
+ if (info.getFirmwareVersion().map(v -> v >= LcnBindingConstants.FIRMWARE_2014).orElse(true)) {
handler.sendPck(PckGenerator.dimAllOutputs(currentColor.getRed().doubleValue(),
currentColor.getGreen().doubleValue(), currentColor.getBlue().doubleValue(), output4.doubleValue(),
COLOR_RAMP_MS));
// request new lock state, if the module doesn't send it on itself
Variable variable = getVariable(LcnChannelGroup.RVARSETPOINT, number);
- if (variable.shouldPollStatusAfterRegulatorLock(info.getFirmwareVersion(), locked)) {
+ if (info.getFirmwareVersion().map(v -> variable.shouldPollStatusAfterRegulatorLock(v, locked)).orElse(true)) {
info.refreshVariable(variable);
}
}
package org.openhab.binding.lcn.internal.subhandler;
import java.util.Collection;
-import java.util.Collections;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.lcn.internal.common.LcnException;
import org.openhab.binding.lcn.internal.common.PckGenerator;
import org.openhab.binding.lcn.internal.common.Variable;
+import org.openhab.binding.lcn.internal.common.Variable.Type;
import org.openhab.binding.lcn.internal.common.VariableValue;
import org.openhab.binding.lcn.internal.connection.ModInfo;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Handles Commands and State changes of regulator setpoints of an LCN module.
*/
@NonNullByDefault
public class LcnModuleRvarSetpointSubHandler extends AbstractLcnModuleVariableSubHandler {
+ private final Logger logger = LoggerFactory.getLogger(LcnModuleRvarSetpointSubHandler.class);
private static final Pattern PATTERN = Pattern
- .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.S(?<id>\\d)(?<value>\\d+)");
+ .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.S(?<id>\\d)(?<value>\\d{1,5})");
public LcnModuleRvarSetpointSubHandler(LcnModuleHandler handler, ModInfo info) {
super(handler, info);
@Override
public void handleStatusMessage(Matcher matcher) throws LcnException {
- Variable variable = Variable.setPointIdToVar(Integer.parseInt(matcher.group("id")) - 1);
+ Variable variable;
+ if (matcher.pattern() == PATTERN) {
+ variable = Variable.setPointIdToVar(Integer.parseInt(matcher.group("id")) - 1);
+ } else if (matcher.pattern() == LcnBindingConstants.MEASUREMENT_PATTERN_BEFORE_2013) {
+ variable = info.getLastRequestedVarWithoutTypeInResponse();
+
+ if (variable.getType() != Type.REGULATOR) {
+ return;
+ }
+ } else {
+ logger.warn("Unexpected pattern: {}", matcher.pattern());
+ return;
+ }
VariableValue value = fireUpdateAndReset(matcher, "", variable);
fireUpdate(LcnChannelGroup.RVARLOCK, variable.getNumber(), OnOffType.from(value.isRegulatorLocked()));
@Override
public Collection<Pattern> getPckStatusMessagePatterns() {
- return Collections.singleton(PATTERN);
+ return List.of(PATTERN, LcnBindingConstants.MEASUREMENT_PATTERN_BEFORE_2013);
}
}
info.hasExtendedMeasurementProcessing()));
// request new value, if the module doesn't send it on itself
- if (variable.shouldPollStatusAfterCommand(info.getFirmwareVersion())) {
+ if (info.getFirmwareVersion().map(v -> variable.shouldPollStatusAfterCommand(v)).orElse(true)) {
info.refreshVariable(variable);
}
} catch (LcnException e) {
*/
package org.openhab.binding.lcn.internal.subhandler;
-import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.lcn.internal.common.LcnException;
import org.openhab.binding.lcn.internal.common.PckGenerator;
import org.openhab.binding.lcn.internal.common.Variable;
+import org.openhab.binding.lcn.internal.common.Variable.Type;
import org.openhab.binding.lcn.internal.connection.ModInfo;
import org.openhab.core.library.types.DecimalType;
import org.slf4j.Logger;
private final Logger logger = LoggerFactory.getLogger(LcnModuleVariableSubHandler.class);
private static final Pattern PATTERN = Pattern
.compile(LcnBindingConstants.ADDRESS_REGEX + "\\.A(?<id>\\d{3})(?<value>\\d+)");
- private static final Pattern PATTERN_LEGACY = Pattern
- .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.(?<value>\\d+)");
public LcnModuleVariableSubHandler(LcnModuleHandler handler, ModInfo info) {
super(handler, info);
handler.sendPck(PckGenerator.setVariableRelative(variable, LcnDefs.RelVarRef.CURRENT, relativeChange));
// request new value, if the module doesn't send it on itself
- if (variable.shouldPollStatusAfterCommand(info.getFirmwareVersion())) {
+ if (info.getFirmwareVersion().map(v -> variable.shouldPollStatusAfterCommand(v)).orElse(true)) {
info.refreshVariable(variable);
}
} catch (LcnException e) {
Variable variable;
if (matcher.pattern() == PATTERN) {
variable = Variable.varIdToVar(Integer.parseInt(matcher.group("id")) - 1);
- } else if (matcher.pattern() == PATTERN_LEGACY) {
+ } else if (matcher.pattern() == LcnBindingConstants.MEASUREMENT_PATTERN_BEFORE_2013) {
variable = info.getLastRequestedVarWithoutTypeInResponse();
- info.setLastRequestedVarWithoutTypeInResponse(Variable.UNKNOWN); // Reset
+
+ if (variable == Variable.UNKNOWN || variable.getType() != Type.VARIABLE) {
+ return;
+ }
} else {
logger.warn("Unexpected pattern: {}", matcher.pattern());
return;
@Override
public Collection<Pattern> getPckStatusMessagePatterns() {
- return Arrays.asList(PATTERN, PATTERN_LEGACY);
+ return List.of(PATTERN, LcnBindingConstants.MEASUREMENT_PATTERN_BEFORE_2013);
}
}