Skip to content

Commit

Permalink
[dsmr] Added support for the Luxembourgian Smart Meter "Smarty" (#5273)
Browse files Browse the repository at this point in the history
* Added support for the Luxembourgian Smart Meter "Smarty"
This is based on the DSMR protocol but with encrypted messages.
Also some improvements to the thing defintion files for missing bridges and some small code improvements.
* Updated readme.
* Removed dependency on DatatypeConverter
* Fixed SAT warnings.
* Switched to units added to core.
* Cleanup unit test.
* Fix null warnings.

Signed-off-by: Hilbrand Bouwkamp <[email protected]>
  • Loading branch information
Hilbrand authored and J-N-K committed Apr 9, 2019
1 parent a1bbfc6 commit ba6cab2
Show file tree
Hide file tree
Showing 77 changed files with 1,311 additions and 348 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,15 @@ Fragment-Host: org.openhab.binding.dsmr
Import-Package:
org.apache.commons.io,
org.eclipse.jdt.annotation;resolution:=optional,
org.eclipse.smarthome.core.common,
org.eclipse.smarthome.core.common.registry,
org.eclipse.smarthome.core.events,
org.eclipse.smarthome.core.thing,
org.eclipse.smarthome.core.thing.binding,
org.eclipse.smarthome.core.thing.binding.builder,
org.eclipse.smarthome.core.thing.util,
org.eclipse.smarthome.io.transport.serial,
org.eclipse.smarthome.test,
org.eclipse.smarthome.test.java,
org.eclipse.smarthome.test.storage,
org.hamcrest;core=split,
org.hamcrest.core,
org.junit,
org.mockito,
org.mockito.hamcrest,
org.mockito.invocation,
org.mockito.stubbing,
org.osgi.framework,
org.osgi.service.component,
org.osgi.service.device,
org.slf4j
org.mockito.stubbing
Require-Bundle:
org.hamcrest,
org.junit
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="product" value="org.eclipse.equinox.p2.director.app.product"/>
<booleanAttribute key="run_in_ui_thread" value="false"/>
<stringAttribute key="selected_target_plugins" value="ch.qos.logback.classic@default:default,ch.qos.logback.core@default:default,ch.qos.logback.slf4j@default:false,com.eclipsesource.jaxrs.jersey-min@default:default,com.google.gson@default:default,com.google.guava@default:default,com.google.inject@default:default,com.neuronrobotics.nrjavaserial@default:default,javax.activation@default:default,javax.inject@default:default,javax.measure.unit-api@default:default,javax.servlet@default:default,javax.transaction@default:false,javax.xml@default:default,net.bytebuddy.byte-buddy-agent@default:default,net.bytebuddy.byte-buddy@default:default,org.antlr.runtime@default:default,org.apache.ant@default:default,org.apache.commons.collections@default:default,org.apache.commons.exec@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.logging@default:default,org.apache.commons.net@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.codehaus.groovy@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime@default:true,org.eclipse.emf.common@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.region@default:false,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.transforms.hook@default:false,org.eclipse.equinox.weaving.hook@default:false,org.eclipse.jetty.client@default:default,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.osgi.alpn.fragment@default:false,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.jetty.websocket.api@default:default,org.eclipse.jetty.websocket.client@default:default,org.eclipse.jetty.websocket.common@default:default,org.eclipse.jetty.xml@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.eclipse.xtend.lib.macro@default:default,org.eclipse.xtend.lib@default:default,org.eclipse.xtext.common.types@default:default,org.eclipse.xtext.util@default:default,org.eclipse.xtext.xbase.lib@default:default,org.eclipse.xtext.xbase@default:default,org.eclipse.xtext@default:default,org.hamcrest.core@default:default,org.hamcrest.integration@default:default,org.hamcrest.library@default:default,org.hamcrest.text@default:default,org.hamcrest@default:default,org.junit@default:default,org.mockito.mockito-core@default:default,org.objectweb.asm@default:default,org.objenesis@default:default,org.slf4j.api@default:default,org.slf4j.log4j@default:default,tec.uom.lib.uom-lib-common@default:default,tec.uom.se@default:default"/>
<stringAttribute key="selected_workspace_plugins" value="org.eclipse.smarthome.config.core@default:default,org.eclipse.smarthome.config.discovery@default:default,org.eclipse.smarthome.config.xml@default:default,org.eclipse.smarthome.core.audio@default:default,org.eclipse.smarthome.core.persistence@default:default,org.eclipse.smarthome.core.scheduler@default:default,org.eclipse.smarthome.core.thing@default:default,org.eclipse.smarthome.core.transform@default:default,org.eclipse.smarthome.core.voice@default:default,org.eclipse.smarthome.core@default:default,org.eclipse.smarthome.io.console@default:default,org.eclipse.smarthome.io.http@default:default,org.eclipse.smarthome.io.net@default:default,org.eclipse.smarthome.io.transport.serial@default:default,org.eclipse.smarthome.test@default:default,org.openhab.binding.dsmr.test@default:false,org.openhab.binding.dsmr@default:default"/>
<stringAttribute key="selected_target_plugins" value="ch.qos.logback.classic@default:default,ch.qos.logback.core@default:default,ch.qos.logback.slf4j@default:false,com.eclipsesource.jaxrs.jersey-min@default:default,com.google.gson@default:default,com.google.guava@default:default,com.google.inject@default:default,com.neuronrobotics.nrjavaserial@default:default,javax.activation@default:default,javax.inject@default:default,javax.measure.unit-api@default:default,javax.servlet@default:default,javax.transaction@default:false,javax.xml.bind@default:default,javax.xml@default:default,net.bytebuddy.byte-buddy-agent@default:default,net.bytebuddy.byte-buddy@default:default,org.antlr.runtime@default:default,org.apache.ant@default:default,org.apache.commons.collections@default:default,org.apache.commons.exec@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.logging@default:default,org.apache.commons.net@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.codehaus.groovy@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime@default:true,org.eclipse.emf.common@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.region@default:false,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.transforms.hook@default:false,org.eclipse.equinox.weaving.hook@default:false,org.eclipse.jetty.client@default:default,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.osgi.alpn.fragment@default:false,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.jetty.websocket.api@default:default,org.eclipse.jetty.websocket.client@default:default,org.eclipse.jetty.websocket.common@default:default,org.eclipse.jetty.xml@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.eclipse.smarthome.config.core@default:default,org.eclipse.smarthome.config.discovery@default:default,org.eclipse.smarthome.config.xml@default:default,org.eclipse.smarthome.core.thing@default:default,org.eclipse.smarthome.core@default:default,org.eclipse.smarthome.io.console@default:default,org.eclipse.smarthome.io.transport.serial@default:default,org.eclipse.smarthome.test@default:default,org.eclipse.xtend.lib.macro@default:default,org.eclipse.xtend.lib@default:default,org.eclipse.xtext.common.types@default:default,org.eclipse.xtext.util@default:default,org.eclipse.xtext.xbase.lib@default:default,org.eclipse.xtext.xbase@default:default,org.eclipse.xtext@default:default,org.hamcrest.core@default:default,org.hamcrest.integration@default:default,org.hamcrest.library@default:default,org.hamcrest.text@default:default,org.hamcrest@default:default,org.junit@default:default,org.mockito.mockito-core@default:default,org.objectweb.asm@default:default,org.objenesis@default:default,org.slf4j.api@default:default,org.slf4j.log4j@default:default,tec.uom.lib.uom-lib-common@default:default,tec.uom.se@default:default"/>
<stringAttribute key="selected_workspace_plugins" value="org.openhab.binding.dsmr.test@default:false,org.openhab.binding.dsmr@default:default"/>
<booleanAttribute key="show_selected_only" value="false"/>
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/
package org.openhab.binding.dsmr.internal;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -61,7 +61,9 @@ public static P1Telegram readTelegram(String telegramName, TelegramState expecte
byte[] telegram = readRawTelegram(telegramName);
P1TelegramParser parser = new P1TelegramParser(p1Telegram::set);

parser.parseData(telegram, 0, telegram.length);
parser.setLenientMode(true);
parser.parse(telegram, telegram.length);
assertNotNull("Telegram state should have been set. (Missing newline at end of message?)", p1Telegram.get());
assertEquals("Expected TelegramState should be as expected", expectedTelegramState,
p1Telegram.get().getTelegramState());
return p1Telegram.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public void handleErrorEvent(@NonNull DSMRConnectorErrorEvent connectorErrorEven
try (InputStream inputStream = new ByteArrayInputStream(TelegramReaderUtil.readRawTelegram(TELEGRAM_NAME))) {
when(mockSerialPort.getInputStream()).thenReturn(inputStream);
DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
scheduler, 1);
new DSMRTelegramListener(), scheduler, 1);
device.start();
assertSame("Expect to be starting discovery state", DeviceState.DISCOVER_SETTINGS, device.getState());
serialPortEventListener
Expand Down Expand Up @@ -136,7 +136,7 @@ public void handleErrorEvent(@NonNull DSMRConnectorErrorEvent connectorErrorEven
throw new PortInUseException();
}).when(mockIdentifier).open(eq(DSMRBindingConstants.DSMR_PORT_NAME), anyInt());
DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
scheduler, 1);
new DSMRTelegramListener(), scheduler, 1);
device.start();
assertSame("Expected an error", DSMRConnectorErrorEvent.IN_USE, eventRef.get());
assertSame("Expect to be in error state", DeviceState.ERROR, device.getState());
Expand All @@ -146,7 +146,8 @@ public void handleErrorEvent(@NonNull DSMRConnectorErrorEvent connectorErrorEven
assertSame("Expect to be starting discovery state", DeviceState.DISCOVER_SETTINGS, device.getState());
// Trigger device to go into error stage with port doesn't exist.
mockIdentifier = null;
device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener, scheduler, 1);
device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener, new DSMRTelegramListener(),
scheduler, 1);
device.start();
assertSame("Expected an error", DSMRConnectorErrorEvent.DONT_EXISTS, eventRef.get());
assertSame("Expect to be in error state", DeviceState.ERROR, device.getState());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Copyright (c) 2010-2018 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.dsmr.internal.device;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;

