[dsmr] Fix handling problem reading gasmeter (#9736)
* [dsmr] Fix handling problem reading gasmeter The gasmeter seem to return an invalid date value in some cases. Possible problems with the meter. This however caused the whole meter readings to be ignored, while it should only ignore the single problem value. Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl> * [dsmr] Make eclipse happy again. Don't ask... Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl> * [dsmr] Removed sat warnings Removed usage of apache library and added NonNullByDefault to test classes.. Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
This commit is contained in:
parent
3557094c13
commit
3f0c0ebf1f
@ -12,8 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.dsmr.internal.device;
|
package org.openhab.binding.dsmr.internal.device;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class describing the DSMR bridge user configuration
|
* Class describing the DSMR bridge user configuration
|
||||||
*
|
*
|
||||||
@ -60,7 +58,8 @@ public class DSMRDeviceConfiguration {
|
|||||||
* @return true if serial port settings are all set.
|
* @return true if serial port settings are all set.
|
||||||
*/
|
*/
|
||||||
public boolean isSerialFixedSettings() {
|
public boolean isSerialFixedSettings() {
|
||||||
return baudrate > 0 && databits > 0 && !StringUtils.isBlank(parity) && !StringUtils.isBlank(stopbits);
|
return baudrate > 0 && databits > 0 && !(parity != null && parity.isBlank())
|
||||||
|
&& !(stopbits != null && stopbits.isBlank());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -35,6 +36,10 @@ import org.slf4j.LoggerFactory;
|
|||||||
class CosemDate extends CosemValueDescriptor<DateTimeType> {
|
class CosemDate extends CosemValueDescriptor<DateTimeType> {
|
||||||
|
|
||||||
public static final CosemDate INSTANCE = new CosemDate("timestamp");
|
public static final CosemDate INSTANCE = new CosemDate("timestamp");
|
||||||
|
/*
|
||||||
|
* Some meters can return the following value when something is wrong.
|
||||||
|
*/
|
||||||
|
public static final String INVALID_METER_VALUE = "632525252525W";
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(CosemDate.class);
|
private final Logger logger = LoggerFactory.getLogger(CosemDate.class);
|
||||||
|
|
||||||
@ -102,8 +107,16 @@ class CosemDate extends CosemValueDescriptor<DateTimeType> {
|
|||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
logger.trace("{} matches pattern: {}", cosemValue, cosemDateFormat.pattern);
|
logger.trace("{} matches pattern: {}", cosemValue, cosemDateFormat.pattern);
|
||||||
|
|
||||||
|
try {
|
||||||
LocalDateTime localDateTime = LocalDateTime.parse(m.group(1), cosemDateFormat.formatter);
|
LocalDateTime localDateTime = LocalDateTime.parse(m.group(1), cosemDateFormat.formatter);
|
||||||
return new DateTimeType(ZonedDateTime.of(localDateTime, ZoneId.systemDefault()));
|
return new DateTimeType(ZonedDateTime.of(localDateTime, ZoneId.systemDefault()));
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
if (INVALID_METER_VALUE.equals(cosemValue)) {
|
||||||
|
throw new ParseException(
|
||||||
|
"Cosem value: '" + cosemValue + "' might indicate something is wrong with the meter.",
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ParseException("Cosem value: '" + cosemValue + "' is not a known CosemDate string", 0);
|
throw new ParseException("Cosem value: '" + cosemValue + "' is not a known CosemDate string", 0);
|
||||||
|
|||||||
@ -12,8 +12,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.dsmr.internal.discovery;
|
package org.openhab.binding.dsmr.internal.discovery;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
@ -143,10 +147,9 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
|
|||||||
.map(Thing::getHandler)
|
.map(Thing::getHandler)
|
||||||
.filter(DSMRMeterHandler.class::isInstance)
|
.filter(DSMRMeterHandler.class::isInstance)
|
||||||
.map(DSMRMeterHandler.class::cast)
|
.map(DSMRMeterHandler.class::cast)
|
||||||
.map(h -> h == null ? null : h.getMeterDescriptor())
|
.map(DSMRMeterHandler::getMeterDescriptor)
|
||||||
.map(d -> Optional.ofNullable(d == null ? null : d.getMeterType()))
|
.filter(Objects::nonNull)
|
||||||
.filter(Optional::isPresent)
|
.map(d -> d.getMeterType())
|
||||||
.map(Optional::get)
|
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
// Create list of all configured meters that are not in the detected list. If not empty meters might not be
|
// Create list of all configured meters that are not in the detected list. If not empty meters might not be
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
||||||
@ -28,6 +28,7 @@ import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
|||||||
*
|
*
|
||||||
* @author Hilbrand Bouwkamp - Initial contribution
|
* @author Hilbrand Bouwkamp - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public final class TelegramReaderUtil {
|
public final class TelegramReaderUtil {
|
||||||
private static final String TELEGRAM_EXT = ".telegram";
|
private static final String TELEGRAM_EXT = ".telegram";
|
||||||
|
|
||||||
@ -43,7 +44,10 @@ public final class TelegramReaderUtil {
|
|||||||
*/
|
*/
|
||||||
public static byte[] readRawTelegram(String telegramName) {
|
public static byte[] readRawTelegram(String telegramName) {
|
||||||
try (InputStream is = TelegramReaderUtil.class.getResourceAsStream(telegramName + TELEGRAM_EXT)) {
|
try (InputStream is = TelegramReaderUtil.class.getResourceAsStream(telegramName + TELEGRAM_EXT)) {
|
||||||
return IOUtils.toByteArray(is);
|
if (is == null) {
|
||||||
|
fail("Could not find telegram file with name:" + telegramName + TELEGRAM_EXT);
|
||||||
|
}
|
||||||
|
return is.readAllBytes();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AssertionError("IOException reading telegram data: ", e);
|
throw new AssertionError("IOException reading telegram data: ", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,8 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@ -47,18 +49,19 @@ import org.openhab.core.io.transport.serial.SerialPortManager;
|
|||||||
* @author Hilbrand Bouwkamp - Initial contribution
|
* @author Hilbrand Bouwkamp - Initial contribution
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@NonNullByDefault
|
||||||
public class DSMRSerialAutoDeviceTest {
|
public class DSMRSerialAutoDeviceTest {
|
||||||
|
|
||||||
private static final String DUMMY_PORTNAME = "/dev/dummy-serial";
|
private static final String DUMMY_PORTNAME = "/dev/dummy-serial";
|
||||||
private static final String TELEGRAM_NAME = "dsmr_50";
|
private static final String TELEGRAM_NAME = "dsmr_50";
|
||||||
|
|
||||||
private @Mock SerialPortIdentifier mockIdentifier;
|
private @NonNullByDefault({}) @Mock SerialPortIdentifier mockIdentifier;
|
||||||
private @Mock ScheduledExecutorService scheduler;
|
private @NonNullByDefault({}) @Mock ScheduledExecutorService scheduler;
|
||||||
private @Mock SerialPort mockSerialPort;
|
private @NonNullByDefault({}) @Mock SerialPort mockSerialPort;
|
||||||
|
|
||||||
private SerialPortManager serialPortManager = new SerialPortManager() {
|
private final SerialPortManager serialPortManager = new SerialPortManager() {
|
||||||
@Override
|
@Override
|
||||||
public SerialPortIdentifier getIdentifier(String name) {
|
public @Nullable SerialPortIdentifier getIdentifier(String name) {
|
||||||
assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
|
assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
|
||||||
return mockIdentifier;
|
return mockIdentifier;
|
||||||
}
|
}
|
||||||
@ -68,7 +71,7 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private SerialPortEventListener serialPortEventListener;
|
private @NonNullByDefault({}) SerialPortEventListener serialPortEventListener;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() throws PortInUseException, TooManyListenersException {
|
public void setUp() throws PortInUseException, TooManyListenersException {
|
||||||
@ -81,7 +84,7 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testHandlingDataAndRestart() throws IOException, PortInUseException {
|
public void testHandlingDataAndRestart() throws IOException, PortInUseException {
|
||||||
mockValidSerialPort();
|
mockValidSerialPort();
|
||||||
AtomicReference<P1Telegram> telegramRef = new AtomicReference<>(null);
|
AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
|
||||||
DSMREventListener listener = new DSMREventListener() {
|
DSMREventListener listener = new DSMREventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void handleTelegramReceived(P1Telegram telegram) {
|
public void handleTelegramReceived(P1Telegram telegram) {
|
||||||
@ -114,7 +117,7 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHandleError() throws IOException, PortInUseException {
|
public void testHandleError() throws IOException, PortInUseException {
|
||||||
AtomicReference<DSMRConnectorErrorEvent> eventRef = new AtomicReference<>(null);
|
AtomicReference<@Nullable DSMRConnectorErrorEvent> eventRef = new AtomicReference<>(null);
|
||||||
DSMREventListener listener = new DSMREventListener() {
|
DSMREventListener listener = new DSMREventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void handleTelegramReceived(P1Telegram telegram) {
|
public void handleTelegramReceived(P1Telegram telegram) {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||||
@ -27,31 +28,34 @@ import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramSt
|
|||||||
*
|
*
|
||||||
* @author Hilbrand Bouwkamp - Initial contribution
|
* @author Hilbrand Bouwkamp - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class P1TelegramParserTest {
|
public class P1TelegramParserTest {
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
public static final List<Object[]> data() {
|
public static final List<Object[]> data() {
|
||||||
return Arrays.asList(new Object[][] {
|
return Arrays.asList(new Object[][] {
|
||||||
{ "ace4000", 59, },
|
{ "ace4000", 59, 0},
|
||||||
{ "dsmr_40", 39, },
|
{ "dsmr_40", 39, 0},
|
||||||
{ "dsmr_42", 39, },
|
{ "dsmr_42", 39, 0},
|
||||||
{ "dsmr_50", 41, },
|
{ "dsmr_50", 41, 0},
|
||||||
{ "flu5", 21, },
|
{ "flu5_invalid_gasmeter", 19, 1},
|
||||||
{ "Iskra_AM550", 41, },
|
{ "flu5", 21, 0},
|
||||||
{ "Landis_Gyr_E350", 10, },
|
{ "Iskra_AM550", 41, 0},
|
||||||
{ "Landis_Gyr_ZCF110", 25, },
|
{ "Landis_Gyr_E350", 10, 0},
|
||||||
{ "Sagemcom_XS210", 41, },
|
{ "Landis_Gyr_ZCF110", 25, 0},
|
||||||
{ "smarty", 28, },
|
{ "Sagemcom_XS210", 41, 0},
|
||||||
{ "smarty_with_units", 23, },
|
{ "smarty", 28, 0},
|
||||||
|
{ "smarty_with_units", 23, 0},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
public void testParsing(final String telegramName, final int numberOfCosemObjects) {
|
public void testParsing(final String telegramName, final int numberOfCosemObjects, final int unknownObjects) {
|
||||||
P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
|
P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
|
||||||
assertEquals(0, telegram.getUnknownCosemObjects().size(), "Should not have any unknown cosem objects");
|
assertEquals(unknownObjects, telegram.getUnknownCosemObjects().size(),
|
||||||
|
"Should not have other than " + unknownObjects + " unknown cosem objects");
|
||||||
assertEquals(numberOfCosemObjects,
|
assertEquals(numberOfCosemObjects,
|
||||||
telegram.getCosemObjects().stream().mapToInt(co -> co.getCosemValues().size()).sum(),
|
telegram.getCosemObjects().stream().mapToInt(co -> co.getCosemValues().size()).sum(),
|
||||||
"Expected number of objects");
|
"Expected number of objects");
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||||
@ -39,6 +40,7 @@ import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
|||||||
*
|
*
|
||||||
* @author Hilbrand Bouwkamp - Initial contribution
|
* @author Hilbrand Bouwkamp - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class DSMRMeterDetectorTest {
|
public class DSMRMeterDetectorTest {
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
@ -45,14 +46,15 @@ import org.openhab.core.thing.ThingUID;
|
|||||||
* @author Hilbrand Bouwkamp - Initial contribution
|
* @author Hilbrand Bouwkamp - Initial contribution
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@NonNullByDefault
|
||||||
public class DSMRMeterDiscoveryServiceTest {
|
public class DSMRMeterDiscoveryServiceTest {
|
||||||
|
|
||||||
private static final String EXPECTED_CONFIGURED_TELEGRAM = "dsmr_50";
|
private static final String EXPECTED_CONFIGURED_TELEGRAM = "dsmr_50";
|
||||||
private static final String UNREGISTERED_METER_TELEGRAM = "unregistered_meter";
|
private static final String UNREGISTERED_METER_TELEGRAM = "unregistered_meter";
|
||||||
|
|
||||||
private @Mock(answer = Answers.RETURNS_DEEP_STUBS) DSMRBridgeHandler bridge;
|
private @NonNullByDefault({}) @Mock(answer = Answers.RETURNS_DEEP_STUBS) DSMRBridgeHandler bridge;
|
||||||
private @Mock Thing thing;
|
private @NonNullByDefault({}) @Mock Thing thing;
|
||||||
private @Mock DSMRMeterHandler meterHandler;
|
private @NonNullByDefault({}) @Mock DSMRMeterHandler meterHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if discovery reports when the user has incorrectly configured the binding with the wrong meter types.
|
* Test if discovery reports when the user has incorrectly configured the binding with the wrong meter types.
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
/FLU5\123456789_A
|
||||||
|
|
||||||
|
0-0:96.1.4(50213)
|
||||||
|
0-0:96.1.1(1234567890123456789012345678)
|
||||||
|
0-0:1.0.0(191128000000W)
|
||||||
|
1-0:1.8.1(000123.456kWh)
|
||||||
|
1-0:1.8.2(000234.567kWh)
|
||||||
|
1-0:2.8.1(000345.678kWh)
|
||||||
|
1-0:2.8.2(000456.789kWh)
|
||||||
|
0-0:96.14.0(0001)
|
||||||
|
1-0:1.7.0(01.234kW)
|
||||||
|
1-0:2.7.0(00.000kW)
|
||||||
|
1-0:32.7.0(234.5V)
|
||||||
|
1-0:31.7.0(005A)
|
||||||
|
0-0:96.3.10(1)
|
||||||
|
0-0:17.0.0(999.9kW)
|
||||||
|
1-0:31.4.0(999A)
|
||||||
|
0-0:96.13.0()
|
||||||
|
0-1:24.1.0(003)
|
||||||
|
0-1:96.1.1(1234567890123456789012345678)
|
||||||
|
0-1:24.4.0(0)
|
||||||
|
0-1:24.2.3(632525252525W)(00000.000)
|
||||||
|
!70AC
|
||||||
Loading…
x
Reference in New Issue
Block a user