Skip to content

Commit

Permalink
[dsmr] Add support for Austrian meters
Browse files Browse the repository at this point in the history
Improved the work done in pr openhab#11193

Also-by: Thomas <thomas@knaller.info>
Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
  • Loading branch information
Hilbrand committed Oct 27, 2021
1 parent 37a4578 commit fa07d21
Show file tree
Hide file tree
Showing 16 changed files with 371 additions and 214 deletions.
283 changes: 146 additions & 137 deletions bundles/org.openhab.binding.dsmr/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;

Expand Down Expand Up @@ -74,7 +73,7 @@ public enum CosemObjectType {
EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 17, 0, 0), CosemQuantity.AMPERE),
EMETER_TRESHOLD_A(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.AMPERE),
EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 31, 4, 0), CosemQuantity.AMPERE),
EMETER_TRESHOLD_KWH(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.KILO_WATT),
EMETER_TRESHOLD_KW(new OBISIdentifier(0, 17, 0, 0, true), CosemQuantity.KILO_WATT),
EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 96, 3, 10), CosemDecimal.INSTANCE),
EMETER_SWITCH_POSITION(new OBISIdentifier(0, 96, 3, 10), CosemDecimal.INSTANCE),
EMETER_POWER_FAILURES(new OBISIdentifier(0, 96, 7, 21), CosemDecimal.INSTANCE),
Expand Down Expand Up @@ -142,7 +141,11 @@ public enum CosemObjectType {

/* Additional Luxembourgish Smarty Electricity */
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 3, 8, 0), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 3, 8, 1), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 3, 8, 2), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, 4, 8, 0), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE1(new OBISIdentifier(1, 4, 8, 1), CosemQuantity.KILO_VAR_HOUR),
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_R_RATE2(new OBISIdentifier(1, 4, 8, 2), CosemQuantity.KILO_VAR_HOUR),
// The actual reactive's and threshold have no unit in the data and therefore are not quantity types.
EMETER_ACTUAL_REACTIVE_DELIVERY(new OBISIdentifier(1, 3, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS),
EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 4, 7, 0), CosemDecimal.INSTANCE_WITH_UNITS),
Expand Down Expand Up @@ -183,9 +186,9 @@ public enum CosemObjectType {
this.obisId = obisId;
if (nrOfRepeatingDescriptors == 0) {
this.descriptors = Arrays.asList(descriptors);
this.repeatingDescriptors = Collections.emptyList();
this.repeatingDescriptors = List.of();
} else {
List<CosemValueDescriptor<?>> allDescriptors = Arrays.asList(descriptors);
final List<CosemValueDescriptor<?>> allDescriptors = List.of(descriptors);

/*
* The last nrOfRepeatingDescriptors CosemValueDescriptor will go into the repeatingDescriptor list.
Expand All @@ -211,16 +214,16 @@ public enum CosemObjectType {
public @Nullable Entry<String, CosemValueDescriptor<?>> getDescriptor(int idx) {
if (idx >= descriptors.size() && !repeatingDescriptors.isEmpty()) {
/* We have a repeating list, find the correct repeating descriptor */
int repeatingIdx = (idx - descriptors.size()) % repeatingDescriptors.size();
final int repeatingIdx = (idx - descriptors.size()) % repeatingDescriptors.size();

CosemValueDescriptor<?> descriptor = repeatingDescriptors.get(repeatingIdx);
final CosemValueDescriptor<?> descriptor = repeatingDescriptors.get(repeatingIdx);

/* The repeating descriptor must have a specific channel */
int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size();
final int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size();

return new SimpleEntry<>(descriptor.getChannelId() + repeatCount, descriptor);
} else if (idx < descriptors.size()) {
CosemValueDescriptor<?> descriptor = descriptors.get(idx);
final CosemValueDescriptor<?> descriptor = descriptors.get(idx);

return new SimpleEntry<>(descriptor.getChannelId(), descriptor);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class CosemQuantity<Q extends @Nullable Quantity<Q>> extends CosemValueDescripto
public static final CosemQuantity<Power> WATT = new CosemQuantity<>(Units.WATT);
public static final CosemQuantity<Power> KILO_VAR = new CosemQuantity<>(Units.KILOVAR);
public static final CosemQuantity<Energy> KILO_VAR_HOUR = new CosemQuantity<>(Units.KILOVAR_HOUR);
public static final CosemQuantity<Power> KILO_VA = new CosemQuantity<>(MetricPrefix.KILO(Units.VOLT_AMPERE));

/**
* Pattern to convert a cosem value to a value that can be parsed by {@link QuantityType}.
Expand Down Expand Up @@ -100,13 +101,14 @@ public CosemQuantity(Unit<Q> unit, String channelId) {
@Override
protected QuantityType<Q> getStateValue(String cosemValue) throws ParseException {
try {
QuantityType<Q> qt = new QuantityType<>(prepare(cosemValue));
final QuantityType<Q> it = new QuantityType<>(prepare(cosemValue));
final @Nullable QuantityType<Q> qt = it.toUnit(unit);

if (!unit.equals(qt.getUnit())) {
if (qt == null) {
throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0);
}
return qt;
} catch (IllegalArgumentException nfe) {
} catch (final IllegalArgumentException nfe) {
throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0);
}
}
Expand All @@ -123,15 +125,15 @@ protected QuantityType<Q> getStateValue(String cosemValue) throws ParseException
* We also support unit that do not follow the exact case.
*/
private String prepare(String cosemValue) {
Matcher matcher = COSEM_VALUE_WITH_UNIT_PATTERN.matcher(cosemValue.replace("m3", "m³"));
final Matcher matcher = COSEM_VALUE_WITH_UNIT_PATTERN.matcher(cosemValue.replace("m3", "m³"));
if (!matcher.find()) {
return cosemValue;
}

try {
Integer.parseInt(matcher.group(2));
return cosemValue;
} catch (NumberFormatException e) {
} catch (final NumberFormatException e) {
return matcher.group(1) + ' ' + matcher.group(2);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,23 @@ private enum State {
*/
private final P1TelegramListener telegramListener;

/**
* Enable in tests. Will throw an exception on CRC error.
*/
private final boolean test;

/**
* Creates a new P1TelegramParser
*
* @param telegramListener
*/
public P1TelegramParser(P1TelegramListener telegramListener) {
this(telegramListener, false);
}

public P1TelegramParser(P1TelegramListener telegramListener, boolean test) {
this.telegramListener = telegramListener;
this.test = test;

factory = new CosemObjectFactory();
state = State.WAIT_FOR_START;
Expand All @@ -151,7 +161,7 @@ public P1TelegramParser(P1TelegramListener telegramListener) {
@Override
public void parse(byte[] data, int length) {
if (lenientMode || logger.isTraceEnabled()) {
String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8);
final String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8);

if (lenientMode) {
rawData.append(rawBlock);
Expand All @@ -161,7 +171,7 @@ public void parse(byte[] data, int length) {
}
}
for (int i = 0; i < length; i++) {
char c = (char) data[i];
final char c = (char) data[i];

switch (state) {
case WAIT_FOR_START:
Expand Down Expand Up @@ -245,22 +255,7 @@ public void parse(byte[] data, int length) {
logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue);
// Only perform CRC check if telegram is still ok
if (telegramState == TelegramState.OK && crcValue.length() > 0) {
if (Pattern.matches(CRC_PATTERN, crcValue)) {
int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16);
int calculatedCRC = crc.getCurrentCRCCode();

if (logger.isDebugEnabled()) {
logger.trace("received CRC value: {}, calculated CRC value: 0x{}", crcValue,
String.format("%04X", calculatedCRC));
}
if (crcP1Telegram != calculatedCRC) {
logger.trace("CRC value does not match, p1 Telegram failed");

telegramState = TelegramState.CRC_ERROR;
}
} else {
telegramState = TelegramState.CRC_ERROR;
}
telegramState = checkCRC(telegramState);
}
telegramListener.telegramReceived(constructTelegram());
reset();
Expand All @@ -280,6 +275,34 @@ public void parse(byte[] data, int length) {
logger.trace("State after parsing: {}", state);
}

private TelegramState checkCRC(TelegramState currentState) {
final TelegramState telegramState;

if (Pattern.matches(CRC_PATTERN, crcValue)) {
final int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16);
final int calculatedCRC = crc.getCurrentCRCCode();

if (logger.isDebugEnabled()) {
logger.trace("received CRC value: {}, calculated CRC value: 0x{}", crcValue,
String.format("%04X", calculatedCRC));
}
if (crcP1Telegram != calculatedCRC) {
if (test) {
throw new IllegalArgumentException(
String.format("Invalid CRC. Read: %s, expected: %04X", crcValue, calculatedCRC));
}
logger.trace("CRC value does not match, p1 Telegram failed");

telegramState = TelegramState.CRC_ERROR;
} else {
telegramState = currentState;
}
} else {
telegramState = TelegramState.CRC_ERROR;
}
return telegramState;
}

private P1Telegram constructTelegram() {
final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);

Expand Down Expand Up @@ -375,11 +398,11 @@ private void clearObisData() {
* Store the current CosemObject in the list of received cosem Objects
*/
private void storeCurrentCosemObject() {
String obisIdString = obisId.toString();
final String obisIdString = obisId.toString();

if (!obisIdString.isEmpty()) {
final String obisValueString = obisValue.toString();
CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);

if (cosemObject == null) {
if (lenientMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public boolean unregisterDSMRMeterListener(P1TelegramListener meterListener) {
*/
private void alive() {
logger.trace("Bridge alive check with #{} children.", getThing().getThings().size());
long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
final long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;

if (deltaLastReceived > receivedTimeoutNanos) {
logger.debug("No data received for {} seconds, restarting port if possible.",
Expand Down Expand Up @@ -271,13 +271,15 @@ public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
* @param telegram received meter values.
*/
private void meterValueReceived(P1Telegram telegram) {
updateStatus(ThingStatus.ONLINE);
if (isInitialized() && getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
getThing().getThings().forEach(child -> {
if (logger.isTraceEnabled()) {
logger.trace("Update child:{} with {} objects", child.getThingTypeUID().getId(),
telegram.getCosemObjects().size());
}
DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler();
final DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler();

if (dsmrMeterHandler instanceof DSMRMeterHandler) {
dsmrMeterHandler.telegramReceived(telegram);
Expand Down
Loading

0 comments on commit fa07d21

Please sign in to comment.