/**
* Test class for the {@link SmartyDecrypter}.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class SmartyDecrypterTest {

private static final String KEY = "D491470F47126332B07D1923B3504188";
private static final int[] TELEGRAM = new int[] { 0xDB, 0x08, 0x53, 0x41, 0x47, 0x67, 0x70, 0x01, 0xBD, 0x54, 0x82,
0x02, 0x7A, 0x30, 0x00, 0x05, 0xA8, 0xE3, 0x80, 0x6E, 0xE6, 0xE6, 0x39, 0x27, 0x4C, 0x7B, 0xC5, 0x70, 0x95,
0xF8, 0x72, 0xB0, 0x8D, 0xDE, 0x62, 0x1F, 0xB7, 0x4E, 0xE8, 0x1E, 0x5E, 0xBE, 0x34, 0x2C, 0x93, 0xD8, 0xE7,
0x37, 0x81, 0xFB, 0x2A, 0x1E, 0xB8, 0x71, 0x00, 0x74, 0xA5, 0x4F, 0xC5, 0x7A, 0xA7, 0xD1, 0xD9, 0x92, 0x36,
0xC4, 0x2E, 0x2E, 0xC0, 0x1A, 0x03, 0x24, 0xEF, 0xC7, 0xF0, 0x2E, 0x3B, 0xA2, 0xFA, 0x43, 0x19, 0x6C, 0xA6,
0x03, 0x83, 0x8A, 0xB8, 0x32, 0x2D, 0xFF, 0xA8, 0x3F, 0x7E, 0x83, 0x93, 0x2E, 0x60, 0x07, 0x40, 0x6B, 0x11,
0x2B, 0x41, 0x74, 0x5F, 0x38, 0x80, 0x56, 0x46, 0xD5, 0x3F, 0xEA, 0x7A, 0x02, 0x9F, 0xA7, 0x9C, 0x36, 0xD9,
0xD7, 0x77, 0xDA, 0x8B, 0x5A, 0x03, 0xED, 0x3E, 0xC6, 0xE4, 0x85, 0xDE, 0xC0, 0xC9, 0x8D, 0x7D, 0x53, 0xC0,
0xBD, 0x17, 0x5B, 0x22, 0x8E, 0x01, 0xF4, 0x66, 0xD9, 0x84, 0x37, 0x77, 0x80, 0x85, 0x03, 0xD6, 0x30, 0x15,
0x8C, 0x82, 0x36, 0xD8, 0x68, 0x08, 0x8E, 0xB1, 0x39, 0x77, 0xBD, 0xFC, 0x47, 0x43, 0x67, 0xE5, 0x57, 0x13,
0x6F, 0xEB, 0x6D, 0x53, 0x7F, 0x18, 0x30, 0x44, 0x84, 0xA0, 0x14, 0xB4, 0x90, 0x99, 0xA6, 0x3C, 0x18, 0xD4,
0x30, 0x3A, 0x4C, 0x53, 0x85, 0x3A, 0x0C, 0xA8, 0xC5, 0xB0, 0xE3, 0xE8, 0x05, 0xBF, 0xD8, 0x42, 0x54, 0x10,
0xFA, 0xB5, 0x22, 0xB3, 0x5A, 0x8F, 0x25, 0x8B, 0x57, 0xBB, 0x1E, 0x97, 0xE2, 0x48, 0x9B, 0x6F, 0x37, 0x07,
0x17, 0x53, 0xAF, 0xFB, 0xB4, 0xEE, 0xBC, 0xA5, 0x02, 0xEE, 0xA5, 0x92, 0xB8, 0x80, 0x85, 0xAE, 0x15, 0x5B,
0xCE, 0xA8, 0xF7, 0x32, 0xD6, 0xB0, 0x11, 0x5B, 0xB9, 0xCE, 0xD7, 0x6B, 0xE5, 0xB8, 0x93, 0xB2, 0xA7, 0xEF,
0xC8, 0x16, 0x3C, 0x17, 0x1E, 0x77, 0xC3, 0xD2, 0x9A, 0x72, 0xB7, 0x47, 0x8E, 0xD6, 0xDF, 0xE2, 0xC4, 0x8B,
0xF3, 0xF9, 0xD8, 0xF8, 0x95, 0xCA, 0x1C, 0xB4, 0x2E, 0xAA, 0xC4, 0xCB, 0x21, 0xD6, 0xEA, 0xB5, 0x1E, 0x77,
0xE4, 0xD6, 0x02, 0x9D, 0x78, 0x84, 0x27, 0xDD, 0x5B, 0xFC, 0x46, 0xBD, 0xD3, 0xE8, 0xA3, 0x2D, 0xBB, 0x6F,
0x93, 0xB4, 0x84, 0x2B, 0x07, 0x3E, 0x9B, 0x6F, 0xE6, 0xE5, 0xDF, 0xC0, 0x58, 0xB5, 0xF4, 0x54, 0xAA, 0x3E,
0xF1, 0x62, 0xBD, 0xF9, 0x3C, 0xB1, 0xE0, 0xC4, 0x52, 0xDB, 0xB2, 0xBE, 0x4C, 0xB4, 0xC6, 0x7D, 0x16, 0x9E,
0x2A, 0x30, 0x61, 0x8F, 0xA2, 0x16, 0x54, 0x41, 0xB6, 0xD3, 0xC2, 0x2F, 0x1C, 0x36, 0x27, 0xE3, 0x5F, 0xFF,
0xCF, 0x9F, 0x19, 0x47, 0xD7, 0xA6, 0xAE, 0x94, 0x2F, 0xC2, 0x1E, 0x24, 0x6F, 0x0E, 0xFC, 0x45, 0x6A, 0x78,
0x89, 0xC9, 0x61, 0xC9, 0x3E, 0xA0, 0x89, 0xEE, 0xF6, 0xD1, 0xA4, 0x40, 0x56, 0xBA, 0xAA, 0xB3, 0x52, 0xCB,
0xEA, 0x7D, 0x7E, 0x20, 0x67, 0x57, 0xAF, 0x0D, 0x42, 0x70, 0x64, 0xB0, 0x58, 0xD5, 0x72, 0x35, 0xA7, 0x8F,
0x8D, 0xB9, 0x1C, 0xE4, 0xD6, 0x0A, 0x3C, 0x6B, 0xAB, 0xD9, 0x9A, 0x61, 0x16, 0x9B, 0x17, 0x2F, 0x24, 0x14,
0xF5, 0x65, 0xBD, 0xE0, 0x23, 0x96, 0x54, 0xEA, 0xC7, 0x75, 0x52, 0xFB, 0xCC, 0x2A, 0x2F, 0xA1, 0x02, 0xD7,
0xCD, 0x00, 0x3D, 0xDB, 0xEB, 0x9C, 0x1F, 0x81, 0x1D, 0xBC, 0x69, 0x53, 0x76, 0x33, 0x6E, 0x2E, 0x9D, 0x6C,
0xFC, 0x73, 0xFC, 0xB4, 0xA2, 0x94, 0x46, 0xBD, 0x7C, 0xE3, 0x17, 0x0E, 0x58, 0x56, 0x76, 0x6B, 0x25, 0xE8,
0x20, 0x49, 0xEE, 0x55, 0x08, 0xEC, 0x18, 0x16, 0x68, 0x5F, 0x1D, 0xCE, 0xC2, 0x38, 0x19, 0x12, 0x16, 0xDF,
0xFB, 0xA0, 0x64, 0x7D, 0x32, 0xA1, 0x55, 0x9D, 0x99, 0x64, 0x0A, 0x15, 0xF2, 0x4A, 0xB0, 0x21, 0x72, 0xDB,
0x3D, 0x56, 0x44, 0x54, 0xE7, 0xA0, 0x34, 0x6C, 0x09, 0x93, 0x99, 0xB8, 0x77, 0x0A, 0x9B, 0xDA, 0xA6, 0x19,
0xC7, 0xC5, 0x23, 0x05, 0x26, 0xD6, 0xB8, 0x04, 0x99, 0xA0, 0xEE, 0x0C, 0xD4, 0xFC, 0x46, 0xA6, 0x77, 0xAF,
0x68, 0xEF, 0x4D, 0xEE, 0x9E, 0x76, 0xB7, 0x8A, 0xC8, 0xA0, 0x4F, 0x43, 0x1D, 0xB3, 0x98, 0x6F, 0xCF, 0xD4,
0x5E, 0xE1, 0xF1, 0x07, 0x99, 0xAF, 0x0B, 0x79, 0x4A, 0x41, 0x03, 0x73, 0x5D, 0x6D, 0xDA, 0x38, 0x29, 0x17,
0x3F, 0x80, 0xF1, 0x38, 0xA5, 0x30, 0x1A, 0xFD, 0xF1, 0xDB, 0x87, 0x74, 0xF0, 0xC4, 0x0E, 0x6E, 0x71, 0x26,
0x4B, 0x33, 0x4A, 0x94, 0x3F, 0xC1, 0x05, 0x52, 0xB0, 0x75, 0x2F, 0x3A, 0x0D, 0x7F, 0xD8, 0x04, 0x58, 0x59,
0x54, 0x08, 0xF2, 0x85, 0x0F, 0x17, };

/**
* Tests decrypting of a single complete Smarty telegram.
*/
@Test
public void testSmartyDecrypter() {
AtomicReference<String> telegramResult = new AtomicReference<String>("");
P1TelegramListener telegramListener = telegram -> telegramResult.set(telegram.getRawTelegram());
SmartyDecrypter decoder = new SmartyDecrypter(new P1TelegramParser(telegramListener),
new DSMRTelegramListener(KEY), KEY);
decoder.setLenientMode(true);
byte[] data = new byte[TELEGRAM.length];

for (int i = 0; i < TELEGRAM.length; i++) {
data[i] = (byte) TELEGRAM[i];
}

decoder.parse(data, data.length);
String expected = new String(TelegramReaderUtil.readRawTelegram("smarty"), StandardCharsets.UTF_8);

assertThat("Should have correctly decrypted the telegram", telegramResult.get(), is(equalTo(expected)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static final List<Object[]> data() {
{ "Landis_Gyr_E350", 10, },
{ "Landis_Gyr_ZCF110", 25, },
{ "Sagemcom_XS210", 41, },

{ "smarty", 24, },
});
}
// @formatter:on
Expand All @@ -59,7 +59,8 @@ public static final List<Object[]> data() {
@Test
public void testParsing() {
P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
assertEquals("Should not have any unknown cosem objects", 0, telegram.getUnknownCosemObjects().size());
assertEquals("Expected number of objects", numberOfCosemObjects,
telegram.getCosemObjects().stream().mapToInt(o -> o.getCosemValues().size()).sum());
telegram.getCosemObjects().stream().mapToInt(co -> co.getCosemValues().size()).sum());
}
}
Loading

0 comments on commit ba6cab2

Please sign in to comment.