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 <[email protected]>
Signed-off-by: Hilbrand Bouwkamp <[email protected]>
  • Loading branch information
Hilbrand committed Oct 1, 2021
1 parent ea184c6 commit 38f6eac
Show file tree
Hide file tree
Showing 15 changed files with 226 additions and 77 deletions.
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 38f6eac

Please sign in to comment.