added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.dsmr-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-dsmr" description="DSMR Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.dsmr/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* This class defines common constants, which are used across the whole binding.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Removed time constants
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class DSMRBindingConstants {
|
||||
/**
|
||||
* Binding id.
|
||||
*/
|
||||
public static final String BINDING_ID = "dsmr";
|
||||
|
||||
/**
|
||||
* Key to use to identify serial port.
|
||||
*/
|
||||
public static final String DSMR_PORT_NAME = "org.openhab.binding.dsmr";
|
||||
|
||||
/**
|
||||
* Bridge device things
|
||||
*/
|
||||
public static final ThingTypeUID THING_TYPE_DSMR_BRIDGE = new ThingTypeUID(BINDING_ID, "dsmrBridge");
|
||||
public static final ThingTypeUID THING_TYPE_SMARTY_BRIDGE = new ThingTypeUID(BINDING_ID, "smartyBridge");
|
||||
|
||||
/**
|
||||
* Configuration parameter for the serial port.
|
||||
*/
|
||||
public static final String CONFIGURATION_SERIAL_PORT = "serialPort";
|
||||
|
||||
public static final String CONFIGURATION_DECRYPTION_KEY = "decryptionKey";
|
||||
public static final String CONFIGURATION_DECRYPTION_KEY_EMPTY = "";
|
||||
|
||||
private DSMRBindingConstants() {
|
||||
// Constants class
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal;
|
||||
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.discovery.DSMRMeterDiscoveryService;
|
||||
import org.openhab.binding.dsmr.internal.handler.DSMRBridgeHandler;
|
||||
import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DSMRHandlerFactory} is responsible for creating things and thing handlers.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored discovery service to use standard discovery class methods.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.dsmr")
|
||||
public class DSMRHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRHandlerFactory.class);
|
||||
|
||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
private @NonNullByDefault({}) SerialPortManager serialPortManager;
|
||||
private @NonNullByDefault({}) LocaleProvider localeProvider;
|
||||
private @NonNullByDefault({}) TranslationProvider i18nProvider;
|
||||
|
||||
/**
|
||||
* Returns if the specified ThingTypeUID is supported by this handler.
|
||||
*
|
||||
* This handler support the THING_TYPE_DSMR_BRIDGE type and all ThingTypesUID that
|
||||
* belongs to the supported DSMRMeterType objects
|
||||
*
|
||||
* @param thingTypeUID {@link ThingTypeUID} to check
|
||||
* @return true if the specified ThingTypeUID is supported, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
if (THING_TYPE_DSMR_BRIDGE.equals(thingTypeUID) || THING_TYPE_SMARTY_BRIDGE.equals(thingTypeUID)) {
|
||||
logger.debug("DSMR Bridge Thing {} supported", thingTypeUID);
|
||||
return true;
|
||||
} else {
|
||||
boolean thingTypeUIDIsMeter = DSMRMeterType.METER_THING_TYPES.contains(thingTypeUID);
|
||||
|
||||
if (thingTypeUIDIsMeter) {
|
||||
logger.trace("{} is a supported DSMR Meter thing", thingTypeUID);
|
||||
}
|
||||
return thingTypeUIDIsMeter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the ThingHandler for the corresponding Thing
|
||||
*
|
||||
* There are two handlers supported:
|
||||
* - DSMRBridgeHandler that handle the Thing that corresponds to the physical DSMR device and does the serial
|
||||
* communication
|
||||
* - MeterHandler that handles the Meter things that are a logical part of the physical device
|
||||
*
|
||||
* @param thing The Thing to create a ThingHandler for
|
||||
* @return ThingHandler for the given Thing or null if the Thing is not supported
|
||||
*/
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
logger.debug("Searching for thingTypeUID {}", thingTypeUID);
|
||||
|
||||
if (THING_TYPE_DSMR_BRIDGE.equals(thingTypeUID) || THING_TYPE_SMARTY_BRIDGE.equals(thingTypeUID)) {
|
||||
DSMRBridgeHandler handler = new DSMRBridgeHandler((Bridge) thing, serialPortManager);
|
||||
registerDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (DSMRMeterType.METER_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new DSMRMeterHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a meter discovery service for the bridge handler.
|
||||
*
|
||||
* @param bridgeHandler handler to register service for
|
||||
*/
|
||||
private synchronized void registerDiscoveryService(DSMRBridgeHandler bridgeHandler) {
|
||||
DSMRMeterDiscoveryService discoveryService = new DSMRMeterDiscoveryService(bridgeHandler);
|
||||
|
||||
discoveryService.setLocaleProvider(localeProvider);
|
||||
discoveryService.setTranslationProvider(i18nProvider);
|
||||
this.discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof DSMRBridgeHandler) {
|
||||
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
DSMRMeterDiscoveryService service = (DSMRMeterDiscoveryService) getBundleContext()
|
||||
.getService(serviceReg.getReference());
|
||||
serviceReg.unregister();
|
||||
if (service != null) {
|
||||
service.unsetLocaleProvider();
|
||||
service.unsetTranslationProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setLocaleProvider(final LocaleProvider localeProvider) {
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
protected void unsetLocaleProvider(final LocaleProvider localeProvider) {
|
||||
this.localeProvider = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setTranslationProvider(TranslationProvider i18nProvider) {
|
||||
this.i18nProvider = i18nProvider;
|
||||
}
|
||||
|
||||
protected void unsetTranslationProvider(TranslationProvider i18nProvider) {
|
||||
this.i18nProvider = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Interface for classes controlling DSMR devices.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DSMRDevice {
|
||||
|
||||
/**
|
||||
* Restart the DSMR device.
|
||||
*/
|
||||
void restart();
|
||||
|
||||
/**
|
||||
* Start the DSMR device.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the DSMR device.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @param lenientMode the lenientMode to set
|
||||
*/
|
||||
void setLenientMode(boolean lenientMode);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* Class describing the DSMR bridge user configuration
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - added receivedTimeout configuration
|
||||
*/
|
||||
public class DSMRDeviceConfiguration {
|
||||
/**
|
||||
* Serial port name
|
||||
*/
|
||||
public String serialPort;
|
||||
|
||||
/**
|
||||
* Serial port baud rate
|
||||
*/
|
||||
public int baudrate;
|
||||
|
||||
/**
|
||||
* Serial port data bits
|
||||
*/
|
||||
public int databits;
|
||||
|
||||
/**
|
||||
* Serial port parity
|
||||
*/
|
||||
public String parity;
|
||||
|
||||
/**
|
||||
* Serial port stop bits
|
||||
*/
|
||||
public String stopbits;
|
||||
|
||||
/**
|
||||
* The Luxembourgish smart meter decryption key
|
||||
*/
|
||||
public String decryptionKey;
|
||||
|
||||
/**
|
||||
* When no message was received after the configured number of seconds action will be taken.
|
||||
*/
|
||||
public int receivedTimeout;
|
||||
|
||||
/**
|
||||
* @return true if serial port settings are all set.
|
||||
*/
|
||||
public boolean isSerialFixedSettings() {
|
||||
return baudrate > 0 && databits > 0 && !StringUtils.isBlank(parity) && !StringUtils.isBlank(stopbits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DSMRDeviceConfiguration [serialPort=" + serialPort + ", Baudrate=" + baudrate + ", Databits=" + databits
|
||||
+ ", Parity=" + parity + ", Stopbits=" + stopbits + ", decryptionKey=" + decryptionKey
|
||||
+ ", receivedTimeout=" + receivedTimeout + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DSMRDeviceRunnable} runs a {@link DSMRDevice} and blocks until it is restarted or shutdown. If it is
|
||||
* restarted it will restart the {@link DSMRDevice}. If it is shutdown the run will end. By using a semaphore to restart
|
||||
* and shutdown this class handles the actual {@link DSMRDevice}, while threads calling restart and shutdown can finish
|
||||
* fast.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRDeviceRunnable implements Runnable {
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRDeviceRunnable.class);
|
||||
private final Semaphore semaphore = new Semaphore(0);
|
||||
private final DSMRDevice device;
|
||||
private final DSMREventListener portEventListener;
|
||||
|
||||
/**
|
||||
* Keeps state of running. If false run will stop.
|
||||
*/
|
||||
private boolean running;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param device the device to control
|
||||
* @param eventListener listener to used ot report errors.
|
||||
*/
|
||||
public DSMRDeviceRunnable(DSMRDevice device, DSMREventListener eventListener) {
|
||||
this.device = device;
|
||||
this.portEventListener = eventListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets state to restart the dsmr device.
|
||||
*/
|
||||
public void restart() {
|
||||
releaseSemaphore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets state to shutdown the dsmr device.
|
||||
*/
|
||||
public void stop() {
|
||||
running = false;
|
||||
releaseSemaphore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the dsmr device. Runs until shutdown.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
running = true;
|
||||
device.start();
|
||||
while (running && !Thread.interrupted()) {
|
||||
semaphore.acquire();
|
||||
// Just drain all other permits to make sure it's not called twice
|
||||
semaphore.drainPermits();
|
||||
if (running) {
|
||||
logger.trace("Restarting device");
|
||||
device.restart();
|
||||
}
|
||||
}
|
||||
logger.trace("Device shutdown");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("DSMRDeviceRunnable stopped with a RuntimeException", e);
|
||||
portEventListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
device.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around semaphore to only release when no permits available.
|
||||
*/
|
||||
private void releaseSemaphore() {
|
||||
synchronized (semaphore) {
|
||||
if (semaphore.availablePermits() == 0) {
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
|
||||
/**
|
||||
* Interface for classes handling DSMR connector events.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - renamed classes/methods
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DSMREventListener {
|
||||
/**
|
||||
* Callback for DSMRPortEvent events
|
||||
*
|
||||
* @param connectorErrorEvent {@link DSMRConnectorErrorEvent} that has occurred
|
||||
*/
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent);
|
||||
|
||||
/**
|
||||
* Callback for received P1 telegrams
|
||||
*
|
||||
* @param telegram the received P1 telegram
|
||||
*/
|
||||
public void handleTelegramReceived(P1Telegram telegram);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
|
||||
/**
|
||||
* Implementation of a DSMRDevice with fixed serial port settings. With fixed port settings the code is much simpler
|
||||
* because no detecting of settings needs to be done and when things fail no redirecting is needed either.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRFixedConfigDevice implements DSMRDevice {
|
||||
|
||||
private final DSMRSerialConnector dsmrPort;
|
||||
private final DSMRSerialSettings fixedPortSettings;
|
||||
private final DSMRTelegramListener telegramListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param serialPortManager the manager to get a new serial port connecting from
|
||||
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
|
||||
* @param fixedPortSettings The serial port connection settings
|
||||
* @param listener the parent {@link DSMREventListener}
|
||||
* @param telegramListener listener to report found telegrams or errors
|
||||
*/
|
||||
public DSMRFixedConfigDevice(SerialPortManager serialPortManager, String serialPortName,
|
||||
DSMRSerialSettings fixedPortSettings, DSMREventListener listener, DSMRTelegramListener telegramListener) {
|
||||
this.fixedPortSettings = fixedPortSettings;
|
||||
this.telegramListener = telegramListener;
|
||||
telegramListener.setDsmrEventListener(listener);
|
||||
|
||||
dsmrPort = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
dsmrPort.open(fixedPortSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
dsmrPort.restart(fixedPortSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
dsmrPort.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLenientMode(boolean lenientMode) {
|
||||
telegramListener.setLenientMode(lenientMode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DSMR Serial device that auto discovers the serial port speed.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - New class. Simplified states and contains code specific to discover the serial port
|
||||
* settings automatically.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
|
||||
/**
|
||||
* Enum to keep track of the internal state of {@link DSMRSerialAutoDevice}.
|
||||
*/
|
||||
enum DeviceState {
|
||||
/**
|
||||
* Discovers the settings of the serial port.
|
||||
*/
|
||||
DISCOVER_SETTINGS,
|
||||
/**
|
||||
* Device is receiving telegram data from the serial port.
|
||||
*/
|
||||
NORMAL,
|
||||
/**
|
||||
* Communication with serial port isn't working.
|
||||
*/
|
||||
ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* When switching baudrate ignore any errors received with the given time frame. Switching baudrate causes errors
|
||||
* and should not be interpreted as reading errors.
|
||||
*/
|
||||
private static final long SWITCHING_BAUDRATE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(5);
|
||||
|
||||
/**
|
||||
* This factor is multiplied with the {@link #baudrateSwitchTimeoutSeconds} and used as the duration the discovery
|
||||
* of the baudrate may take.
|
||||
*/
|
||||
private static final long DISCOVER_TIMEOUT_FACTOR = 4L;
|
||||
|
||||
/*
|
||||
* Februari 2017
|
||||
* Due to the Dutch Smart Meter program where every residence is provided
|
||||
* a smart for free and the smart meters are DSMR V4 or higher
|
||||
* we assume the majority of meters communicate with HIGH_SPEED_SETTINGS
|
||||
* For older meters this means initializing is taking probably 1 minute
|
||||
*/
|
||||
private static final DSMRSerialSettings DEFAULT_PORT_SETTINGS = DSMRSerialSettings.HIGH_SPEED_SETTINGS;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRSerialAutoDevice.class);
|
||||
|
||||
/**
|
||||
* DSMR Connector to the serial port
|
||||
*/
|
||||
private final DSMRSerialConnector dsmrConnector;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final DSMRTelegramListener telegramListener;
|
||||
|
||||
/**
|
||||
* Time in seconds in which period valid data is expected during discovery. If exceeded without success the baudrate
|
||||
* is switched
|
||||
*/
|
||||
private final int baudrateSwitchTimeoutSeconds;
|
||||
|
||||
/**
|
||||
* Serial port connection settings
|
||||
*/
|
||||
private DSMRSerialSettings portSettings = DEFAULT_PORT_SETTINGS;
|
||||
|
||||
/**
|
||||
* Keeps track of the state this instance is in.
|
||||
*/
|
||||
private DeviceState state = DeviceState.NORMAL;
|
||||
|
||||
/**
|
||||
* Timer for handling discovery of a single setting.
|
||||
*/
|
||||
private @Nullable ScheduledFuture<?> halfTimeTimer;
|
||||
|
||||
/**
|
||||
* Timer for handling end of discovery.
|
||||
*/
|
||||
private @Nullable ScheduledFuture<?> endTimeTimer;
|
||||
|
||||
/**
|
||||
* The listener of the class handling the connector events
|
||||
*/
|
||||
private DSMREventListener parentListener;
|
||||
|
||||
/**
|
||||
* Time in nanos the last time the baudrate was switched. This is used during discovery ignore errors retrieved
|
||||
* after switching baudrate for the period set in {@link #SWITCHING_BAUDRATE_TIMEOUT_NANOS}.
|
||||
*/
|
||||
private long lastSwitchedBaudrateNanos;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DSMRSerialAutoDevice}
|
||||
*
|
||||
* @param serialPortManager the manager to get a new serial port connecting from
|
||||
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
|
||||
* @param listener the parent {@link DSMREventListener}
|
||||
* @param telegramListener listener to report found telegrams or errors
|
||||
* @param scheduler the scheduler to use with the baudrate switching timers
|
||||
* @param baudrateSwitchTimeoutSeconds timeout period for when to try other baudrate settings and end the discovery
|
||||
* of the baudrate
|
||||
*/
|
||||
public DSMRSerialAutoDevice(SerialPortManager serialPortManager, String serialPortName, DSMREventListener listener,
|
||||
DSMRTelegramListener telegramListener, ScheduledExecutorService scheduler,
|
||||
int baudrateSwitchTimeoutSeconds) {
|
||||
this.parentListener = listener;
|
||||
this.scheduler = scheduler;
|
||||
this.baudrateSwitchTimeoutSeconds = baudrateSwitchTimeoutSeconds;
|
||||
this.telegramListener = telegramListener;
|
||||
telegramListener.setDsmrEventListener(listener);
|
||||
dsmrConnector = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
|
||||
logger.debug("Initialized port '{}'", serialPortName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
stopDiscover(DeviceState.DISCOVER_SETTINGS);
|
||||
portSettings = DEFAULT_PORT_SETTINGS;
|
||||
telegramListener.setDsmrEventListener(this);
|
||||
dsmrConnector.open(portSettings);
|
||||
restartHalfTimer();
|
||||
endTimeTimer = scheduler.schedule(this::endTimeScheduledCall,
|
||||
baudrateSwitchTimeoutSeconds * DISCOVER_TIMEOUT_FACTOR, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
if (state == DeviceState.ERROR) {
|
||||
stop();
|
||||
start();
|
||||
} else if (state == DeviceState.NORMAL) {
|
||||
dsmrConnector.restart(portSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stop() {
|
||||
dsmrConnector.close();
|
||||
stopDiscover(state);
|
||||
logger.trace("stopped with state:{}", state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle if telegrams are received.
|
||||
*
|
||||
* @param telegram the details of the received telegram
|
||||
*/
|
||||
@Override
|
||||
public void handleTelegramReceived(P1Telegram telegram) {
|
||||
if (!telegram.getCosemObjects().isEmpty()) {
|
||||
stopDiscover(DeviceState.NORMAL);
|
||||
parentListener.handleTelegramReceived(telegram);
|
||||
logger.info("Start receiving telegrams on port {} with settings: {}", dsmrConnector.getPortName(),
|
||||
portSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for DSMR Port events.
|
||||
*
|
||||
* @param portEvent {@link DSMRConnectorErrorEvent} to handle
|
||||
*/
|
||||
@Override
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
|
||||
logger.trace("Received portEvent {}", portEvent.getEventDetails());
|
||||
if (portEvent == DSMRConnectorErrorEvent.READ_ERROR) {
|
||||
switchBaudrate();
|
||||
} else {
|
||||
logger.debug("Error during discovery of port settings: {}, current state:{}.", portEvent.getEventDetails(),
|
||||
state);
|
||||
stopDiscover(DeviceState.ERROR);
|
||||
parentListener.handleErrorEvent(portEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lenientMode the lenientMode to set
|
||||
*/
|
||||
@Override
|
||||
public void setLenientMode(boolean lenientMode) {
|
||||
telegramListener.setLenientMode(lenientMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the state of the instance. Used for testing only.
|
||||
*/
|
||||
DeviceState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the baudrate on the serial port.
|
||||
*/
|
||||
private void switchBaudrate() {
|
||||
if (lastSwitchedBaudrateNanos + SWITCHING_BAUDRATE_TIMEOUT_NANOS > System.nanoTime()) {
|
||||
// Ignore switching baudrate if this is called within the timeout after a previous switch.
|
||||
return;
|
||||
}
|
||||
lastSwitchedBaudrateNanos = System.nanoTime();
|
||||
if (state == DeviceState.DISCOVER_SETTINGS) {
|
||||
restartHalfTimer();
|
||||
logger.debug(
|
||||
"Discover port settings is running for half time now and still nothing discovered, switching baudrate and retrying");
|
||||
portSettings = portSettings == DSMRSerialSettings.HIGH_SPEED_SETTINGS
|
||||
? DSMRSerialSettings.LOW_SPEED_SETTINGS
|
||||
: DSMRSerialSettings.HIGH_SPEED_SETTINGS;
|
||||
dsmrConnector.setSerialPortParams(portSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the discovery process as triggered by the end timer. It will only act if the discovery process was still
|
||||
* running.
|
||||
*/
|
||||
private void endTimeScheduledCall() {
|
||||
if (state == DeviceState.DISCOVER_SETTINGS) {
|
||||
stopDiscover(DeviceState.ERROR);
|
||||
parentListener.handleErrorEvent(DSMRConnectorErrorEvent.DONT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the discovery of port speed process and sets the state with which it should be stopped.
|
||||
*
|
||||
* @param state the state with which the process was stopped.
|
||||
*/
|
||||
private void stopDiscover(DeviceState state) {
|
||||
telegramListener.setDsmrEventListener(parentListener);
|
||||
logger.debug("Stop discovery of port settings.");
|
||||
if (halfTimeTimer != null) {
|
||||
halfTimeTimer.cancel(true);
|
||||
halfTimeTimer = null;
|
||||
}
|
||||
if (endTimeTimer != null) {
|
||||
endTimeTimer.cancel(true);
|
||||
endTimeTimer = null;
|
||||
}
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to (re)start the switching baudrate timer.
|
||||
*/
|
||||
private void restartHalfTimer() {
|
||||
if (halfTimeTimer != null) {
|
||||
halfTimeTimer.cancel(true);
|
||||
}
|
||||
halfTimeTimer = scheduler.schedule(this::switchBaudrate, baudrateSwitchTimeoutSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorListener;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
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.P1TelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Helper listener to receive telegram data from the connector, send it to the parser and forward data or errors from
|
||||
* the parser to the DSMR Device.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Moved this code out of the DSMRPort class, fixed some issues and reduced code
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRTelegramListener.class);
|
||||
private final TelegramParser parser;
|
||||
|
||||
private @NonNullByDefault({}) DSMREventListener dsmrEventListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param eventListener listener to send received errors or messages to
|
||||
*/
|
||||
public DSMRTelegramListener() {
|
||||
parser = new P1TelegramParser(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs {@link DSMRTelegramListener} with a Smarty decryptor to first decrypt incoming messages.
|
||||
*
|
||||
* @param decryptionKey Smarty decryption key
|
||||
*/
|
||||
public DSMRTelegramListener(String decryptionKey) {
|
||||
parser = new SmartyDecrypter(new P1TelegramParser(this), this, decryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DSMR event listener.
|
||||
*
|
||||
* @param eventListener the listener to set
|
||||
*/
|
||||
public void setDsmrEventListener(DSMREventListener eventListener) {
|
||||
this.dsmrEventListener = eventListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleData(byte[] data, int length) {
|
||||
parser.parse(data, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
|
||||
dsmrEventListener.handleErrorEvent(portEvent);
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for cosemObjects received in a P1 telegram
|
||||
*
|
||||
* @param telegram the received telegram.
|
||||
*/
|
||||
@Override
|
||||
public void telegramReceived(P1Telegram telegram) {
|
||||
final TelegramState telegramState = telegram.getTelegramState();
|
||||
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Received {} Cosem Objects with state: '{}'", cosemObjects.size(), telegramState);
|
||||
}
|
||||
if (telegramState == TelegramState.OK || telegramState == TelegramState.INVALID_ENCRYPTION_KEY) {
|
||||
dsmrEventListener.handleTelegramReceived(telegram);
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Telegram received with error state '{}': {}", telegramState,
|
||||
cosemObjects.stream().map(CosemObject::toString).collect(Collectors.joining(",")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lenientMode the lenientMode to set
|
||||
*/
|
||||
public void setLenientMode(boolean lenientMode) {
|
||||
parser.setLenientMode(lenientMode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.P1TelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Decodes messages send by The Luxembourgian Smart Meter "Smarty".
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartyDecrypter implements TelegramParser {
|
||||
|
||||
private enum State {
|
||||
WAITING_FOR_START_BYTE,
|
||||
READ_SYSTEM_TITLE_LENGTH,
|
||||
READ_SYSTEM_TITLE,
|
||||
READ_SEPARATOR_82,
|
||||
READ_PAYLOAD_LENGTH,
|
||||
READ_SEPARATOR_30,
|
||||
READ_FRAME_COUNTER,
|
||||
READ_PAYLOAD,
|
||||
READ_GCM_TAG,
|
||||
DONE_READING_TELEGRAM
|
||||
}
|
||||
|
||||
private static final byte START_BYTE = (byte) 0xDB;
|
||||
private static final byte SEPARATOR_82 = (byte) 0x82;
|
||||
private static final byte SEPARATOR_30 = 0x30;
|
||||
private static final int ADD_LENGTH = 17;
|
||||
private static final String ADD = "3000112233445566778899AABBCCDDEEFF";
|
||||
private static final byte[] ADD_DECODED = HexUtils.hexToBytes(ADD);
|
||||
private static final int IV_BUFFER_LENGTH = 40;
|
||||
private static final int GCM_TAG_LENGTH = 12;
|
||||
private static final int GCM_BITS = GCM_TAG_LENGTH * Byte.SIZE;
|
||||
private static final int MESSAGES_BUFFER_SIZE = 4096;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartyDecrypter.class);
|
||||
private final ByteBuffer iv = ByteBuffer.allocate(IV_BUFFER_LENGTH);
|
||||
private final ByteBuffer cipherText = ByteBuffer.allocate(MESSAGES_BUFFER_SIZE);
|
||||
private final TelegramParser parser;
|
||||
private @Nullable final SecretKeySpec secretKeySpec;
|
||||
|
||||
private State state = State.WAITING_FOR_START_BYTE;
|
||||
private int currentBytePosition;
|
||||
private int changeToNextStateAt;
|
||||
private int ivLength;
|
||||
private int dataLength;
|
||||
private boolean lenientMode;
|
||||
private P1TelegramListener telegramListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param parser parser of the Cosem messages
|
||||
* @param telegramListener
|
||||
* @param decryptionKey The key to decrypt the messages
|
||||
*/
|
||||
public SmartyDecrypter(final TelegramParser parser, final P1TelegramListener telegramListener,
|
||||
final String decryptionKey) {
|
||||
this.parser = parser;
|
||||
this.telegramListener = telegramListener;
|
||||
secretKeySpec = decryptionKey.isEmpty() ? null : new SecretKeySpec(HexUtils.hexToBytes(decryptionKey), "AES");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(final byte[] data, final int length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
currentBytePosition++;
|
||||
if (processStateActions(data[i])) {
|
||||
processCompleted();
|
||||
}
|
||||
}
|
||||
if (lenientMode && secretKeySpec == null) {
|
||||
parser.parse(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processStateActions(final byte rawInput) {
|
||||
switch (state) {
|
||||
case WAITING_FOR_START_BYTE:
|
||||
if (rawInput == START_BYTE) {
|
||||
reset();
|
||||
state = State.READ_SYSTEM_TITLE_LENGTH;
|
||||
}
|
||||
break;
|
||||
case READ_SYSTEM_TITLE_LENGTH:
|
||||
state = State.READ_SYSTEM_TITLE;
|
||||
// 2 start bytes (position 0 and 1) + system title length
|
||||
changeToNextStateAt = 1 + rawInput;
|
||||
break;
|
||||
case READ_SYSTEM_TITLE:
|
||||
iv.put(rawInput);
|
||||
ivLength++;
|
||||
if (currentBytePosition >= changeToNextStateAt) {
|
||||
state = State.READ_SEPARATOR_82;
|
||||
changeToNextStateAt++;
|
||||
}
|
||||
break;
|
||||
case READ_SEPARATOR_82:
|
||||
if (rawInput == SEPARATOR_82) {
|
||||
state = State.READ_PAYLOAD_LENGTH; // Ignore separator byte
|
||||
changeToNextStateAt += 2;
|
||||
} else {
|
||||
logger.debug("Missing separator (0x82). Dropping telegram.");
|
||||
state = State.WAITING_FOR_START_BYTE;
|
||||
}
|
||||
break;
|
||||
case READ_PAYLOAD_LENGTH:
|
||||
dataLength <<= 8;
|
||||
dataLength |= rawInput;
|
||||
if (currentBytePosition >= changeToNextStateAt) {
|
||||
state = State.READ_SEPARATOR_30;
|
||||
changeToNextStateAt++;
|
||||
}
|
||||
break;
|
||||
case READ_SEPARATOR_30:
|
||||
if (rawInput == SEPARATOR_30) {
|
||||
state = State.READ_FRAME_COUNTER;
|
||||
// 4 bytes for frame counter
|
||||
changeToNextStateAt += 4;
|
||||
} else {
|
||||
logger.debug("Missing separator (0x30). Dropping telegram.");
|
||||
state = State.WAITING_FOR_START_BYTE;
|
||||
}
|
||||
break;
|
||||
case READ_FRAME_COUNTER:
|
||||
iv.put(rawInput);
|
||||
ivLength++;
|
||||
if (currentBytePosition >= changeToNextStateAt) {
|
||||
state = State.READ_PAYLOAD;
|
||||
changeToNextStateAt += dataLength - ADD_LENGTH;
|
||||
}
|
||||
break;
|
||||
case READ_PAYLOAD:
|
||||
cipherText.put(rawInput);
|
||||
if (currentBytePosition >= changeToNextStateAt) {
|
||||
state = State.READ_GCM_TAG;
|
||||
changeToNextStateAt += GCM_TAG_LENGTH;
|
||||
}
|
||||
break;
|
||||
case READ_GCM_TAG:
|
||||
// All input has been read.
|
||||
cipherText.put(rawInput);
|
||||
if (currentBytePosition >= changeToNextStateAt) {
|
||||
state = State.DONE_READING_TELEGRAM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (state == State.DONE_READING_TELEGRAM) {
|
||||
state = State.WAITING_FOR_START_BYTE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processCompleted() {
|
||||
final byte[] plainText = decrypt();
|
||||
|
||||
reset();
|
||||
if (plainText == null) {
|
||||
telegramListener
|
||||
.telegramReceived(new P1Telegram(Collections.emptyList(), TelegramState.INVALID_ENCRYPTION_KEY));
|
||||
} else {
|
||||
parser.parse(plainText, plainText.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the collected message.
|
||||
*
|
||||
* @return the decrypted message
|
||||
*/
|
||||
private byte @Nullable [] decrypt() {
|
||||
try {
|
||||
if (secretKeySpec != null) {
|
||||
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec,
|
||||
new GCMParameterSpec(GCM_BITS, iv.array(), 0, ivLength));
|
||||
cipher.updateAAD(ADD_DECODED);
|
||||
return cipher.doFinal(cipherText.array(), 0, cipherText.position());
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
logger.warn("Decrypting smarty telegram failed: ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
parser.reset();
|
||||
state = State.WAITING_FOR_START_BYTE;
|
||||
iv.clear();
|
||||
cipherText.clear();
|
||||
currentBytePosition = 0;
|
||||
changeToNextStateAt = 0;
|
||||
ivLength = 0;
|
||||
dataLength = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLenientMode(final boolean lenientMode) {
|
||||
this.lenientMode = lenientMode;
|
||||
parser.setLenientMode(lenientMode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.connector;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for connectors. Reads data from an input stream. Subclasses should implement connection specific methods
|
||||
* and trigger the reading of the data.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Major refactoring. Code moved around from other classes.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class DSMRBaseConnector {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRBaseConnector.class);
|
||||
|
||||
/**
|
||||
* Listener to send received data and errors to.
|
||||
*/
|
||||
protected final DSMRConnectorListener dsmrConnectorListener;
|
||||
|
||||
/**
|
||||
* 1Kbyte buffer for storing received data.
|
||||
*/
|
||||
private final byte[] buffer = new byte[1024]; // 1K
|
||||
|
||||
/**
|
||||
* Read lock to have 1 process reading at a time.
|
||||
*/
|
||||
private final Object readLock = new Object();
|
||||
|
||||
/**
|
||||
* Keeps track of the open state of the connector.
|
||||
*/
|
||||
private boolean open;
|
||||
|
||||
public DSMRBaseConnector(DSMRConnectorListener connectorListener) {
|
||||
this.dsmrConnectorListener = connectorListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input stream reading the Serial port.
|
||||
*/
|
||||
private @Nullable BufferedInputStream inputStream;
|
||||
|
||||
/**
|
||||
* Opens the connector with the given stream to read data from.
|
||||
*
|
||||
* @param inputStream input stream to read data from
|
||||
* @throws IOException throws exception in case input stream is null
|
||||
*/
|
||||
protected void open(@Nullable InputStream inputStream) throws IOException {
|
||||
if (inputStream == null) {
|
||||
throw new IOException("Inputstream is null");
|
||||
}
|
||||
this.inputStream = new BufferedInputStream(inputStream);
|
||||
open = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true if connector is in state open
|
||||
*/
|
||||
protected boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connector.
|
||||
*/
|
||||
protected void close() {
|
||||
open = false;
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Failed to close reader", ioe);
|
||||
}
|
||||
}
|
||||
inputStream = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads available data from the input stream.
|
||||
*/
|
||||
protected void handleDataAvailable() {
|
||||
try {
|
||||
synchronized (readLock) {
|
||||
BufferedInputStream localInputStream = inputStream;
|
||||
|
||||
if (localInputStream != null) {
|
||||
int bytesAvailable = localInputStream.available();
|
||||
while (bytesAvailable > 0) {
|
||||
int bytesAvailableRead = localInputStream.read(buffer, 0,
|
||||
Math.min(bytesAvailable, buffer.length));
|
||||
|
||||
if (open && bytesAvailableRead > 0) {
|
||||
dsmrConnectorListener.handleData(buffer, bytesAvailableRead);
|
||||
} else {
|
||||
logger.debug("Expected bytes {} to read, but {} bytes were read", bytesAvailable,
|
||||
bytesAvailableRead);
|
||||
}
|
||||
bytesAvailable = localInputStream.available();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
|
||||
logger.debug("Exception on read data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.connector;
|
||||
|
||||
/**
|
||||
* Error events from a connector.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Reduced number of event to only errors
|
||||
*/
|
||||
public enum DSMRConnectorErrorEvent {
|
||||
DONT_EXISTS,
|
||||
IN_USE,
|
||||
INTERNAL_ERROR,
|
||||
NOT_COMPATIBLE,
|
||||
READ_ERROR;
|
||||
|
||||
/**
|
||||
* @return the event details
|
||||
*/
|
||||
public String getEventDetails() {
|
||||
return "@text/error.connector." + name().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.connector;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Connector listener to handle connector events.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DSMRConnectorListener {
|
||||
|
||||
/**
|
||||
* Callback for {@link DSMRConnectorErrorEvent} events.
|
||||
*
|
||||
* @param portEvent {@link DSMRConnectorErrorEvent} that has occurred
|
||||
*/
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent);
|
||||
|
||||
/**
|
||||
* Handle data.
|
||||
*
|
||||
* @param buffer byte buffer with the data
|
||||
* @param length length of the data in the buffer. Buffer may be larger than data in buffer, therefore always use
|
||||
* length
|
||||
*/
|
||||
void handleData(byte[] buffer, int length);
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.connector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEventListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class that implements the DSMR port functionality that comply to the Dutch Smart Meter Requirements.
|
||||
* <p>
|
||||
* This class will handle communication with the Serial Port itself and notify listeners about events and received P1
|
||||
* telegrams.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Split code and moved DSMR specific handling to separate class.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPortEventListener {
|
||||
/**
|
||||
* Serial port read time out in milliseconds.
|
||||
*/
|
||||
private static final int SERIAL_PORT_READ_TIMEOUT_MILLISECONDS = (int) TimeUnit.SECONDS.toMillis(15);
|
||||
|
||||
/**
|
||||
* The receive threshold/time out set on the serial port.
|
||||
*/
|
||||
private static final int SERIAL_TIMEOUT_MILLISECONDS = 1000;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRSerialConnector.class);
|
||||
|
||||
/**
|
||||
* Manager to get new port from.
|
||||
*/
|
||||
private final SerialPortManager portManager;
|
||||
|
||||
/**
|
||||
* Serial port name.
|
||||
*/
|
||||
private final String serialPortName;
|
||||
|
||||
/**
|
||||
* Serial port instance.
|
||||
*/
|
||||
private AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* DSMR Connector listener.
|
||||
*/
|
||||
private final DSMRConnectorListener dsmrConnectorListener;
|
||||
|
||||
/**
|
||||
* The portLock is used for the shared data used when opening and closing
|
||||
* the port. The following shared data must be guarded by the lock:
|
||||
* SerialPort, BufferedReader
|
||||
*/
|
||||
private final Object portLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a new DSMR serial connector. This is only a reference to a port. The port will
|
||||
* not be opened nor it is checked if the DSMR Port can successfully be opened.
|
||||
*
|
||||
* @param portManager Serial Port Manager
|
||||
* @param serialPortName Device identifier of the port (e.g. /dev/ttyUSB0)
|
||||
* @param dsmrConnectorListener The listener to send error or received data from the port
|
||||
*/
|
||||
public DSMRSerialConnector(SerialPortManager portManager, String serialPortName,
|
||||
DSMRConnectorListener dsmrConnectorListener) {
|
||||
super(dsmrConnectorListener);
|
||||
this.portManager = portManager;
|
||||
this.serialPortName = serialPortName;
|
||||
this.dsmrConnectorListener = dsmrConnectorListener;
|
||||
}
|
||||
|
||||
public String getPortName() {
|
||||
return serialPortName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Operation System Serial Port.
|
||||
*
|
||||
* @param portSettings The serial port settings to open the port with
|
||||
*/
|
||||
public void open(DSMRSerialSettings portSettings) {
|
||||
DSMRConnectorErrorEvent errorEvent = null;
|
||||
|
||||
synchronized (portLock) {
|
||||
SerialPortIdentifier portIdentifier = portManager.getIdentifier(serialPortName);
|
||||
if (portIdentifier == null) {
|
||||
logger.debug("Port {} does not exists", serialPortName);
|
||||
|
||||
errorEvent = DSMRConnectorErrorEvent.DONT_EXISTS;
|
||||
} else {
|
||||
errorEvent = open(portSettings, portIdentifier);
|
||||
}
|
||||
if (errorEvent != null) {
|
||||
// handle event within lock
|
||||
dsmrConnectorListener.handleErrorEvent(errorEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable DSMRConnectorErrorEvent open(DSMRSerialSettings portSettings,
|
||||
SerialPortIdentifier portIdentifier) {
|
||||
DSMRConnectorErrorEvent errorEvent = null;
|
||||
|
||||
try {
|
||||
logger.trace("Opening port {}", serialPortName);
|
||||
SerialPort oldSerialPort = serialPortReference.get();
|
||||
|
||||
// Opening Operating System Serial Port
|
||||
SerialPort serialPort = portIdentifier.open(DSMRBindingConstants.DSMR_PORT_NAME,
|
||||
SERIAL_PORT_READ_TIMEOUT_MILLISECONDS);
|
||||
|
||||
// Configure Serial Port based on specified port speed
|
||||
logger.trace("Configure serial port parameters: {}", portSettings);
|
||||
serialPort.setSerialPortParams(portSettings.getBaudrate(), portSettings.getDataBits(),
|
||||
portSettings.getStopbits(), portSettings.getParity());
|
||||
|
||||
// SerialPort is ready, open the reader
|
||||
logger.trace("SerialPort opened successful on {}", serialPortName);
|
||||
open(serialPort.getInputStream());
|
||||
|
||||
serialPort.addEventListener(this);
|
||||
|
||||
// Start listening for events
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
serialPort.notifyOnBreakInterrupt(true);
|
||||
serialPort.notifyOnFramingError(true);
|
||||
serialPort.notifyOnOverrunError(true);
|
||||
serialPort.notifyOnParityError(true);
|
||||
|
||||
try {
|
||||
serialPort.enableReceiveThreshold(SERIAL_TIMEOUT_MILLISECONDS);
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
logger.debug("Enable receive threshold is unsupported");
|
||||
}
|
||||
try {
|
||||
serialPort.enableReceiveTimeout(SERIAL_TIMEOUT_MILLISECONDS);
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
logger.debug("Enable receive timeout is unsupported");
|
||||
}
|
||||
// The binding is ready, let the meter know we want to receive values
|
||||
serialPort.setRTS(true);
|
||||
if (!serialPortReference.compareAndSet(oldSerialPort, serialPort)) {
|
||||
logger.warn("Possible bug because a new serial port value was set during opening new port.");
|
||||
errorEvent = DSMRConnectorErrorEvent.INTERNAL_ERROR;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Failed to get inputstream for serialPort", ioe);
|
||||
|
||||
errorEvent = DSMRConnectorErrorEvent.READ_ERROR;
|
||||
} catch (TooManyListenersException tmle) {
|
||||
logger.warn("Possible bug because a listener was added while one already set.", tmle);
|
||||
|
||||
errorEvent = DSMRConnectorErrorEvent.INTERNAL_ERROR;
|
||||
} catch (PortInUseException piue) {
|
||||
logger.debug("Port already in use: {}", serialPortName, piue);
|
||||
|
||||
errorEvent = DSMRConnectorErrorEvent.IN_USE;
|
||||
} catch (UnsupportedCommOperationException ucoe) {
|
||||
logger.debug("Port does not support requested port settings (invalid dsmr:portsettings parameter?): {}",
|
||||
serialPortName, ucoe);
|
||||
|
||||
errorEvent = DSMRConnectorErrorEvent.NOT_COMPATIBLE;
|
||||
}
|
||||
return errorEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the serial port and release OS resources.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (portLock) {
|
||||
logger.debug("Closing DSMR serial port");
|
||||
|
||||
// Stop listening for serial port events
|
||||
SerialPort serialPort = serialPortReference.get();
|
||||
|
||||
if (serialPort != null) {
|
||||
// Let meter stop sending values
|
||||
serialPort.setRTS(false);
|
||||
serialPort.removeEventListener();
|
||||
try {
|
||||
InputStream inputStream = serialPort.getInputStream();
|
||||
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Failed to close serial port inputstream", ioe);
|
||||
}
|
||||
serialPort.close();
|
||||
}
|
||||
super.close();
|
||||
serialPort = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set serial port settings or restarts the connection with the port settings if it's not open.
|
||||
*
|
||||
* @param portSettings the port settings to set on the serial port
|
||||
*/
|
||||
public void setSerialPortParams(DSMRSerialSettings portSettings) {
|
||||
synchronized (portLock) {
|
||||
if (isOpen()) {
|
||||
logger.debug("Update port {} with settings: {}", this.serialPortName, portSettings);
|
||||
try {
|
||||
SerialPort serialPort = serialPortReference.get();
|
||||
|
||||
if (serialPort != null) {
|
||||
serialPort.setSerialPortParams(portSettings.getBaudrate(), portSettings.getDataBits(),
|
||||
portSettings.getStopbits(), portSettings.getParity());
|
||||
}
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
logger.debug(
|
||||
"Port does {} not support requested port settings (invalid dsmr:portsettings parameter?): {}",
|
||||
serialPortName, portSettings);
|
||||
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.NOT_COMPATIBLE);
|
||||
}
|
||||
} else {
|
||||
restart(portSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the Serial Port speed (LOW --> HIGH and vice versa).
|
||||
*/
|
||||
public void restart(DSMRSerialSettings portSettings) {
|
||||
synchronized (portLock) {
|
||||
logger.trace("Restart port {} with settings: {}", this.serialPortName, portSettings);
|
||||
close();
|
||||
open(portSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialEvent(@Nullable SerialPortEvent seEvent) {
|
||||
if (seEvent == null) {
|
||||
return;
|
||||
}
|
||||
if (logger.isTraceEnabled() && SerialPortEvent.DATA_AVAILABLE != seEvent.getEventType()) {
|
||||
logger.trace("Serial event: {}, new value:{}", seEvent.getEventType(), seEvent.getNewValue());
|
||||
}
|
||||
try {
|
||||
switch (seEvent.getEventType()) {
|
||||
case SerialPortEvent.DATA_AVAILABLE:
|
||||
handleDataAvailable();
|
||||
break;
|
||||
case SerialPortEvent.BI:
|
||||
handleErrorEvent("Break interrupt", seEvent);
|
||||
break;
|
||||
case SerialPortEvent.FE:
|
||||
handleErrorEvent("Frame error", seEvent);
|
||||
break;
|
||||
case SerialPortEvent.OE:
|
||||
handleErrorEvent("Overrun error", seEvent);
|
||||
break;
|
||||
case SerialPortEvent.PE:
|
||||
handleErrorEvent("Parity error", seEvent);
|
||||
break;
|
||||
default: // do nothing
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("RuntimeException during handling serial event: {}", seEvent.getEventType(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error event. If open and it's a new value it should be handled by the listener.
|
||||
*
|
||||
* @param typeName type of the event, used in logging only
|
||||
* @param portEvent Serial port event that triggered the error.
|
||||
*/
|
||||
private void handleErrorEvent(String typeName, SerialPortEvent portEvent) {
|
||||
if (isOpen() && portEvent.getNewValue()) {
|
||||
logger.trace("New DSMR port {} event", typeName);
|
||||
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleDataAvailable() {
|
||||
// open port if it is not open
|
||||
if (serialPortReference.get() == null) {
|
||||
logger.debug("Serial port is not open, no values will be read");
|
||||
return;
|
||||
}
|
||||
super.handleDataAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.connector;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
|
||||
/**
|
||||
* Class for storing port settings
|
||||
* This class does store 4 serial parameters (baudrate, databits, parity, stopbits)
|
||||
* for use in {@link DSMRSerialConnector}.
|
||||
*
|
||||
* This class can also convert a string setting (<speed> <databits><parity><stopbits>)
|
||||
* to a {@link DSMRSerialSettings} object (e.g. 115200 8N1)
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Removed auto detecting state checking from this class.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRSerialSettings {
|
||||
|
||||
/**
|
||||
* Fixed settings for high speed communication (DSMR V4 and up)
|
||||
*/
|
||||
public static final DSMRSerialSettings HIGH_SPEED_SETTINGS = new DSMRSerialSettings(115200, SerialPort.DATABITS_8,
|
||||
SerialPort.PARITY_NONE, SerialPort.STOPBITS_1);
|
||||
|
||||
/**
|
||||
* Fixed settings for low speed communication (DSMR V3 and down)
|
||||
*/
|
||||
public static final DSMRSerialSettings LOW_SPEED_SETTINGS = new DSMRSerialSettings(9600, SerialPort.DATABITS_7,
|
||||
SerialPort.PARITY_EVEN, SerialPort.STOPBITS_1);
|
||||
|
||||
/**
|
||||
* Serial port baudrate
|
||||
*/
|
||||
private final int baudrate;
|
||||
|
||||
/**
|
||||
* Serial port databits
|
||||
*/
|
||||
private final int databits;
|
||||
|
||||
/**
|
||||
* Serial port parity
|
||||
*/
|
||||
private final int parity;
|
||||
|
||||
/**
|
||||
* Serial port stop bits
|
||||
*/
|
||||
private final int stopbits;
|
||||
|
||||
/**
|
||||
* Construct a new {@link DSMRSerialSettings} object.
|
||||
*
|
||||
* @param baudrate baudrate of the port
|
||||
* @param databits no data bits to use (use SerialPort.DATABITS_* constant)
|
||||
* @param parity parity to use (use SerialPort.PARITY_* constant)
|
||||
* @param stopbits no stopbits to use (use SerialPort.STOPBITS_* constant)
|
||||
*/
|
||||
private DSMRSerialSettings(int baudrate, int databits, int parity, int stopbits) {
|
||||
this.baudrate = baudrate;
|
||||
this.databits = databits;
|
||||
this.parity = parity;
|
||||
this.stopbits = stopbits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the baudrate
|
||||
*
|
||||
* @return baudrate setting
|
||||
*/
|
||||
public int getBaudrate() {
|
||||
return baudrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of data bits
|
||||
*
|
||||
* @return databits setting
|
||||
*/
|
||||
public int getDataBits() {
|
||||
return databits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parity setting
|
||||
*
|
||||
* @return parity setting
|
||||
*/
|
||||
public int getParity() {
|
||||
return parity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of stop bits
|
||||
*
|
||||
* @return stop bits setting
|
||||
*/
|
||||
public int getStopbits() {
|
||||
return stopbits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String toString = "Baudrate:" + baudrate + ", databits:" + databits;
|
||||
|
||||
switch (parity) {
|
||||
case SerialPort.PARITY_EVEN:
|
||||
toString += ", parity:even";
|
||||
break;
|
||||
case SerialPort.PARITY_NONE:
|
||||
toString += ", parity:none";
|
||||
break;
|
||||
case SerialPort.PARITY_ODD:
|
||||
toString += ", parity:odd";
|
||||
break;
|
||||
default:
|
||||
toString += ", parity:<unknown>";
|
||||
break;
|
||||
}
|
||||
switch (stopbits) {
|
||||
case SerialPort.STOPBITS_1:
|
||||
toString += ", stopbits:1";
|
||||
break;
|
||||
case SerialPort.STOPBITS_1_5:
|
||||
toString += ", stopbits:1.5";
|
||||
break;
|
||||
case SerialPort.STOPBITS_2:
|
||||
toString += ", stopbits:2";
|
||||
break;
|
||||
default:
|
||||
toString += ", stopbits:<unknown>";
|
||||
break;
|
||||
}
|
||||
return toString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the manual entered port setting if all configuration fields have a value (not null).
|
||||
*
|
||||
* @param deviceConfiguration manual entered device configuration
|
||||
* @return serial configuration.
|
||||
*/
|
||||
public static DSMRSerialSettings getPortSettingsFromConfiguration(DSMRDeviceConfiguration deviceConfiguration) {
|
||||
int baudrate = deviceConfiguration.baudrate;
|
||||
int databits = deviceConfiguration.databits;
|
||||
|
||||
int parity;
|
||||
switch (deviceConfiguration.parity) {
|
||||
case "E":
|
||||
parity = SerialPort.PARITY_EVEN;
|
||||
break;
|
||||
case "O":
|
||||
parity = SerialPort.PARITY_ODD;
|
||||
break;
|
||||
case "N":
|
||||
parity = SerialPort.PARITY_NONE;
|
||||
break;
|
||||
default:
|
||||
parity = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
int stopbits;
|
||||
switch (deviceConfiguration.stopbits) {
|
||||
case "1":
|
||||
stopbits = SerialPort.STOPBITS_1;
|
||||
break;
|
||||
case "1.5":
|
||||
stopbits = SerialPort.STOPBITS_1_5;
|
||||
break;
|
||||
case "2":
|
||||
stopbits = SerialPort.STOPBITS_2;
|
||||
break;
|
||||
default:
|
||||
stopbits = -1;
|
||||
break;
|
||||
}
|
||||
return new DSMRSerialSettings(baudrate, databits, parity, stopbits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* CosemDate represents a datetime value and will try to autodetect the format
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Class now a factory instead of data containing class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class CosemDate extends CosemValueDescriptor<DateTimeType> {
|
||||
|
||||
public static final CosemDate INSTANCE = new CosemDate("timestamp");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CosemDate.class);
|
||||
|
||||
public CosemDate(String ohChannelId) {
|
||||
super(ohChannelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum contains the known date formats for the DSMR-specification
|
||||
*/
|
||||
private enum CosemDateFormat {
|
||||
/*
|
||||
* Ignore DST setting for general format. We use local time that is already DST
|
||||
*/
|
||||
COSEM_DATE_GENERAL("(\\d{12})([S,W]?)", "yyMMddHHmmss"),
|
||||
COSEM_DATE_DSMR_V2("(\\d{2}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})", "yy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* Cached compiled pattern
|
||||
*/
|
||||
private final Pattern pattern;
|
||||
|
||||
/**
|
||||
* Cached java date formatter
|
||||
*/
|
||||
private final DateTimeFormatter formatter;
|
||||
|
||||
/**
|
||||
* Constructs a new CosemDateFormat
|
||||
*
|
||||
* @param regex String containing the regular expression to check the value against (the date format
|
||||
* should at least contain 1 regex group
|
||||
* @param javaDateFormat String containing the datetime format to use for parsing
|
||||
*/
|
||||
private CosemDateFormat(String regex, String javaDateFormat) {
|
||||
pattern = Pattern.compile(regex);
|
||||
formatter = DateTimeFormatter.ofPattern(javaDateFormat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a String value to an openHAB DateTimeType
|
||||
* <p>
|
||||
* The input string must be in the format yyMMddHHmmssX
|
||||
* <p>
|
||||
* Based on the DSMR specification X is:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>''. Valid for DSMR v3 specification
|
||||
* <li>'S'. Specifies a summer time (DST = 1) datetime
|
||||
* <li>'W'. Specifies a winter time (DST = 0) datetime
|
||||
* </ul>
|
||||
*
|
||||
* @param cosemValue the value to parse
|
||||
* @return {@link DateTimeType} representing the value the cosem value
|
||||
* @throws ParseException if parsing failed
|
||||
*/
|
||||
@Override
|
||||
protected DateTimeType getStateValue(String cosemValue) throws ParseException {
|
||||
for (CosemDateFormat cosemDateFormat : CosemDateFormat.values()) {
|
||||
logger.trace("Trying pattern: {}", cosemDateFormat.pattern);
|
||||
|
||||
Matcher m = cosemDateFormat.pattern.matcher(cosemValue);
|
||||
|
||||
if (m.matches()) {
|
||||
logger.trace("{} matches pattern: {}", cosemValue, cosemDateFormat.pattern);
|
||||
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(m.group(1), cosemDateFormat.formatter);
|
||||
return new DateTimeType(ZonedDateTime.of(localDateTime, ZoneId.systemDefault()));
|
||||
}
|
||||
}
|
||||
throw new ParseException("Cosem value: '" + cosemValue + "' is not a known CosemDate string", 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
|
||||
/**
|
||||
* CosemInteger represents an decimal value
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Combined Integer and Double because {@link DecimalType} handles both
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class CosemDecimal extends CosemValueDescriptor<DecimalType> {
|
||||
|
||||
public static final CosemDecimal INSTANCE = new CosemDecimal(false);
|
||||
public static final CosemDecimal INSTANCE_WITH_UNITS = new CosemDecimal(true);
|
||||
|
||||
/**
|
||||
* If true it can be the input contains a unit. In that case the unit will be stripped before parsing.
|
||||
*/
|
||||
private final boolean expectUnit;
|
||||
|
||||
private CosemDecimal(boolean expectUnit) {
|
||||
this.expectUnit = expectUnit;
|
||||
}
|
||||
|
||||
public CosemDecimal(String ohChannelId) {
|
||||
super(ohChannelId);
|
||||
this.expectUnit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a String value (that represents an decimal) to an {@link DecimalType} object.
|
||||
*
|
||||
* @param cosemValue the value to parse
|
||||
* @return {@link DecimalType} representing the value of the cosem value
|
||||
* @throws ParseException if parsing failed
|
||||
*/
|
||||
@Override
|
||||
protected DecimalType getStateValue(String cosemValue) throws ParseException {
|
||||
try {
|
||||
final String value;
|
||||
|
||||
if (expectUnit) {
|
||||
final int sep = cosemValue.indexOf('*');
|
||||
value = sep > 0 ? cosemValue.substring(0, sep) : cosemValue;
|
||||
} else {
|
||||
value = cosemValue;
|
||||
}
|
||||
return new DecimalType(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new ParseException("Failed to parse value '" + cosemValue + "' as integer", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
|
||||
/**
|
||||
* {@link CosemHexString} represents a string value stored as Hexadecimal values.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Class now a factory instead of data containing class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class CosemHexString extends CosemValueDescriptor<StringType> {
|
||||
|
||||
public static final CosemHexString INSTANCE = new CosemHexString();
|
||||
|
||||
private static final String NO_VALUE = "00";
|
||||
|
||||
/**
|
||||
* Parses a String representing the hex value to a {@link StringType}.
|
||||
*
|
||||
* @param cosemValue the value to parse
|
||||
* @return {@link StringType} representing the value the cosem hex value
|
||||
* @throws ParseException if parsing failed
|
||||
*/
|
||||
@Override
|
||||
protected StringType getStateValue(String cosemValue) throws ParseException {
|
||||
final String cosemHexValue = cosemValue.replaceAll("\\r\\n", "").trim();
|
||||
|
||||
if (cosemHexValue.length() % 2 != 0) {
|
||||
throw new ParseException(cosemHexValue + " is not a valid hexadecimal string", 0);
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < cosemHexValue.length(); i += 2) {
|
||||
final String hexValue = cosemHexValue.substring(i, i + 2);
|
||||
|
||||
if (!NO_VALUE.equals(hexValue)) {
|
||||
try {
|
||||
sb.append((char) Integer.parseInt(hexValue, 16));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ParseException("Failed to parse hex value from '" + cosemValue + "' as char", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new StringType(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class for Cosem Object implementation
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CosemObject {
|
||||
|
||||
/**
|
||||
* Regular expression for finding CosemValues
|
||||
*/
|
||||
private static final Pattern COSEM_VALUES_PATTERN = Pattern.compile("(\\(([^\\(\\)]*)\\))");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CosemObject.class);
|
||||
|
||||
/**
|
||||
* CosemObject type
|
||||
*/
|
||||
private final CosemObjectType type;
|
||||
|
||||
/**
|
||||
* The actual OBISIdentifier for this CosemObject
|
||||
*/
|
||||
private final OBISIdentifier obisIdentifier;
|
||||
|
||||
/**
|
||||
* List of COSEM value in this message
|
||||
*/
|
||||
private Map<String, State> cosemValues;
|
||||
|
||||
/**
|
||||
* Construct a new CosemObject with the specified OBIS Message Type
|
||||
*
|
||||
* @param msgType
|
||||
* {@link CosemObjectType}
|
||||
*/
|
||||
public CosemObject(CosemObjectType msgType, OBISIdentifier obisIdentifier) {
|
||||
this.type = msgType;
|
||||
this.obisIdentifier = obisIdentifier;
|
||||
|
||||
cosemValues = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link CosemObjectType} for this Cosem Object
|
||||
*
|
||||
* @return the {@link CosemObjectType} for this Cosem Object
|
||||
*/
|
||||
public CosemObjectType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the obisIdentifier
|
||||
*/
|
||||
public OBISIdentifier getObisIdentifier() {
|
||||
return obisIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string representation of this Cosem Object
|
||||
*
|
||||
* @return string representation of this Cosem Object
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cosem Object(type:" + type + ", cosemValues:" + cosemValues + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Cosem values that are part of this Cosem Object
|
||||
*
|
||||
* @return Map of channel keys with state values that are part of this Cosem Object
|
||||
*/
|
||||
public Map<String, ? extends State> getCosemValues() {
|
||||
return cosemValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the List of COSEM String value to COSEM objects values.
|
||||
* <p>
|
||||
* When the parser has problems it throws an {@link ParseException}. The
|
||||
* already parsed values will still be available. It is up to the caller how
|
||||
* to handle a partially parsed message.
|
||||
*
|
||||
* @param cosemValueString the List of COSEM String values
|
||||
* @throws ParseException if parsing fails
|
||||
*/
|
||||
public void parseCosemValues(String cosemValueString) throws ParseException {
|
||||
logger.trace("Parsing CosemValue string {}", cosemValueString);
|
||||
|
||||
Matcher cosemValueMatcher = COSEM_VALUES_PATTERN.matcher(cosemValueString);
|
||||
int nrOfCosemValues = countCosemValues(cosemValueMatcher);
|
||||
|
||||
if (type.supportsNrOfValues(nrOfCosemValues)) {
|
||||
logger.trace("Received items: {} is supported", nrOfCosemValues);
|
||||
|
||||
int cosemValueItr = 0;
|
||||
while (cosemValueMatcher.find()) {
|
||||
Entry<String, CosemValueDescriptor<?>> valueDescriptorEntry = type.getDescriptor(cosemValueItr);
|
||||
State cosemValue = valueDescriptorEntry.getValue().getStateValue(cosemValueMatcher.group(2));
|
||||
|
||||
if (cosemValue != null) {
|
||||
if (!cosemValues.containsKey(valueDescriptorEntry.getKey())) {
|
||||
cosemValues.put(valueDescriptorEntry.getKey(), cosemValue);
|
||||
} else {
|
||||
logger.warn("Value for descriptor {} already exists, dropping value {}", valueDescriptorEntry,
|
||||
cosemValue);
|
||||
}
|
||||
}
|
||||
cosemValueItr++;
|
||||
}
|
||||
} else {
|
||||
throw new ParseException(type + " does not support " + nrOfCosemValues + " items", 0);
|
||||
}
|
||||
}
|
||||
|
||||
private int countCosemValues(Matcher cosemValueMatcher) {
|
||||
int nrOfCosemValues = 0;
|
||||
|
||||
while (cosemValueMatcher.find()) {
|
||||
nrOfCosemValues++;
|
||||
}
|
||||
// We need the matcher again, reset to initial state
|
||||
cosemValueMatcher.reset();
|
||||
return nrOfCosemValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for constructing Cosem Objects from Strings
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CosemObjectFactory {
|
||||
private final Logger logger = LoggerFactory.getLogger(CosemObjectFactory.class);
|
||||
|
||||
/**
|
||||
* Lookup cache for fixed OBIS Identifiers
|
||||
*/
|
||||
private final Map<OBISIdentifier, CosemObjectType> obisLookupTableFixed = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Lookup cache for fixed OBIS Identifiers that has the same id for different data types
|
||||
*/
|
||||
private final Map<OBISIdentifier, List<CosemObjectType>> obisLookupTableMultipleFixed = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Lookup cache for dynamic OBIS Identifiers
|
||||
*/
|
||||
private final Map<OBISIdentifier, CosemObjectType> obisLookupTableDynamic = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Lookup cache for wild card Cosem Object types
|
||||
*/
|
||||
private final List<CosemObjectType> obisWildcardCosemTypeList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new CosemObjectFactory
|
||||
*/
|
||||
public CosemObjectFactory() {
|
||||
/*
|
||||
* Fill lookup tables. There are 3 entities:
|
||||
* - obisLookupTableFixed. This lookup table contains all CosemObjectType with a fixed OBISIdentifier
|
||||
* (i.e. groupA != null && groupB != null && groupC != null).
|
||||
* - obisLookupTableDynamic. This lookup table contains all CosemObjectType with a wildcard OBISIdentifier
|
||||
* (i.e. groupA == null || groupB == null || groupC == null). This lookuptable will be filled
|
||||
* dynamically with unique wildcard OBISIdentifiers when values are received and matches a particular real
|
||||
* device (if the device is changed, this lookupTable must be cleared by removing the corresponding DSMRDevice
|
||||
* Thing from the configuration.
|
||||
* - obisWildCardCosemTypeList. This is the list of all wild card Cosem Object types. Multiple Cosem Object
|
||||
* Types can have the same wild card OBISIdentifer.
|
||||
*
|
||||
* To facilitate autodiscovery the list has all supported CosemObjectTypes. To improve performance once the
|
||||
* correct OBISIdentifier is discovered for a certain OBISMsgType this is added to the obisLookupTableDynamic.
|
||||
*/
|
||||
for (CosemObjectType msgType : CosemObjectType.values()) {
|
||||
if (msgType.obisId.reducedOBISIdentifierIsWildCard()) {
|
||||
obisWildcardCosemTypeList.add(msgType);
|
||||
} else if (msgType.obisId.isConflict()) {
|
||||
obisLookupTableMultipleFixed.computeIfAbsent(msgType.obisId, r -> new ArrayList<>()).add(msgType);
|
||||
} else {
|
||||
obisLookupTableFixed.put(msgType.obisId, msgType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Cosem Object from specified string or null if string couldn't be
|
||||
* parsed correctly or no corresponding Cosem Object was found
|
||||
*
|
||||
* @param obisIdString String containing the OBIS message identifier
|
||||
* @param cosemStringValues String containing Cosem values
|
||||
* @return CosemObject or null if parsing failed
|
||||
*/
|
||||
public @Nullable CosemObject getCosemObject(String obisIdString, String cosemStringValues) {
|
||||
OBISIdentifier obisId;
|
||||
OBISIdentifier reducedObisId;
|
||||
OBISIdentifier reducedObisIdGroupE;
|
||||
|
||||
try {
|
||||
obisId = new OBISIdentifier(obisIdString);
|
||||
reducedObisId = obisId.getReducedOBISIdentifier();
|
||||
reducedObisIdGroupE = obisId.getReducedOBISIdentifierGroupE();
|
||||
} catch (final ParseException pe) {
|
||||
logger.debug("Received invalid OBIS identifier: {}", obisIdString);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.trace("Received obisIdString {}, obisId: {}, values: {}", obisIdString, obisId, cosemStringValues);
|
||||
|
||||
CosemObject cosemObject = null;
|
||||
|
||||
if (obisLookupTableFixed.containsKey(reducedObisId)) {
|
||||
cosemObject = getCosemObjectInternal(obisLookupTableFixed.get(reducedObisId), obisId, cosemStringValues);
|
||||
logger.trace("Found obisId {} in the fixed lookup table", reducedObisId);
|
||||
} else if (obisLookupTableMultipleFixed.containsKey(reducedObisId)) {
|
||||
for (CosemObjectType cosemObjectType : obisLookupTableMultipleFixed.get(reducedObisId)) {
|
||||
cosemObject = getCosemObjectInternal(cosemObjectType, obisId, cosemStringValues);
|
||||
if (cosemObject != null) {
|
||||
logger.trace("Found obisId {} in the fixed lookup table", reducedObisId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (obisLookupTableDynamic.containsKey(reducedObisId)) {
|
||||
logger.trace("Found obisId {} in the dynamic lookup table", reducedObisId);
|
||||
cosemObject = getCosemObjectInternal(obisLookupTableDynamic.get(reducedObisId), obisId, cosemStringValues);
|
||||
} else if (obisLookupTableFixed.containsKey(reducedObisIdGroupE)) {
|
||||
cosemObject = getCosemObjectInternal(obisLookupTableFixed.get(reducedObisIdGroupE), obisId,
|
||||
cosemStringValues);
|
||||
} else {
|
||||
for (CosemObjectType obisMsgType : obisWildcardCosemTypeList) {
|
||||
if (obisMsgType.obisId.equalsWildCard(reducedObisId)) {
|
||||
cosemObject = getCosemObjectInternal(obisMsgType, obisId, cosemStringValues);
|
||||
if (cosemObject != null) {
|
||||
logger.trace("Searched reducedObisId {} in the wild card type list, result: {}", reducedObisId,
|
||||
cosemObject);
|
||||
obisLookupTableDynamic.put(reducedObisId, obisMsgType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cosemObject == null) {
|
||||
logger.debug("Received unknown Cosem Object(OBIS id: {})", obisId);
|
||||
}
|
||||
|
||||
return cosemObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CosemObject from the given type, OBISIdentifier and the values
|
||||
*
|
||||
* @param cosemObjectType the type of the CosemObject
|
||||
* @param obisIdentifier the actual OBISIdentifier how this cosemObjectType is identified
|
||||
* @param cosemStringValues the values of the CosemObject
|
||||
*
|
||||
* @return a CosemObject or null if parsing failed
|
||||
*/
|
||||
private @Nullable CosemObject getCosemObjectInternal(CosemObjectType cosemObjectType, OBISIdentifier obisIdentifier,
|
||||
String cosemStringValues) {
|
||||
CosemObject obj = new CosemObject(cosemObjectType, obisIdentifier);
|
||||
|
||||
try {
|
||||
logger.trace("Parse values for Cosem Object type: {}", cosemObjectType);
|
||||
obj.parseCosemValues(cosemStringValues);
|
||||
|
||||
return obj;
|
||||
} catch (ParseException pe) {
|
||||
logger.trace("Failed to construct Cosem Object for type {}, values: {}", cosemObjectType, cosemStringValues,
|
||||
pe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterConstants;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
|
||||
/**
|
||||
* Enumeration Cosem Object types
|
||||
* <p>
|
||||
* Each Cosem Object type consists of the following attributes:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>OBIS Identifier (reduced form)
|
||||
* <li>List of value descriptors (See {@link CosemValueDescriptor})
|
||||
* <li>List of repeating value descriptors (See {@link CosemValueDescriptor}). Repeating value descriptors will always
|
||||
* be the last descriptors.
|
||||
* </ul>
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Cosem subclasses made into factory classes and introduced quantity type
|
||||
*/
|
||||
public enum CosemObjectType {
|
||||
UNKNOWN(new OBISIdentifier(-1, DSMRMeterConstants.UNKNOWN_CHANNEL, -1, -1, -1, null), CosemString.INSTANCE),
|
||||
|
||||
/* General messages */
|
||||
P1_VERSION_OUTPUT(new OBISIdentifier(1, 3, 0, 2, 8, null), CosemString.INSTANCE),
|
||||
P1_EMUCS_VERSION_OUTPUT(new OBISIdentifier(0, 0, 96, 1, 4, null), CosemString.INSTANCE),
|
||||
P1_TIMESTAMP(new OBISIdentifier(0, 0, 1, 0, 0, null), new CosemDate("")),
|
||||
P1_TEXT_CODE(new OBISIdentifier(0, 0, 96, 13, 1, null), CosemHexString.INSTANCE),
|
||||
P1_TEXT_STRING(new OBISIdentifier(0, 0, 96, 13, 0, null), CosemHexString.INSTANCE),
|
||||
P1_TEXT_STRING_LONG(new OBISIdentifier(0, 0, 96, 13, null, null), CosemHexString.INSTANCE),
|
||||
|
||||
/* Generic Meter Cosem Object types */
|
||||
METER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null), CosemHexString.INSTANCE),
|
||||
METER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null), CosemString.INSTANCE),
|
||||
METER_VALVE_SWITCH_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null), CosemDecimal.INSTANCE),
|
||||
|
||||
/* Electricity Meter */
|
||||
EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 0, 42, 0, 0, null), CosemString.INSTANCE),
|
||||
EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 1, null), CosemHexString.INSTANCE),
|
||||
EMETER_VALUE(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, null, 1, 8, 0, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, null, 1, 8, 1, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, null, 1, 8, 2, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_DELIVERY_TARIFF0_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 0, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_DELIVERY_TARIFF1_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 1, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_DELIVERY_TARIFF2_ANTIFRAUD(new OBISIdentifier(1, null, 15, 8, 2, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, null, 2, 8, 0, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, null, 2, 8, 1, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, null, 2, 8, 2, null), CosemQuantity.KILO_WATT_HOUR),
|
||||
EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, null, 96, 14, 0, null), CosemString.INSTANCE),
|
||||
EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, null, 15, 7, 0, null), CosemQuantity.WATT),
|
||||
EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 0, 1, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 0, 2, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_TRESHOLD_A_V2_1(new OBISIdentifier(1, 0, 17, 0, 0, null), CosemQuantity.AMPERE),
|
||||
EMETER_TRESHOLD_A(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.AMPERE),
|
||||
EMETER_FUSE_THRESHOLD_A(new OBISIdentifier(1, 0, 31, 4, 0, null), CosemQuantity.AMPERE),
|
||||
EMETER_TRESHOLD_KWH(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemQuantity.KILO_WATT),
|
||||
EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 0, 96, 3, 10, null), CosemDecimal.INSTANCE),
|
||||
EMETER_SWITCH_POSITION(new OBISIdentifier(0, 0, 96, 3, 10, null), CosemDecimal.INSTANCE),
|
||||
EMETER_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 21, null), CosemDecimal.INSTANCE),
|
||||
EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 9, null), CosemDecimal.INSTANCE),
|
||||
EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 0, 99, 97, 0, null), 2, new CosemDecimal("entries"),
|
||||
new CosemString("obisId"),
|
||||
/* Next 2 descriptors are repeating */
|
||||
CosemDate.INSTANCE, new CosemQuantity<>(SmartHomeUnits.SECOND, "duration")),
|
||||
EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 0, 32, 32, 0, null), CosemDecimal.INSTANCE),
|
||||
EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 0, 52, 32, 0, null), CosemDecimal.INSTANCE),
|
||||
EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 0, 72, 32, 0, null), CosemDecimal.INSTANCE),
|
||||
EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 0, 32, 36, 0, null), CosemDecimal.INSTANCE),
|
||||
EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 0, 52, 36, 0, null), CosemDecimal.INSTANCE),
|
||||
EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 0, 72, 36, 0, null), CosemDecimal.INSTANCE),
|
||||
EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 0, 31, 7, 0, null), CosemQuantity.AMPERE),
|
||||
EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 0, 51, 7, 0, null), CosemQuantity.AMPERE),
|
||||
EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 0, 71, 7, 0, null), CosemQuantity.AMPERE),
|
||||
EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 21, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 41, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_INSTANT_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 61, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_INSTANT_POWER_PRODUCTION_L1(new OBISIdentifier(1, 0, 22, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_INSTANT_POWER_PRODUCTION_L2(new OBISIdentifier(1, 0, 42, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 0, 62, 7, 0, null), CosemQuantity.KILO_WATT),
|
||||
EMETER_INSTANT_VOLTAGE_L1(new OBISIdentifier(1, 0, 32, 7, 0, null), CosemQuantity.VOLT),
|
||||
EMETER_INSTANT_VOLTAGE_L2(new OBISIdentifier(1, 0, 52, 7, 0, null), CosemQuantity.VOLT),
|
||||
EMETER_INSTANT_VOLTAGE_L3(new OBISIdentifier(1, 0, 72, 7, 0, null), CosemQuantity.VOLT),
|
||||
|
||||
/* Gas Meter */
|
||||
GMETER_EQUIPMENT_IDENTIFIER_V2(new OBISIdentifier(7, 0, 0, 0, 0, null), CosemString.INSTANCE),
|
||||
GMETER_24H_DELIVERY_V2(new OBISIdentifier(7, 0, 23, 1, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
|
||||
GMETER_24H_DELIVERY_COMPENSATED_V2(new OBISIdentifier(7, 0, 23, 2, 0, null), CosemQuantity.CUBIC_METRE,
|
||||
CosemDate.INSTANCE),
|
||||
GMETER_LAST_VALUE(new OBISIdentifier(0, null, 24, 2, 3, null), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
|
||||
GMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemDate.INSTANCE, // Time stamp off the reading
|
||||
new CosemString("val1"), // Specification is not clear what this value is
|
||||
new CosemDecimal("val2"), // Specification is not clear what this value is
|
||||
new CosemDecimal("val3"), // Specification is not clear what this value is
|
||||
new CosemString("obisId"), // String containing a OBIS Identifier
|
||||
new CosemString("unit"), // String containing the type (m3)
|
||||
CosemDecimal.INSTANCE),
|
||||
GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 0, 96, 3, 10, null), CosemDecimal.INSTANCE),
|
||||
GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 0, 24, 4, 0, null), CosemDecimal.INSTANCE),
|
||||
|
||||
/* Heating Meter */
|
||||
HMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(5, 0, 0, 0, 0, null), CosemString.INSTANCE),
|
||||
HMETER_VALUE_V2(new OBISIdentifier(5, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
|
||||
|
||||
/* Cooling Meter */
|
||||
CMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(6, 0, 0, 0, 0, null), CosemString.INSTANCE),
|
||||
CMETER_VALUE_V2(new OBISIdentifier(6, 0, 1, 0, 0, null), CosemQuantity.GIGA_JOULE, CosemDate.INSTANCE),
|
||||
|
||||
/* Water Meter */
|
||||
WMETER_EQUIPMENT_IDENTIFIER_V2_2(new OBISIdentifier(8, 0, 0, 0, 0, null), CosemString.INSTANCE),
|
||||
WMETER_VALUE_V2(new OBISIdentifier(8, 0, 1, 0, 0, null), CosemQuantity.CUBIC_METRE, CosemDate.INSTANCE),
|
||||
WMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.CUBIC_METRE),
|
||||
|
||||
/* M3 Meter (Gas, Water) */
|
||||
M3METER_VALUE(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.CUBIC_METRE),
|
||||
|
||||
/* GJ Meter (Heating, Cooling) */
|
||||
GJMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemQuantity.GIGA_JOULE),
|
||||
GJMETER_VALUE_V4(new OBISIdentifier(0, null, 24, 2, 1, null), CosemDate.INSTANCE, CosemQuantity.GIGA_JOULE),
|
||||
|
||||
/* Generic Meter (DSMR v3 only) */
|
||||
GENMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null), CosemDecimal.INSTANCE),
|
||||
|
||||
/* Additional Luxembourgish Smarty Electricity */
|
||||
EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 3, 8, 0, null), CosemQuantity.KILO_VAR_HOUR),
|
||||
EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q(new OBISIdentifier(1, null, 4, 8, 0, null), 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, 0, 3, 7, 0, null), CosemDecimal.INSTANCE_WITH_UNITS),
|
||||
EMETER_ACTUAL_REACTIVE_PRODUCTION(new OBISIdentifier(1, 0, 4, 7, 0, null), CosemDecimal.INSTANCE_WITH_UNITS),
|
||||
EMETER_ACTIVE_THRESHOLD_SMAX(new OBISIdentifier(0, 0, 17, 0, 0, null, true), CosemDecimal.INSTANCE_WITH_UNITS),
|
||||
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 23, 7, 0, null), CosemQuantity.KILO_VAR),
|
||||
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 43, 7, 0, null), CosemQuantity.KILO_VAR),
|
||||
EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 63, 7, 0, null), CosemQuantity.KILO_VAR),
|
||||
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1(new OBISIdentifier(1, 0, 24, 7, 0, null), CosemQuantity.KILO_VAR),
|
||||
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2(new OBISIdentifier(1, 0, 44, 7, 0, null), CosemQuantity.KILO_VAR),
|
||||
EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3(new OBISIdentifier(1, 0, 64, 7, 0, null), CosemQuantity.KILO_VAR);
|
||||
|
||||
/** OBIS reduced identifier */
|
||||
public final OBISIdentifier obisId;
|
||||
|
||||
/** COSEM value descriptors */
|
||||
private final List<CosemValueDescriptor<?>> descriptors;
|
||||
private final List<CosemValueDescriptor<?>> repeatingDescriptors;
|
||||
|
||||
/**
|
||||
* Constructs a new CosemObjectType
|
||||
*
|
||||
* @param obisId {@link OBISIdentifier} containing the obisIdentifier for CosemObjectType
|
||||
* @param descriptors variable parameter list of {@link CosemValueDescriptor}
|
||||
*/
|
||||
CosemObjectType(OBISIdentifier obisId, CosemValueDescriptor<?>... descriptors) {
|
||||
this(obisId, 0, descriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CosemObjectType
|
||||
*
|
||||
* @param obisId {@link OBISIdentifier} containing the obisIdentifier for CosemObjectType
|
||||
* @param nrOfRepeatingDescriptors nr of repeating descriptors (this are the last n descriptors in the variable list
|
||||
* descriptors)
|
||||
* @param descriptors variable parameter list of {@link CosemValueDescriptor}
|
||||
*/
|
||||
CosemObjectType(OBISIdentifier obisId, int nrOfRepeatingDescriptors, CosemValueDescriptor<?>... descriptors) {
|
||||
this.obisId = obisId;
|
||||
if (nrOfRepeatingDescriptors == 0) {
|
||||
this.descriptors = Arrays.asList(descriptors);
|
||||
this.repeatingDescriptors = Collections.emptyList();
|
||||
} else {
|
||||
List<CosemValueDescriptor<?>> allDescriptors = Arrays.asList(descriptors);
|
||||
|
||||
/*
|
||||
* The last nrOfRepeatingDescriptors CosemValueDescriptor will go into the repeatingDescriptor list.
|
||||
* The other descriptors will got into the regular list
|
||||
*/
|
||||
this.descriptors = allDescriptors.subList(0, allDescriptors.size() - nrOfRepeatingDescriptors);
|
||||
this.repeatingDescriptors = allDescriptors.subList(this.descriptors.size(),
|
||||
this.descriptors.size() + nrOfRepeatingDescriptors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link CosemValueDescriptor} for the specified index.
|
||||
* If the list contains repeating descriptors the specified index will mapped onto the repeating list
|
||||
*
|
||||
* e.g. If the list contains 4 descriptors and the last 2 are repeating, idx=6 will return the 4th descriptor.
|
||||
*
|
||||
* The idx is < 0 or outside a non-repeating descriptorslist size null is returned
|
||||
*
|
||||
* @param idx the CosemValueDescriptor to return
|
||||
* @return the CosemValueDescriptor or null if not found.
|
||||
*/
|
||||
public 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();
|
||||
|
||||
CosemValueDescriptor<?> descriptor = repeatingDescriptors.get(repeatingIdx);
|
||||
|
||||
/* The repeating descriptor must have a specific channel */
|
||||
int repeatCount = (idx - descriptors.size()) / repeatingDescriptors.size();
|
||||
|
||||
return new SimpleEntry<>(descriptor.getChannelId() + repeatCount, descriptor);
|
||||
} else if (idx < descriptors.size()) {
|
||||
CosemValueDescriptor<?> descriptor = descriptors.get(idx);
|
||||
|
||||
return new SimpleEntry<>(descriptor.getChannelId(), descriptor);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this CosemObjectType supports the requested number of values.
|
||||
*
|
||||
* Note that for repeating list the number of values must match the repeating pattern.
|
||||
*
|
||||
* So if the list contains 4 values and the last 2 are repeating, nrOfValues = 6 will return true,
|
||||
* however nrOfvalues = 7 will return false (only 4, 6, 8, etc... is allowed)
|
||||
*
|
||||
* @param nrOfValues number of values to check.
|
||||
*
|
||||
* @return true if this CosemObjectType support the requested number of values, false otherwise.
|
||||
*/
|
||||
public boolean supportsNrOfValues(int nrOfValues) {
|
||||
if (repeatingDescriptors.size() == 0) {
|
||||
return nrOfValues == descriptors.size();
|
||||
} else {
|
||||
/* There are repeating descriptors */
|
||||
return ((nrOfValues - descriptors.size()) % repeatingDescriptors.size()) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.measure.Quantity;
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.ElectricCurrent;
|
||||
import javax.measure.quantity.ElectricPotential;
|
||||
import javax.measure.quantity.Energy;
|
||||
import javax.measure.quantity.Power;
|
||||
import javax.measure.quantity.Volume;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
|
||||
/**
|
||||
* {@link CosemQuantity} represents a value with a unit.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*
|
||||
* @param <Q> The {@link Quantity} type of the unit of this class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class CosemQuantity<Q extends @Nullable Quantity<Q>> extends CosemValueDescriptor<QuantityType<Q>> {
|
||||
|
||||
public static final CosemQuantity<ElectricCurrent> AMPERE = new CosemQuantity<>(SmartHomeUnits.AMPERE);
|
||||
public static final CosemQuantity<Volume> CUBIC_METRE = new CosemQuantity<>(SIUnits.CUBIC_METRE);
|
||||
public static final CosemQuantity<Energy> GIGA_JOULE = new CosemQuantity<>(MetricPrefix.GIGA(SmartHomeUnits.JOULE));
|
||||
public static final CosemQuantity<Power> KILO_WATT = new CosemQuantity<>(MetricPrefix.KILO(SmartHomeUnits.WATT));
|
||||
public static final CosemQuantity<Energy> KILO_WATT_HOUR = new CosemQuantity<>(SmartHomeUnits.KILOWATT_HOUR);
|
||||
public static final CosemQuantity<ElectricPotential> VOLT = new CosemQuantity<>(SmartHomeUnits.VOLT);
|
||||
public static final CosemQuantity<Power> WATT = new CosemQuantity<>(SmartHomeUnits.WATT);
|
||||
public static final CosemQuantity<Power> KILO_VAR = new CosemQuantity<>(SmartHomeUnits.KILOVAR);
|
||||
public static final CosemQuantity<Energy> KILO_VAR_HOUR = new CosemQuantity<>(SmartHomeUnits.KILOVAR_HOUR);
|
||||
|
||||
/**
|
||||
* Pattern to convert a cosem value to a value that can be parsed by {@link QuantityType}.
|
||||
* The specification states that the delimiter between the value and the unit is a '*'-character.
|
||||
* We have seen on the Kaifa 0025 meter that both '*' and the '_' character are used.
|
||||
*
|
||||
* On the Kampstrup 162JxC in some CosemValues the separator is missing
|
||||
*
|
||||
* The above quirks are supported
|
||||
*
|
||||
* We also support unit that do not follow the exact case.
|
||||
*/
|
||||
private static final Pattern COSEM_VALUE_WITH_UNIT_PATTERN = Pattern.compile("^([\\d\\.]+)[\\*_]?(.+)$",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* Unit of this CosemValue
|
||||
*/
|
||||
private final Unit<Q> unit;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CosemDouble}.
|
||||
*
|
||||
* @param unit the unit of the value
|
||||
*/
|
||||
private CosemQuantity(Unit<Q> unit) {
|
||||
this(unit, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param unit Unit of this CosemQuantity instance
|
||||
* @param channelId the channel for this CosemValueDescriptor
|
||||
*/
|
||||
public CosemQuantity(Unit<Q> unit, String channelId) {
|
||||
super(channelId);
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a String value (that represents a value with a unit) to a {@link QuantityType} object.
|
||||
*
|
||||
* @param cosemValue the value to parse
|
||||
* @return {@link QuanitytType} on success
|
||||
* @throws ParseException in case unit doesn't match.
|
||||
*/
|
||||
@Override
|
||||
protected QuantityType<Q> getStateValue(String cosemValue) throws ParseException {
|
||||
try {
|
||||
QuantityType<Q> qt = new QuantityType<>(prepare(cosemValue));
|
||||
|
||||
if (!unit.equals(qt.getUnit())) {
|
||||
throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0);
|
||||
}
|
||||
return qt;
|
||||
} catch (IllegalArgumentException nfe) {
|
||||
throw new ParseException("Failed to parse value '" + cosemValue + "' as unit " + unit, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if COSEM value has a unit, check and parse the value. We assume here numbers (float or integers)
|
||||
* The specification states that the delimiter between the value and the unit is a '*'-character.
|
||||
* We have seen on the Kaifa 0025 meter that both '*' and the '_' character are used.
|
||||
*
|
||||
* On the Kampstrup 162JxC in some CosemValues the separator is missing. This
|
||||
*
|
||||
* The above quirks are supported
|
||||
*
|
||||
* 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³"));
|
||||
|
||||
return matcher.find() ? matcher.group(1) + ' ' + matcher.group(2) : cosemValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
|
||||
/**
|
||||
* {@link CosemString} represents a string value.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Class now a factory instead of data containing class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class CosemString extends CosemValueDescriptor<StringType> {
|
||||
|
||||
public static final CosemString INSTANCE = new CosemString();
|
||||
|
||||
private CosemString() {
|
||||
}
|
||||
|
||||
public CosemString(String channelId) {
|
||||
super(channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a String value to a {@link StringType}.
|
||||
*
|
||||
* @param cosemValue the value to parse
|
||||
* @return {@link StringType} representing the value of the cosem value
|
||||
*/
|
||||
@Override
|
||||
protected StringType getStateValue(String cosemValue) {
|
||||
return new StringType(cosemValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* This CosemValueDescriptor provides meta data for a CosemValue
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class CosemValueDescriptor<S extends State> {
|
||||
|
||||
/**
|
||||
* String describing the channel on which this value descriptor is available.
|
||||
*/
|
||||
private final String channelId;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CosemValueDescriptor} with no unit and a default channel.
|
||||
*/
|
||||
public CosemValueDescriptor() {
|
||||
this("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CosemValueDescriptor}.
|
||||
*
|
||||
* @param channelId the channel for this CosemValueDescriptor
|
||||
*/
|
||||
public CosemValueDescriptor(String channelId) {
|
||||
this.channelId = channelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string value to the {@link State} value
|
||||
*
|
||||
* @param CosemValue the Cosem value to parse
|
||||
* @return S the {@link State} object instance of the Cosem value
|
||||
* @throws ParseException if parsing failed
|
||||
*/
|
||||
protected abstract S getStateValue(String cosemValue) throws ParseException;
|
||||
|
||||
/**
|
||||
* Returns the channel id for this {@link CosemValueDescriptor}
|
||||
*
|
||||
* @return the channel identifier
|
||||
*/
|
||||
public String getChannelId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns String representation of this {@link CosemValueDescriptor}
|
||||
*
|
||||
* @return String representation of this {@link CosemValueDescriptor}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CosemValueDescriptor[channel=" + channelId + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.cosem;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Class representing an OBISIdentifier
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Fix bug in regex pattern.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OBISIdentifier {
|
||||
/**
|
||||
* String representing a-b:c.d.e.f OBIS ID
|
||||
*/
|
||||
private static final String OBISID_REGEX = "((\\d+)\\-)?((\\d+):)?((\\d+)\\.)(\\d+)(\\.(\\d+))?(.(\\d+))?";
|
||||
|
||||
/**
|
||||
* OBIS ID pattern
|
||||
*/
|
||||
private static final Pattern OBIS_ID_PATTERN = Pattern.compile(OBISID_REGEX);
|
||||
|
||||
/* the six individual group values of the OBIS ID */
|
||||
private int groupA;
|
||||
private @Nullable Integer groupB;
|
||||
private int groupC;
|
||||
private int groupD;
|
||||
private @Nullable Integer groupE;
|
||||
private @Nullable Integer groupF;
|
||||
|
||||
private boolean conflict;
|
||||
|
||||
/**
|
||||
* Constructs a new OBIS Identifier (A-B:C.D.E.F)
|
||||
*
|
||||
* @param groupA A value
|
||||
* @param groupB B value
|
||||
* @param groupC C value
|
||||
* @param groupD D value
|
||||
* @param groupE E value
|
||||
* @param groupF F value
|
||||
*/
|
||||
public OBISIdentifier(int groupA, @Nullable Integer groupB, int groupC, int groupD, @Nullable Integer groupE,
|
||||
@Nullable Integer groupF) {
|
||||
this(groupA, groupB, groupC, groupD, groupE, groupF, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new OBIS Identifier (A-B:C.D.E.F)
|
||||
*
|
||||
* @param groupA A value
|
||||
* @param groupB B value
|
||||
* @param groupC C value
|
||||
* @param groupD D value
|
||||
* @param groupE E value
|
||||
* @param groupF F value
|
||||
* @param conflict if true indicates this OBIS Identifier is used for different types of data.
|
||||
*/
|
||||
public OBISIdentifier(int groupA, @Nullable Integer groupB, int groupC, int groupD, @Nullable Integer groupE,
|
||||
@Nullable Integer groupF, boolean conflict) {
|
||||
this.groupA = groupA;
|
||||
this.groupB = groupB;
|
||||
this.groupC = groupC;
|
||||
this.groupD = groupD;
|
||||
this.groupE = groupE;
|
||||
this.groupF = groupF;
|
||||
this.conflict = conflict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link OBISIdentifier} of the specified String
|
||||
*
|
||||
* @param obisIDString the OBIS String ID
|
||||
* @throws ParseException if obisIDString is not a valid OBIS Identifier
|
||||
*/
|
||||
public OBISIdentifier(String obisIDString) throws ParseException {
|
||||
Matcher m = OBIS_ID_PATTERN.matcher(obisIDString);
|
||||
|
||||
if (m.matches()) {
|
||||
// Optional value A
|
||||
if (m.group(2) != null) {
|
||||
this.groupA = Integer.parseInt(m.group(2));
|
||||
}
|
||||
|
||||
// Optional value B
|
||||
if (m.group(4) != null) {
|
||||
this.groupB = Integer.valueOf(m.group(4));
|
||||
}
|
||||
|
||||
// Required value C & D
|
||||
this.groupC = Integer.parseInt(m.group(6));
|
||||
this.groupD = Integer.parseInt(m.group(7));
|
||||
|
||||
// Optional value E
|
||||
if (m.group(9) != null) {
|
||||
this.groupE = Integer.valueOf(m.group(9));
|
||||
}
|
||||
|
||||
// Optional value F
|
||||
if (m.group(11) != null) {
|
||||
this.groupF = Integer.valueOf(m.group(11));
|
||||
}
|
||||
} else {
|
||||
throw new ParseException("Invalid OBIS identifier:" + obisIDString, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConflict() {
|
||||
return conflict;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the groupA
|
||||
*/
|
||||
public int getGroupA() {
|
||||
return groupA;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the groupB
|
||||
*/
|
||||
public @Nullable Integer getGroupB() {
|
||||
return groupB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the groupC
|
||||
*/
|
||||
public int getGroupC() {
|
||||
return groupC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the groupD
|
||||
*/
|
||||
public int getGroupD() {
|
||||
return groupD;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the groupE
|
||||
*/
|
||||
public @Nullable Integer getGroupE() {
|
||||
return groupE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the groupF
|
||||
*/
|
||||
public @Nullable Integer getGroupF() {
|
||||
return groupF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return groupA + "-" + (groupB == null ? "" : (groupB + ":")) + groupC + "." + groupD
|
||||
+ (groupE == null ? "" : ("." + groupE)) + (groupF == null ? "" : ("*" + groupF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not both {@link OBISIdentifier} are exact equal (all identifiers match).
|
||||
*
|
||||
* If wild card matching is needed (since some fields are null in case of a wildcard) use
|
||||
* {@link #equalsWildCard(OBISIdentifier)} instead
|
||||
*
|
||||
* @return true if both OBISIdentifiers match, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
OBISIdentifier o;
|
||||
if (other != null && other instanceof OBISIdentifier) {
|
||||
o = (OBISIdentifier) other;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
boolean result = true;
|
||||
|
||||
result &= groupA == o.groupA;
|
||||
if (groupB != null && o.groupB != null) {
|
||||
result &= (groupB.equals(o.groupB));
|
||||
} else if (!(groupB == null && o.groupB == null)) {
|
||||
result = false;
|
||||
}
|
||||
result &= groupC == o.groupC;
|
||||
result &= groupD == o.groupD;
|
||||
if (groupE != null && o.groupE != null) {
|
||||
result &= groupE.equals(o.groupE);
|
||||
} else if (!(groupE == null && o.groupE == null)) {
|
||||
result = false;
|
||||
}
|
||||
if (groupF != null && o.groupF != null) {
|
||||
result &= (groupF.equals(o.groupF));
|
||||
} else if (!(groupF == null && o.groupF == null)) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this OBIS Identifier and the other identifier equals taking the wildcards into account
|
||||
*
|
||||
* @param o OBISIdentifier to compare to
|
||||
*
|
||||
* @return true if identifiers match fully or against a wildcard, false otherwise
|
||||
*/
|
||||
public boolean equalsWildCard(OBISIdentifier o) {
|
||||
boolean result = true;
|
||||
|
||||
result &= groupA == o.groupA;
|
||||
if (groupB != null && o.groupB != null) {
|
||||
result &= (groupB.equals(o.groupB));
|
||||
}
|
||||
result &= groupC == o.groupC;
|
||||
result &= groupD == o.groupD;
|
||||
if (groupE != null && o.groupE != null) {
|
||||
result &= (groupE.equals(o.groupE));
|
||||
}
|
||||
if (groupF != null && o.groupF != null) {
|
||||
result &= (groupF.equals(o.groupF));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(groupA, (groupB != null ? groupB : 0), groupC, groupD, (groupE != null ? groupE : 0),
|
||||
(groupF != null ? groupF : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an reduced OBIS Identifier. This means group F is set to null
|
||||
* (.i.e. not applicable)
|
||||
*
|
||||
* @return reduced OBIS Identifier
|
||||
*/
|
||||
public OBISIdentifier getReducedOBISIdentifier() {
|
||||
return new OBISIdentifier(groupA, groupB, groupC, groupD, groupE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an reduced OBIS Identifier with both group E and F is set to null
|
||||
* (.i.e. not applicable)
|
||||
*
|
||||
* @return reduced OBIS Identifier
|
||||
*/
|
||||
public OBISIdentifier getReducedOBISIdentifierGroupE() {
|
||||
return new OBISIdentifier(groupA, groupB, groupC, groupD, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the reduced OBIS Identifier is a wildcard identifier (meaning groupA groupB or groupC is
|
||||
* null)
|
||||
* Note that the DSMR specification does not use groupF so this is implemented always as a wildcard.
|
||||
* To distinguish wildcard from non wildcard OBISIdentifiers, groupF is ignored.
|
||||
*
|
||||
* @return true if the reducedOBISIdentifier is a wildcard identifier, false otherwise.
|
||||
*/
|
||||
public boolean reducedOBISIdentifierIsWildCard() {
|
||||
return groupB == null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.p1telegram;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* CRC16 implementation.
|
||||
*
|
||||
* This class supports 2 ways of using.
|
||||
* The first is to give a complete byte array and get the CRC16 {@link #calculate(byte[], int)}
|
||||
* The second variant the instance stores the actual CRC16 value giving the possibility to add bytes in subsequent
|
||||
* calls to {@link #processByte(byte)}
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CRC16 {
|
||||
|
||||
public enum Polynom {
|
||||
CRC16_IBM(0xA001), // standard CRC-16 x16+x15+x2+1 (CRC-16-IBM)
|
||||
CRC16_IBM_REVERSE(0xC002), // standard reverse x16+x14+x+1 (CRC-16-IBM)
|
||||
CRC16_CCIT(0x8408), // CCITT/SDLC/HDLC X16+X12+X5+1 (CRC-16-CCITT)
|
||||
// The initial CRC value is usually 0xFFFF and the result is complemented.
|
||||
CRC16_CCIT_REVERSE(0x8810), // CCITT reverse X16+X11+X4+1 (CRC-16-CCITT)
|
||||
CRC16_LRCC(0x8000); // LRCC-16 X16+1
|
||||
|
||||
/**
|
||||
* the polynom to use
|
||||
*/
|
||||
public final int polynom;
|
||||
|
||||
/**
|
||||
* Constructs a new Polynom using the required polynom value
|
||||
*
|
||||
* @param polynom
|
||||
*/
|
||||
private Polynom(int polynom) {
|
||||
this.polynom = polynom;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The cached CRC16 table based on the requested CRC16 variant
|
||||
*/
|
||||
private short[] crcTable;
|
||||
|
||||
/**
|
||||
* The current crcValue
|
||||
*/
|
||||
private int crcValue;
|
||||
|
||||
/**
|
||||
* Constructs a new CRC16 object using the requested polynom
|
||||
*
|
||||
* @param polynom the CRC16 polynom to use
|
||||
*/
|
||||
public CRC16(Polynom polynom) {
|
||||
crcTable = genCrc16Table(polynom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a CRC16 based on the specified data and the initial CRCvalue
|
||||
*
|
||||
* @param data byes to calculate the CRC16 for
|
||||
* @param initialCrcValue initial CRC value
|
||||
* @return the CRC16 value
|
||||
*/
|
||||
public int calculate(byte[] data, int initialCrcValue) {
|
||||
int crc = initialCrcValue;
|
||||
for (int p = 0; p < data.length; p++) {
|
||||
crc = (crc >> 8) ^ (crcTable[(crc & 0xFF) ^ (data[p] & 0xFF)] & 0xFFFF);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the CRC16 code to 0.
|
||||
*/
|
||||
public void initialize() {
|
||||
initialize(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the CRC16 code to the given initial value.
|
||||
*
|
||||
* @param initialCrcValue the initial value to set.
|
||||
*/
|
||||
public void initialize(int initialCrcValue) {
|
||||
crcValue = initialCrcValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processed a single byte and updates the internal CRC16 value
|
||||
*
|
||||
* @param b the byte to process
|
||||
*/
|
||||
public void processByte(byte b) {
|
||||
crcValue = (crcValue >> 8) ^ (crcTable[(crcValue & 0xFF) ^ (b & 0xFF)] & 0xFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current CRC16 code
|
||||
*
|
||||
* @return integer containing the current CRC16 code.
|
||||
*/
|
||||
public int getCurrentCRCCode() {
|
||||
return crcValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the CRC16 table
|
||||
*
|
||||
* @param polynom the polynonm to use
|
||||
*
|
||||
* @return the generated CRC16 table
|
||||
*/
|
||||
private short[] genCrc16Table(Polynom polynom) {
|
||||
short[] table = new short[256];
|
||||
for (int x = 0; x < 256; x++) {
|
||||
int w = x;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((w & 1) != 0) {
|
||||
w = (w >> 1) ^ polynom.polynom;
|
||||
} else {
|
||||
w = w >> 1;
|
||||
}
|
||||
}
|
||||
table[x] = (short) w;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.p1telegram;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
|
||||
/**
|
||||
* Data class containing a Telegram with CosemObjects and TelegramState and if in lenient mode also the raw telegram
|
||||
* data.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class P1Telegram {
|
||||
|
||||
/**
|
||||
* The TelegramState described the meta data of the P1Telegram
|
||||
*/
|
||||
public enum TelegramState {
|
||||
/**
|
||||
* OK. Telegram was successful received and CRC16 checksum is verified (CRC16 only for DSMR V4 and up)
|
||||
*/
|
||||
OK("P1 telegram received OK"),
|
||||
/**
|
||||
* CRC_ERROR. CRC16 checksum failed (only DSMR V4 and up)
|
||||
*/
|
||||
CRC_ERROR("CRC checksum failed for received P1 telegram"),
|
||||
/**
|
||||
* DATA_CORRUPTION. The P1 telegram has syntax errors.
|
||||
*/
|
||||
DATA_CORRUPTION("Received P1 telegram is corrupted"),
|
||||
/**
|
||||
* P1TelegramListener. The smarty telegram was successful received but could not be decoded because of an
|
||||
* invalid
|
||||
* encryption key.
|
||||
*/
|
||||
INVALID_ENCRYPTION_KEY("Failed to decrypt P1 telegram due to invalid encryption key");
|
||||
|
||||
/**
|
||||
* public accessible state details
|
||||
*/
|
||||
public final String stateDetails;
|
||||
|
||||
/**
|
||||
* Constructs a new TelegramState enum
|
||||
*
|
||||
* @param stateDetails String containing the details of this TelegramState
|
||||
*/
|
||||
private TelegramState(String stateDetails) {
|
||||
this.stateDetails = stateDetails;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<CosemObject> cosemObjects;
|
||||
private final TelegramState telegramState;
|
||||
private final String rawTelegram;
|
||||
private final List<Entry<String, String>> unknownCosemObjects;
|
||||
|
||||
public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState) {
|
||||
this(cosemObjects, telegramState, "", Collections.emptyList());
|
||||
}
|
||||
|
||||
public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState, String rawTelegram,
|
||||
List<Entry<String, String>> unknownCosemObjects) {
|
||||
this.cosemObjects = cosemObjects;
|
||||
this.telegramState = telegramState;
|
||||
this.rawTelegram = rawTelegram;
|
||||
this.unknownCosemObjects = unknownCosemObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of CosemObjects
|
||||
*/
|
||||
public List<CosemObject> getCosemObjects() {
|
||||
return cosemObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The raw telegram data.
|
||||
*/
|
||||
public String getRawTelegram() {
|
||||
return rawTelegram;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The state of the telegram
|
||||
*/
|
||||
public TelegramState getTelegramState() {
|
||||
return telegramState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of CosemObject found in the telegram but not known to the binding
|
||||
*/
|
||||
public List<Entry<String, String>> getUnknownCosemObjects() {
|
||||
return unknownCosemObjects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.p1telegram;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Interface for receiving CosemObjects that come from a P1 Telegram
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored interface to use single data object
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface P1TelegramListener {
|
||||
|
||||
/**
|
||||
* Callback on received telegram.
|
||||
*
|
||||
* @param telegram The received telegram
|
||||
*/
|
||||
public void telegramReceived(P1Telegram telegram);
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.p1telegram;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectFactory;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link P1TelegramParser} is a class that will read P1-port data create a full P1
|
||||
* telegram.
|
||||
*
|
||||
* Data can be parsed in chunks. If a full P1 telegram is received, listeners are notified
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Removed asynchronous call and some clean up
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class P1TelegramParser implements TelegramParser {
|
||||
|
||||
/**
|
||||
* State of the parser
|
||||
*/
|
||||
private enum State {
|
||||
/** Wait for the '/' character */
|
||||
WAIT_FOR_START,
|
||||
/** '/' character seen */
|
||||
HEADER,
|
||||
/** Waiting for the header to end with a CR & LF */
|
||||
CRLF,
|
||||
/** Handling OBIS Identifier */
|
||||
DATA_OBIS_ID,
|
||||
/** Parsing OBIS value */
|
||||
DATA_OBIS_VALUE,
|
||||
/** OBIS value end seen ')' */
|
||||
DATA_OBIS_VALUE_END,
|
||||
/** Parsing CRC value following '!' */
|
||||
CRC_VALUE
|
||||
}
|
||||
|
||||
/**
|
||||
* Pattern for the CRC-code
|
||||
*/
|
||||
private static final String CRC_PATTERN = "[0-9A-Z]{4}";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(P1TelegramParser.class);
|
||||
|
||||
/* internal state variables */
|
||||
|
||||
/**
|
||||
* current obisId buffer.
|
||||
*/
|
||||
private final StringBuilder obisId = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Current cosem object values buffer.
|
||||
*/
|
||||
private final StringBuilder obisValue = new StringBuilder();
|
||||
|
||||
/**
|
||||
* In lenient mode store raw data and log when a complete message is received.
|
||||
*/
|
||||
private final StringBuilder rawData = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Current crc value read.
|
||||
*/
|
||||
private final StringBuilder crcValue = new StringBuilder();
|
||||
|
||||
/**
|
||||
* CRC calculation helper
|
||||
*/
|
||||
private final CRC16 crc;
|
||||
|
||||
/**
|
||||
* Current state of the P1 telegram parser
|
||||
*/
|
||||
private volatile State state = State.WAIT_FOR_START;
|
||||
|
||||
/**
|
||||
* Work in lenient mode (more fault tolerant)
|
||||
*/
|
||||
private volatile boolean lenientMode;
|
||||
|
||||
/**
|
||||
* Current telegram state
|
||||
*/
|
||||
private volatile TelegramState telegramState;
|
||||
|
||||
/**
|
||||
* CosemObjectFactory helper class
|
||||
*/
|
||||
private final CosemObjectFactory factory;
|
||||
|
||||
/**
|
||||
* Received Cosem Objects in the P1Telegram that is currently received
|
||||
*/
|
||||
private final List<CosemObject> cosemObjects = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of Cosem Object values that are not known to this binding.
|
||||
*/
|
||||
private final List<Entry<String, String>> unknownCosemObjects = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Listener for new P1 telegrams
|
||||
*/
|
||||
private final P1TelegramListener telegramListener;
|
||||
|
||||
/**
|
||||
* Creates a new P1TelegramParser
|
||||
*
|
||||
* @param telegramListener
|
||||
*/
|
||||
public P1TelegramParser(P1TelegramListener telegramListener) {
|
||||
this.telegramListener = telegramListener;
|
||||
|
||||
factory = new CosemObjectFactory();
|
||||
state = State.WAIT_FOR_START;
|
||||
crc = new CRC16(CRC16.Polynom.CRC16_IBM);
|
||||
telegramState = TelegramState.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses data. If a complete message is received the message will be passed to the telegramListener.
|
||||
*
|
||||
* @param data byte data to parse
|
||||
* @param length number of bytes to parse
|
||||
*/
|
||||
@Override
|
||||
public void parse(byte[] data, int length) {
|
||||
if (lenientMode || logger.isTraceEnabled()) {
|
||||
String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8);
|
||||
|
||||
if (lenientMode) {
|
||||
rawData.append(rawBlock);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Raw data: {}, Parser state entering parseData: {}", rawBlock, state);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = (char) data[i];
|
||||
|
||||
switch (state) {
|
||||
case WAIT_FOR_START:
|
||||
if (c == '/') {
|
||||
setState(State.HEADER);
|
||||
}
|
||||
break;
|
||||
case HEADER:
|
||||
if (c == '\r') {
|
||||
setState(State.CRLF);
|
||||
}
|
||||
break;
|
||||
case CRLF:
|
||||
if (Character.isWhitespace(c)) { // NOPMD EmptyIfStmt
|
||||
// do nothing
|
||||
} else if (Character.isDigit(c)) {
|
||||
setState(State.DATA_OBIS_ID);
|
||||
} else {
|
||||
handleUnexpectedCharacter(c);
|
||||
|
||||
setState(State.WAIT_FOR_START);
|
||||
}
|
||||
break;
|
||||
case DATA_OBIS_ID:
|
||||
if (Character.isWhitespace(c)) { // NOPMD EmptyIfStmt
|
||||
// ignore
|
||||
} else if (Character.isDigit(c) || c == ':' || c == '-' || c == '.' || c == '*') { // NOPMD
|
||||
// do nothing
|
||||
} else if (c == '(') {
|
||||
setState(State.DATA_OBIS_VALUE);
|
||||
} else if (c == '!') {
|
||||
handleUnexpectedCharacter(c);
|
||||
if (lenientMode) {
|
||||
// Clear current Obis Data (Keep already received data)
|
||||
clearObisData();
|
||||
setState(State.CRC_VALUE);
|
||||
} else {
|
||||
setState(State.WAIT_FOR_START);
|
||||
}
|
||||
} else {
|
||||
handleUnexpectedCharacter(c);
|
||||
|
||||
if (lenientMode) {
|
||||
clearObisData();
|
||||
setState(State.DATA_OBIS_ID);
|
||||
} else {
|
||||
setState(State.WAIT_FOR_START);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DATA_OBIS_VALUE:
|
||||
if (c == ')') {
|
||||
setState(State.DATA_OBIS_VALUE_END);
|
||||
}
|
||||
break;
|
||||
case DATA_OBIS_VALUE_END:
|
||||
if (Character.isWhitespace(c)) { // NOPMD EmptyIfStmt
|
||||
// ignore
|
||||
} else if (Character.isDigit(c)) {
|
||||
setState(State.DATA_OBIS_ID);
|
||||
} else if (c == '(') {
|
||||
setState(State.DATA_OBIS_VALUE);
|
||||
} else if (c == '!') {
|
||||
setState(State.CRC_VALUE);
|
||||
} else {
|
||||
handleUnexpectedCharacter(c);
|
||||
|
||||
if (!lenientMode) {
|
||||
setState(State.WAIT_FOR_START);
|
||||
} // Other wise try to recover in lenient mode
|
||||
}
|
||||
break;
|
||||
|
||||
case CRC_VALUE:
|
||||
/*
|
||||
* Normally the P1 telegram ends with a \r\n sequence
|
||||
* If we already see a '/' character we also assume the current
|
||||
* P1 telegram is correctly finished
|
||||
*/
|
||||
if (c == '\r' || c == '/') {
|
||||
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;
|
||||
}
|
||||
}
|
||||
telegramListener.telegramReceived(constructTelegram());
|
||||
reset();
|
||||
if (c == '/') {
|
||||
/*
|
||||
* Immediately proceed to the next state (robust implementation for meter that do not follow
|
||||
* the specification
|
||||
*/
|
||||
setState(State.HEADER);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
handleCharacter(c);
|
||||
}
|
||||
logger.trace("State after parsing: {}", state);
|
||||
}
|
||||
|
||||
private P1Telegram constructTelegram() {
|
||||
final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);
|
||||
|
||||
if (lenientMode) {
|
||||
return new P1Telegram(cosemObjectsCopy, telegramState, rawData.toString(),
|
||||
unknownCosemObjects.isEmpty() ? Collections.emptyList() : new ArrayList<>(unknownCosemObjects));
|
||||
} else {
|
||||
return new P1Telegram(cosemObjectsCopy, telegramState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
setState(State.WAIT_FOR_START);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an unexpected character. The character will be logged and the current telegram is marked corrupted
|
||||
*
|
||||
* @param c the unexpected character
|
||||
*/
|
||||
private void handleUnexpectedCharacter(char c) {
|
||||
logger.debug("Unexpected character '{}' in state: {}. This P1 telegram is marked as failed", c, state);
|
||||
|
||||
telegramState = TelegramState.DATA_CORRUPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a single character
|
||||
*
|
||||
* @param c the character to process
|
||||
*/
|
||||
private void handleCharacter(char c) {
|
||||
switch (state) {
|
||||
case WAIT_FOR_START:
|
||||
// ignore the data
|
||||
break;
|
||||
case HEADER:
|
||||
crc.processByte((byte) c);
|
||||
break;
|
||||
case CRLF:
|
||||
crc.processByte((byte) c);
|
||||
break;
|
||||
case DATA_OBIS_ID:
|
||||
obisId.append(c);
|
||||
crc.processByte((byte) c);
|
||||
break;
|
||||
case DATA_OBIS_VALUE:
|
||||
obisValue.append(c);
|
||||
crc.processByte((byte) c);
|
||||
break;
|
||||
case DATA_OBIS_VALUE_END:
|
||||
obisValue.append(c);
|
||||
crc.processByte((byte) c);
|
||||
break;
|
||||
case CRC_VALUE:
|
||||
if (c == '!') {
|
||||
crc.processByte((byte) c);
|
||||
} else {
|
||||
crcValue.append(c);
|
||||
}
|
||||
// CRC data is not part of received data
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all internal state
|
||||
*/
|
||||
private void clearInternalData() {
|
||||
obisId.setLength(0);
|
||||
obisValue.setLength(0);
|
||||
rawData.setLength(0);
|
||||
crcValue.setLength(0);
|
||||
crc.initialize();
|
||||
cosemObjects.clear();
|
||||
unknownCosemObjects.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all the current OBIS data. I.e.
|
||||
* - current OBIS identifier
|
||||
* - current OBIS value
|
||||
*/
|
||||
private void clearObisData() {
|
||||
obisId.setLength(0);
|
||||
obisValue.setLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current CosemObject in the list of received cosem Objects
|
||||
*/
|
||||
private void storeCurrentCosemObject() {
|
||||
String obisIdString = obisId.toString();
|
||||
|
||||
if (!obisIdString.isEmpty()) {
|
||||
final String obisValueString = obisValue.toString();
|
||||
CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
|
||||
|
||||
if (cosemObject == null) {
|
||||
if (lenientMode) {
|
||||
unknownCosemObjects.add(new SimpleEntry<>(obisIdString, obisValueString));
|
||||
}
|
||||
} else {
|
||||
logger.trace("Adding {} to list of Cosem Objects", cosemObject);
|
||||
cosemObjects.add(cosemObject);
|
||||
}
|
||||
}
|
||||
clearObisData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newState the new state to set
|
||||
*/
|
||||
private void setState(State newState) {
|
||||
synchronized (state) {
|
||||
switch (newState) {
|
||||
case HEADER:
|
||||
// Clear CRC data and mark current telegram as OK
|
||||
crc.initialize();
|
||||
break;
|
||||
case WAIT_FOR_START:
|
||||
// Clears internal state data and mark current telegram as OK
|
||||
clearInternalData();
|
||||
telegramState = TelegramState.OK;
|
||||
break;
|
||||
case DATA_OBIS_ID:
|
||||
// If the current state is CRLF we are processing the header and don't have a cosem object yet
|
||||
if (state != State.CRLF) {
|
||||
storeCurrentCosemObject();
|
||||
}
|
||||
break;
|
||||
case CRC_VALUE:
|
||||
storeCurrentCosemObject();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLenientMode(boolean lenientMode) {
|
||||
this.lenientMode = lenientMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.p1telegram;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface TelegramParser {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data byte data to parse
|
||||
* @param length number of bytes to parse
|
||||
*/
|
||||
void parse(byte[] data, int length);
|
||||
|
||||
/**
|
||||
* Reset the current telegram state.
|
||||
*/
|
||||
default void reset() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lenientMode the lenientMode to set
|
||||
*/
|
||||
default void setLenientMode(boolean lenientMode) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMREventListener;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This implements the discovery service for detecting new DSMR Meters.
|
||||
*
|
||||
* The service will iterate over the available serial ports and open the given serial port and wait for telegrams. If
|
||||
* the port is already owned because it's already detected this service will ignore it. But it will report a warning in
|
||||
* case the port was locked due to a crash.
|
||||
* After {@link #BAUDRATE_SWITCH_TIMEOUT_SECONDS} seconds it will switch the baud rate and wait again for telegrams.
|
||||
* When that doesn't produce any results the service will give up (assuming no DSMR Bridge is present).
|
||||
*
|
||||
* If a telegram is received with at least 1 Cosem Object a bridge is assumed available and a Thing is added (regardless
|
||||
* if there were problems receiving the telegram) and the discovery is stopped.
|
||||
*
|
||||
* If there are communication problems the service will give an warning and give up
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
|
||||
public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements DSMREventListener {
|
||||
|
||||
/**
|
||||
* The timeout used to switch baudrate if no valid data is received within that time frame.
|
||||
*/
|
||||
private static final int BAUDRATE_SWITCH_TIMEOUT_SECONDS = 25;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRBridgeDiscoveryService.class);
|
||||
|
||||
/**
|
||||
* Serial Port Manager.
|
||||
*/
|
||||
private @NonNullByDefault({}) SerialPortManager serialPortManager;
|
||||
|
||||
/**
|
||||
* DSMR Device that is scanned when discovery process in progress.
|
||||
*/
|
||||
private @Nullable DSMRDeviceRunnable currentScannedDevice;
|
||||
|
||||
/**
|
||||
* Name of the serial port that is scanned when discovery process in progress.
|
||||
*/
|
||||
private String currentScannedPortName = "";
|
||||
|
||||
/**
|
||||
* Keeps a boolean during time discovery process in progress.
|
||||
*/
|
||||
private boolean scanning;
|
||||
|
||||
/**
|
||||
* Starts a new discovery scan.
|
||||
*
|
||||
* All available Serial Ports are scanned for P1 telegrams.
|
||||
*/
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Started DSMR discovery scan");
|
||||
scanning = true;
|
||||
Stream<SerialPortIdentifier> portEnum = serialPortManager.getIdentifiers();
|
||||
|
||||
// Traverse each available serial port
|
||||
portEnum.forEach(portIdentifier -> {
|
||||
if (scanning) {
|
||||
currentScannedPortName = portIdentifier.getName();
|
||||
if (portIdentifier.isCurrentlyOwned()) {
|
||||
logger.trace("Possible port to check:{}, owned:{} by:{}", currentScannedPortName,
|
||||
portIdentifier.isCurrentlyOwned(), portIdentifier.getCurrentOwner());
|
||||
if (DSMR_PORT_NAME.equals(portIdentifier.getCurrentOwner())) {
|
||||
logger.debug("The port {} is owned by this binding. If no DSMR meters will be found it "
|
||||
+ "might indicate the port is locked by an older instance of this binding. "
|
||||
+ "Restart the system to unlock the port.", currentScannedPortName);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Start discovery on serial port: {}", currentScannedPortName);
|
||||
//
|
||||
final DSMRTelegramListener telegramListener = new DSMRTelegramListener("");
|
||||
final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
|
||||
portIdentifier.getName(), this, telegramListener, scheduler,
|
||||
BAUDRATE_SWITCH_TIMEOUT_SECONDS);
|
||||
device.setLenientMode(true);
|
||||
currentScannedDevice = new DSMRDeviceRunnable(device, this);
|
||||
currentScannedDevice.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
scanning = false;
|
||||
stopSerialPortScan();
|
||||
super.stopScan();
|
||||
logger.debug("Stop DSMR discovery scan");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the serial port device.
|
||||
*/
|
||||
private void stopSerialPortScan() {
|
||||
logger.debug("Stop discovery scan on port [{}].", currentScannedPortName);
|
||||
if (currentScannedDevice != null) {
|
||||
currentScannedDevice.stop();
|
||||
}
|
||||
currentScannedDevice = null;
|
||||
currentScannedPortName = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle if telegrams are received.
|
||||
*
|
||||
* If there are cosem objects received a new bridge will we discovered.
|
||||
*
|
||||
* @param telegram the received telegram
|
||||
*/
|
||||
@Override
|
||||
public void handleTelegramReceived(P1Telegram telegram) {
|
||||
List<CosemObject> cosemObjects = telegram.getCosemObjects();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
|
||||
}
|
||||
if (telegram.getTelegramState() == TelegramState.INVALID_ENCRYPTION_KEY) {
|
||||
bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
|
||||
stopSerialPortScan();
|
||||
} else if (!cosemObjects.isEmpty()) {
|
||||
ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
|
||||
meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
|
||||
stopSerialPortScan();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a bridge.
|
||||
*
|
||||
* @return The {@link ThingUID} of the newly created bridge
|
||||
*/
|
||||
private ThingUID bridgeDiscovered(ThingTypeUID bridgeThingTypeUID) {
|
||||
ThingUID thingUID = new ThingUID(bridgeThingTypeUID, Integer.toHexString(currentScannedPortName.hashCode()));
|
||||
final boolean smarty = THING_TYPE_SMARTY_BRIDGE.equals(bridgeThingTypeUID);
|
||||
final String label = String.format("@text/thing-type.dsmr.%s.label", smarty ? "smartyBridge" : "dsmrBridge");
|
||||
|
||||
// Construct the configuration for this meter
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
|
||||
if (smarty) {
|
||||
properties.put(CONFIGURATION_DECRYPTION_KEY, CONFIGURATION_DECRYPTION_KEY_EMPTY);
|
||||
}
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(bridgeThingTypeUID)
|
||||
.withProperties(properties).withLabel(label).build();
|
||||
|
||||
logger.debug("[{}] discovery result:{}", currentScannedPortName, discoveryResult);
|
||||
|
||||
thingDiscovered(discoveryResult);
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
|
||||
logger.debug("[{}] Error on port during discovery: {}", currentScannedPortName, portEvent);
|
||||
stopSerialPortScan();
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setLocaleProvider(final LocaleProvider localeProvider) {
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
protected void unsetLocaleProvider(final LocaleProvider localeProvider) {
|
||||
this.localeProvider = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setTranslationProvider(TranslationProvider i18nProvider) {
|
||||
this.i18nProvider = i18nProvider;
|
||||
}
|
||||
|
||||
protected void unsetTranslationProvider(TranslationProvider i18nProvider) {
|
||||
this.i18nProvider = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for discovery services.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class DSMRDiscoveryService extends AbstractDiscoveryService {
|
||||
/**
|
||||
* Timeout for discovery time.
|
||||
*/
|
||||
private static final int DSMR_DISCOVERY_TIMEOUT_SECONDS = 60;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRDiscoveryService.class);
|
||||
|
||||
/**
|
||||
* Meter Detector instance.
|
||||
*/
|
||||
protected final DSMRMeterDetector meterDetector = new DSMRMeterDetector();
|
||||
|
||||
/**
|
||||
* Constructs a new DSMRMeterDiscoveryService with the specified DSMR Bridge ThingUID
|
||||
*/
|
||||
public DSMRDiscoveryService() {
|
||||
super(DSMRMeterType.METER_THING_TYPES, DSMR_DISCOVERY_TIMEOUT_SECONDS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when a new meter is discovered
|
||||
* The new meter is described by the {@link DSMRMeterDescriptor}
|
||||
*
|
||||
* There will be a DiscoveryResult created and sent to the framework.
|
||||
*
|
||||
* At this moment there are no reasons why a new meter will not be accepted.
|
||||
*
|
||||
* Therefore this callback will always return true.
|
||||
*
|
||||
* @param meterDescriptor the descriptor of the new detected meter
|
||||
* @param dsmrBridgeUID ThingUID for the DSMR Bridges
|
||||
* @return true (meter is always accepted)
|
||||
*/
|
||||
public boolean meterDiscovered(DSMRMeterDescriptor meterDescriptor, ThingUID dsmrBridgeUID) {
|
||||
DSMRMeterType meterType = meterDescriptor.getMeterType();
|
||||
ThingTypeUID thingTypeUID = meterType.getThingTypeUID();
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, dsmrBridgeUID, meterDescriptor.getChannelId());
|
||||
|
||||
// Construct the configuration for this meter
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("meterType", meterType.name());
|
||||
properties.put("channel", meterDescriptor.getChannel());
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
|
||||
.withBridge(dsmrBridgeUID).withProperties(properties).withLabel(meterType.meterKind.getLabelKey())
|
||||
.build();
|
||||
|
||||
logger.debug("{} for meterDescriptor {}", discoveryResult, meterDescriptor);
|
||||
thingDiscovered(discoveryResult);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterKind;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DSMRMeterDetector} class contains the logic to discover DSMR Meters from a list of CosemObjects.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class DSMRMeterDetector {
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRMeterDetector.class);
|
||||
|
||||
/**
|
||||
* Returns a collection of {@link DSMRMeterDescriptor} that can handle the supplied list of CosemObjects.
|
||||
*
|
||||
* If no meters are detected an empty collection is returned.
|
||||
*
|
||||
* @param telegram The received telegram
|
||||
* @return collection of detected {@link DSMRMeterDescriptor}
|
||||
*/
|
||||
public Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectMeters(P1Telegram telegram) {
|
||||
final Map<DSMRMeterKind, DSMRMeterDescriptor> detectedMeters = new HashMap<>();
|
||||
final Map<CosemObjectType, CosemObject> availableCosemObjects = new HashMap<>();
|
||||
final Map<CosemObjectType, CosemObject> undetectedCosemObjects = new HashMap<>();
|
||||
|
||||
// Fill hashmap for fast comparing the set of received Cosem objects to the required set of Cosem Objects
|
||||
telegram.getCosemObjects().forEach(msg -> availableCosemObjects.put(msg.getType(), msg));
|
||||
undetectedCosemObjects.putAll(availableCosemObjects);
|
||||
|
||||
// Find compatible meters
|
||||
for (DSMRMeterType meterType : DSMRMeterType.values()) {
|
||||
logger.trace("Trying if meter type {} is compatible", meterType);
|
||||
final DSMRMeterDescriptor meterDescriptor = meterType.isCompatible(availableCosemObjects);
|
||||
|
||||
if (meterDescriptor == null) {
|
||||
logger.trace("Meter type {} is not compatible", meterType);
|
||||
} else {
|
||||
logger.debug("Meter type {} is compatible", meterType);
|
||||
|
||||
final DSMRMeterDescriptor prevDetectedMeter = detectedMeters.get(meterType.meterKind);
|
||||
|
||||
if (prevDetectedMeter == null // First meter of this kind, add it
|
||||
|| (prevDetectedMeter.getChannel() == meterDescriptor.getChannel())
|
||||
&& meterType.requiredCosemObjects.length > prevDetectedMeter
|
||||
.getMeterType().requiredCosemObjects.length) {
|
||||
logger.debug("New compatible meter: {}", meterDescriptor);
|
||||
detectedMeters.put(meterType.meterKind, meterDescriptor);
|
||||
for (CosemObjectType cot : meterDescriptor.getMeterType().supportedCosemObjects) {
|
||||
undetectedCosemObjects.remove(cot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.trace("Telegram as received from the device:\n{}\n", telegram.getRawTelegram());
|
||||
return new SimpleEntry<>(detectedMeters.values(), undetectedCosemObjects);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.handler.DSMRBridgeHandler;
|
||||
import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This implements the discovery service for new DSMR Meters on a active DSMR bridge.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P1TelegramListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRMeterDiscoveryService.class);
|
||||
|
||||
/**
|
||||
* The {@link DSMRBridgeHandler} instance
|
||||
*/
|
||||
private final DSMRBridgeHandler dsmrBridgeHandler;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link DSMRMeterDiscoveryService} attached to the give bridge handler.
|
||||
*
|
||||
* @param dsmrBridgeHandler The bridge handler this discovery service is attached to
|
||||
*/
|
||||
public DSMRMeterDiscoveryService(DSMRBridgeHandler dsmrBridgeHandler) {
|
||||
this.dsmrBridgeHandler = dsmrBridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Start discovery on existing DSMR bridge.");
|
||||
dsmrBridgeHandler.setLenientMode(true);
|
||||
dsmrBridgeHandler.registerDSMRMeterListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
logger.debug("Stop discovery on existing DSMR bridge.");
|
||||
dsmrBridgeHandler.setLenientMode(false);
|
||||
super.stopScan();
|
||||
dsmrBridgeHandler.unregisterDSMRMeterListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void telegramReceived(P1Telegram telegram) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
|
||||
}
|
||||
final Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectedMeters = meterDetector
|
||||
.detectMeters(telegram);
|
||||
verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue());
|
||||
validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(),
|
||||
detectedMeters.getKey().stream().map(md -> md.getMeterType()).collect(Collectors.toSet()));
|
||||
detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
|
||||
}
|
||||
|
||||
protected void verifyUnregisteredCosemObjects(P1Telegram telegram,
|
||||
Map<CosemObjectType, CosemObject> undetectedCosemObjects) {
|
||||
if (!undetectedCosemObjects.isEmpty()) {
|
||||
if (undetectedCosemObjects.entrySet().stream()
|
||||
.anyMatch(e -> e.getKey() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
|
||||
&& e.getValue().getCosemValues().entrySet().stream().anyMatch(
|
||||
cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) {
|
||||
// Unregistered meter detected. log to the user.
|
||||
reportUnregisteredMeters();
|
||||
} else {
|
||||
reportUnrecognizedCosemObjects(undetectedCosemObjects);
|
||||
logger.info("There are unrecognized cosem values in the data received from the meter,"
|
||||
+ " which means some meters might not be detected. Please report your raw data as reference: {}",
|
||||
telegram.getRawTelegram());
|
||||
}
|
||||
}
|
||||
if (!telegram.getUnknownCosemObjects().isEmpty()) {
|
||||
logger.info("There are unrecognized cosem values in the data received from the meter,"
|
||||
+ " which means you have values that can't be read by a channel: {}. Please report them and your raw data as reference: {}",
|
||||
telegram.getUnknownCosemObjects().stream()
|
||||
.map(e -> String.format("obis id:%s, value:%s", e.getKey(), e.getValue()))
|
||||
.collect(Collectors.joining(", ")),
|
||||
telegram.getRawTelegram());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Unrecognized cosem objects where found. This can be a bug or a new meter not yet supported.
|
||||
*
|
||||
* @param unidentifiedCosemObjects Map with the unrecognized.
|
||||
*/
|
||||
protected void reportUnrecognizedCosemObjects(Map<CosemObjectType, CosemObject> unidentifiedCosemObjects) {
|
||||
unidentifiedCosemObjects
|
||||
.forEach((k, v) -> logger.info("Unrecognized cosem object '{}' found in the data: {}", k, v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a meter equipment identifier is found that has an empty value. This
|
||||
*/
|
||||
protected void reportUnregisteredMeters() {
|
||||
logger.info(
|
||||
"An unregistered meter has been found. Probably a new meter. Retry discovery once the meter is registered with the energy provider.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the meters configured by the user match with what is detected in the telegram. Some meters are a
|
||||
* subset of other meters and therefore an invalid configured meter does work, but not all available data is
|
||||
* available to the user.
|
||||
*
|
||||
* @param things The list of configured things
|
||||
* @param configuredMeterTypes The set of meters detected in the telegram
|
||||
*/
|
||||
private void validateConfiguredMeters(List<Thing> things, Set<DSMRMeterType> configuredMeterTypes) {
|
||||
// @formatter:off
|
||||
final Set<DSMRMeterType> configuredMeters = things.stream()
|
||||
.map(Thing::getHandler)
|
||||
.filter(DSMRMeterHandler.class::isInstance)
|
||||
.map(DSMRMeterHandler.class::cast)
|
||||
.map(DSMRMeterHandler::getMeterDescriptor)
|
||||
.filter(Objects::nonNull)
|
||||
.map(h -> h.getMeterType())
|
||||
.collect(Collectors.toSet());
|
||||
// @formatter:on
|
||||
// Create list of all configured meters that are not in the detected list. If not empty meters might not be
|
||||
// correctly configured.
|
||||
final List<DSMRMeterType> invalidConfigured = configuredMeters.stream()
|
||||
.filter(dm -> !configuredMeterTypes.contains(dm)).collect(Collectors.toList());
|
||||
// Create a list of all detected meters not yet configured.
|
||||
final List<DSMRMeterType> unconfiguredMeters = configuredMeterTypes.stream()
|
||||
.filter(dm -> !configuredMeters.contains(dm)).collect(Collectors.toList());
|
||||
|
||||
if (!invalidConfigured.isEmpty()) {
|
||||
reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the validation finds in inconsistency between configured meters.
|
||||
*
|
||||
* @param invalidConfigured The list of invalid configured meters
|
||||
* @param unconfiguredMeters The list of meters that were detected, but not configured
|
||||
*/
|
||||
protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
|
||||
List<DSMRMeterType> unconfiguredMeters) {
|
||||
logger.info(
|
||||
"Possible incorrect meters configured. These are configured: {}."
|
||||
+ "But the following unconfigured meters are found in the data received from the meter: {}",
|
||||
invalidConfigured.stream().map(m -> m.name()).collect(Collectors.joining(", ")),
|
||||
unconfiguredMeters.stream().map(m -> m.name()).collect(Collectors.joining(", ")));
|
||||
}
|
||||
|
||||
public void setLocaleProvider(final LocaleProvider localeProvider) {
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public void unsetLocaleProvider() {
|
||||
this.localeProvider = null;
|
||||
}
|
||||
|
||||
public void setTranslationProvider(TranslationProvider i18nProvider) {
|
||||
this.i18nProvider = i18nProvider;
|
||||
}
|
||||
|
||||
public void unsetTranslationProvider() {
|
||||
this.i18nProvider = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.handler;
|
||||
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMREventListener;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRFixedConfigDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DSMRBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored way messages are forwarded to meters. Removed availableMeters dependency.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventListener {
|
||||
|
||||
/**
|
||||
* Factor that will be multiplied with {@link #receivedTimeoutNanos} to get the timeout factor after which the
|
||||
* device is set off line.
|
||||
*/
|
||||
private static final int OFFLINE_TIMEOUT_FACTOR = 10;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRBridgeHandler.class);
|
||||
|
||||
/**
|
||||
* Additional meter listeners to get received meter values.
|
||||
*/
|
||||
private final List<P1TelegramListener> meterListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Serial Port Manager.
|
||||
*/
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
/**
|
||||
* The dsmrDevice managing the connection and handling telegrams.
|
||||
*/
|
||||
private @NonNullByDefault({}) DSMRDevice dsmrDevice;
|
||||
|
||||
/**
|
||||
* Long running process that controls the DSMR device connection.
|
||||
*/
|
||||
private @NonNullByDefault({}) DSMRDeviceRunnable dsmrDeviceRunnable;
|
||||
|
||||
/**
|
||||
* Thread for {@link DSMRDeviceRunnable}. A thread is used because the {@link DSMRDeviceRunnable} is a blocking
|
||||
* process that runs as long as the thing is not disposed.
|
||||
*/
|
||||
private @NonNullByDefault({}) Thread dsmrDeviceThread;
|
||||
|
||||
/**
|
||||
* Watchdog to check if messages received and restart if necessary.
|
||||
*/
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> watchdog;
|
||||
|
||||
/**
|
||||
* Number of nanoseconds after which a timeout is triggered when no messages received.
|
||||
*/
|
||||
private long receivedTimeoutNanos;
|
||||
|
||||
/**
|
||||
* Timestamp in nanoseconds of last P1 telegram received
|
||||
*/
|
||||
private volatile long telegramReceivedTimeNanos;
|
||||
|
||||
private final boolean smartyMeter;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param bridge the Bridge ThingType
|
||||
* @param serialPortManager The Serial port manager
|
||||
*/
|
||||
public DSMRBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
this.serialPortManager = serialPortManager;
|
||||
smartyMeter = THING_TYPE_SMARTY_BRIDGE.equals(bridge.getThingTypeUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link DSMRBridgeHandler} does not support handling commands.
|
||||
*
|
||||
* @param channelUID the {@link ChannelUID} of the channel to which the command was sent
|
||||
* @param command the {@link Command}
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// DSMRBridgeHandler does not support commands
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this {@link DSMRBridgeHandler}.
|
||||
*
|
||||
* This method will get the corresponding configuration and initialize and start the corresponding
|
||||
* {@link DSMRDevice}.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
|
||||
|
||||
if (smartyMeter && (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/error.configuration.invalidsmartykey");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.trace("Using configuration {}", deviceConfig);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
receivedTimeoutNanos = TimeUnit.SECONDS.toNanos(deviceConfig.receivedTimeout);
|
||||
final DSMRDevice dsmrDevice = createDevice(deviceConfig);
|
||||
resetLastReceivedState();
|
||||
this.dsmrDevice = dsmrDevice; // otherwise Eclipse will give a null pointer error on the next line :-(
|
||||
dsmrDeviceRunnable = new DSMRDeviceRunnable(dsmrDevice, this);
|
||||
dsmrDeviceThread = new Thread(dsmrDeviceRunnable);
|
||||
dsmrDeviceThread.setName("OH-binding-" + getThing().getUID());
|
||||
dsmrDeviceThread.setDaemon(true);
|
||||
dsmrDeviceThread.start();
|
||||
watchdog = scheduler.scheduleWithFixedDelay(this::alive, receivedTimeoutNanos, receivedTimeoutNanos,
|
||||
TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link DSMRDevice} that corresponds with the user specified configuration.
|
||||
*
|
||||
* @param deviceConfig device configuration
|
||||
* @return Specific {@link DSMRDevice} instance
|
||||
*/
|
||||
private DSMRDevice createDevice(DSMRDeviceConfiguration deviceConfig) {
|
||||
final DSMRDevice dsmrDevice;
|
||||
|
||||
if (smartyMeter) {
|
||||
dsmrDevice = new DSMRFixedConfigDevice(serialPortManager, deviceConfig.serialPort,
|
||||
DSMRSerialSettings.HIGH_SPEED_SETTINGS, this, new DSMRTelegramListener(deviceConfig.decryptionKey));
|
||||
} else {
|
||||
final DSMRTelegramListener telegramListener = new DSMRTelegramListener();
|
||||
|
||||
if (deviceConfig.isSerialFixedSettings()) {
|
||||
dsmrDevice = new DSMRFixedConfigDevice(serialPortManager, deviceConfig.serialPort,
|
||||
DSMRSerialSettings.getPortSettingsFromConfiguration(deviceConfig), this, telegramListener);
|
||||
} else {
|
||||
dsmrDevice = new DSMRSerialAutoDevice(serialPortManager, deviceConfig.serialPort, this,
|
||||
telegramListener, scheduler, deviceConfig.receivedTimeout);
|
||||
}
|
||||
}
|
||||
return dsmrDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a meter listener.
|
||||
*
|
||||
* @param meterListener the meter discovery listener to add
|
||||
* @return true if listener is added, false otherwise
|
||||
*/
|
||||
public boolean registerDSMRMeterListener(P1TelegramListener meterListener) {
|
||||
logger.trace("Register DSMRMeterListener");
|
||||
return meterListeners.add(meterListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a meter listener
|
||||
*
|
||||
* @param meterListener the meter discovery listener to remove
|
||||
* @return true is listener is removed, false otherwise
|
||||
*/
|
||||
public boolean unregisterDSMRMeterListener(P1TelegramListener meterListener) {
|
||||
logger.trace("Unregister DSMRMeterListener");
|
||||
return meterListeners.remove(meterListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Watchdog method that is run with the scheduler and checks if meter values were received. If the timeout is
|
||||
* exceeded the device is restarted. If the off line timeout factor is exceeded the device is set off line. By not
|
||||
* setting the device on first exceed off line their is some slack in the system and it won't flip on and offline in
|
||||
* case of an unstable system.
|
||||
*/
|
||||
private void alive() {
|
||||
logger.trace("Bridge alive check with #{} children.", getThing().getThings().size());
|
||||
long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
|
||||
|
||||
if (deltaLastReceived > receivedTimeoutNanos) {
|
||||
logger.debug("No data received for {} seconds, restarting port if possible.",
|
||||
TimeUnit.NANOSECONDS.toSeconds(deltaLastReceived));
|
||||
if (dsmrDeviceRunnable != null) {
|
||||
dsmrDeviceRunnable.restart();
|
||||
}
|
||||
if (deltaLastReceived > receivedTimeoutNanos * OFFLINE_TIMEOUT_FACTOR) {
|
||||
logger.trace("Setting device offline if not yet done, and reset last received time.");
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.bridge.nodata");
|
||||
}
|
||||
resetLastReceivedState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last received time of messages to the current time.
|
||||
*/
|
||||
private void resetLastReceivedState() {
|
||||
telegramReceivedTimeNanos = System.nanoTime();
|
||||
logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handleTelegramReceived(P1Telegram telegram) {
|
||||
if (telegram.getCosemObjects().isEmpty()) {
|
||||
logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}",
|
||||
telegram.getTelegramState().stateDetails);
|
||||
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, telegram.getTelegramState().stateDetails);
|
||||
} else {
|
||||
resetLastReceivedState();
|
||||
meterValueReceived(telegram);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
|
||||
if (portEvent != DSMRConnectorErrorEvent.READ_ERROR) {
|
||||
deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, portEvent.getEventDetails());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to forward the last received messages to the bound meters and to the meterListeners.
|
||||
*
|
||||
* @param telegram received meter values.
|
||||
*/
|
||||
private void meterValueReceived(P1Telegram telegram) {
|
||||
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();
|
||||
|
||||
if (dsmrMeterHandler instanceof DSMRMeterHandler) {
|
||||
dsmrMeterHandler.telegramReceived(telegram);
|
||||
}
|
||||
});
|
||||
meterListeners.forEach(m -> m.telegramReceived(telegram));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (watchdog != null) {
|
||||
watchdog.cancel(true);
|
||||
watchdog = null;
|
||||
}
|
||||
if (dsmrDeviceRunnable != null) {
|
||||
dsmrDeviceRunnable.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lenientMode the lenientMode to set
|
||||
*/
|
||||
public void setLenientMode(boolean lenientMode) {
|
||||
logger.trace("SetLenientMode: {}", lenientMode);
|
||||
if (dsmrDevice != null) {
|
||||
dsmrDevice.setLenientMode(lenientMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to set device off line.
|
||||
*
|
||||
* @param status off line status
|
||||
* @param details off line detailed message
|
||||
*/
|
||||
private void deviceOffline(ThingStatusDetail status, String details) {
|
||||
updateStatus(ThingStatus.OFFLINE, status, details);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.handler;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeter;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterConfiguration;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The MeterHandler will create logic DSMR meter ThingTypes
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Separated thing state update cycle from meter values received cycle
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRMeterHandler.class);
|
||||
|
||||
/**
|
||||
* The DSMRMeter instance.
|
||||
*/
|
||||
private @NonNullByDefault({}) DSMRMeter meter;
|
||||
|
||||
/**
|
||||
* Last received cosem objects.
|
||||
*/
|
||||
private List<CosemObject> lastReceivedValues = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Reference to the meter watchdog.
|
||||
*/
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> meterWatchdog;
|
||||
|
||||
/**
|
||||
* Creates a new MeterHandler for the given Thing.
|
||||
*
|
||||
* @param thing {@link Thing} to create the MeterHandler for
|
||||
*/
|
||||
public DSMRMeterHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* DSMR Meter don't support handling commands
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a DSMR Meter
|
||||
*
|
||||
* This method will load the corresponding configuration
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initialize MeterHandler for Thing {}", getThing().getUID());
|
||||
DSMRMeterType meterType;
|
||||
|
||||
try {
|
||||
meterType = DSMRMeterType.valueOf(getThing().getThingTypeUID().getId().toUpperCase());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warn(
|
||||
"{} could not be initialized due to an invalid meterType {}. Delete this Thing if the problem persists.",
|
||||
getThing(), getThing().getThingTypeUID().getId().toUpperCase());
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/error.configuration.invalidmetertype");
|
||||
return;
|
||||
}
|
||||
DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
|
||||
DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, meterConfig.channel);
|
||||
meter = new DSMRMeter(meterDescriptor);
|
||||
meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (meterWatchdog != null) {
|
||||
meterWatchdog.cancel(false);
|
||||
meterWatchdog = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of all channels from the last received Cosem values from the meter. The lastReceivedValues are
|
||||
* cleared after processing here so when it does contain values the next time this method is called and it contains
|
||||
* values those are new values.
|
||||
*/
|
||||
private synchronized void updateState() {
|
||||
logger.trace("Update state for device: {}", getThing().getThingTypeUID().getId());
|
||||
if (!lastReceivedValues.isEmpty()) {
|
||||
for (CosemObject cosemObject : lastReceivedValues) {
|
||||
String channel = cosemObject.getType().name().toLowerCase();
|
||||
|
||||
for (Entry<String, ? extends State> entry : cosemObject.getCosemValues().entrySet()) {
|
||||
if (!entry.getKey().isEmpty()) {
|
||||
/* CosemObject has a specific sub channel */
|
||||
channel += "_" + entry.getKey();
|
||||
}
|
||||
State newState = entry.getValue();
|
||||
logger.debug("Updating state for channel {} to value {}", channel, newState);
|
||||
updateState(channel, newState);
|
||||
}
|
||||
}
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
lastReceivedValues = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for received meter values. When this method is called but the telegram has no values for this meter this
|
||||
* meter is set to offline because something is wrong, possible the meter has been removed.
|
||||
*
|
||||
* @param telegram The received telegram
|
||||
*/
|
||||
@Override
|
||||
public void telegramReceived(P1Telegram telegram) {
|
||||
lastReceivedValues = Collections.emptyList();
|
||||
final DSMRMeter localMeter = meter;
|
||||
|
||||
if (localMeter == null) {
|
||||
return;
|
||||
}
|
||||
List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects());
|
||||
|
||||
if (filteredValues.isEmpty()) {
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.thing.nodata");
|
||||
}
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Received {} objects for {}", filteredValues.size(), getThing().getThingTypeUID().getId());
|
||||
}
|
||||
lastReceivedValues = filteredValues;
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
|
||||
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
||||
// Set status to offline --> Thing will become online after receiving meter values
|
||||
setDeviceOffline(ThingStatusDetail.NONE, null);
|
||||
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
setDeviceOffline(ThingStatusDetail.BRIDGE_OFFLINE, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the {@link DSMRMeterDescriptor} this object is configured with
|
||||
*/
|
||||
public @Nullable DSMRMeterDescriptor getMeterDescriptor() {
|
||||
return meter == null ? null : meter.getMeterDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to set the meter off line.
|
||||
*
|
||||
* @param status off line status
|
||||
* @param details off line detailed message
|
||||
*/
|
||||
private void setDeviceOffline(ThingStatusDetail status, @Nullable String details) {
|
||||
updateStatus(ThingStatus.OFFLINE, status, details);
|
||||
getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.NULL));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.OBISIdentifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DSMR Meter represents a meter for this binding.
|
||||
*
|
||||
* A physical meter is a certain {@link DSMRMeterType} on a M-Bus channel. This is the {@link DSMRMeterDescriptor}
|
||||
* and is a private member of the {@link DSMRMeter}.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Refactored class, removed actual handling and moved to handler class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRMeter {
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRMeter.class);
|
||||
|
||||
/**
|
||||
* Meter identification.
|
||||
*/
|
||||
private final DSMRMeterDescriptor meterDescriptor;
|
||||
|
||||
/**
|
||||
* List of supported message identifiers for this meter
|
||||
*/
|
||||
private List<OBISIdentifier> supportedIdentifiers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new DSMRMeter
|
||||
*
|
||||
* @param meterDescriptor {@link DSMRMeterDescriptor} containing the description of the new meter
|
||||
*/
|
||||
public DSMRMeter(DSMRMeterDescriptor meterDescriptor) {
|
||||
this.meterDescriptor = meterDescriptor;
|
||||
|
||||
for (CosemObjectType msgType : meterDescriptor.getMeterType().supportedCosemObjects) {
|
||||
OBISIdentifier obisId = msgType.obisId;
|
||||
if (msgType.obisId.getGroupB() == null) {
|
||||
supportedIdentifiers.add(new OBISIdentifier(obisId.getGroupA(), meterDescriptor.getChannel(),
|
||||
obisId.getGroupC(), obisId.getGroupD(), obisId.getGroupE(), msgType.obisId.getGroupF()));
|
||||
} else {
|
||||
supportedIdentifiers.add(msgType.obisId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Cosem Objects this meter will handle and removed them from the passed {@link CosemObject} list.
|
||||
*
|
||||
* @param cosemObjects list of CosemObject that must be processed and where the objects of this meter are removed
|
||||
* @return List of CosemObject that this meter can process
|
||||
*/
|
||||
public List<CosemObject> filterMeterValues(List<CosemObject> cosemObjects) {
|
||||
logger.trace("supported identifiers: {}, searching for objects {}", supportedIdentifiers, cosemObjects);
|
||||
List<CosemObject> filteredValues = cosemObjects.stream()
|
||||
.filter(cosemObject -> supportedIdentifiers
|
||||
.contains(cosemObject.getObisIdentifier().getReducedOBISIdentifier()))
|
||||
.collect(Collectors.toList());
|
||||
return filteredValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the {@link DSMRMeterDescriptor} this object is configured with
|
||||
*/
|
||||
public DSMRMeterDescriptor getMeterDescriptor() {
|
||||
return meterDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return meterDescriptor.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This class describes the configuration for a meter.
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Added refresh field
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRMeterConfiguration {
|
||||
/**
|
||||
* M-Bus channel
|
||||
*/
|
||||
public int channel;
|
||||
|
||||
/**
|
||||
* Status update rate as specified by the user in seconds.
|
||||
*/
|
||||
public int refresh;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DSMRMeterConfiguration(channel:" + channel + ",refresh=" + refresh + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Class containing constants that are applicable to the DSMRMeter
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class DSMRMeterConstants {
|
||||
|
||||
/**
|
||||
* Unknown M-Bus channel
|
||||
*/
|
||||
public static final int UNKNOWN_CHANNEL = -1;
|
||||
|
||||
private DSMRMeterConstants() {
|
||||
// Constants class
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The DSMRMeterDescriptor describes a meter.
|
||||
*
|
||||
* A DSMR Meter consists of the following properties:
|
||||
* - MeterType
|
||||
* - M-Bus channel
|
||||
* - Identifier
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRMeterDescriptor {
|
||||
/**
|
||||
* Meter type
|
||||
*/
|
||||
private final DSMRMeterType meterType;
|
||||
|
||||
/**
|
||||
* M-Bus channel
|
||||
*/
|
||||
private final int channel;
|
||||
|
||||
/**
|
||||
* Constructor for new DSMRMeterDescriptor
|
||||
*
|
||||
* @param meterType The meter type
|
||||
* @param channel The M-Bus channel this meter is connected to
|
||||
* @throws IllegalArgumentException if one of the parameters is null
|
||||
*/
|
||||
public DSMRMeterDescriptor(DSMRMeterType meterType, int channel) {
|
||||
this.meterType = meterType;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the meterType
|
||||
*/
|
||||
public DSMRMeterType getMeterType() {
|
||||
return meterType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the channel
|
||||
*/
|
||||
public int getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id to identify of channel as String or as "default" is its the unknown channel.
|
||||
*/
|
||||
public String getChannelId() {
|
||||
return channel == DSMRMeterConstants.UNKNOWN_CHANNEL ? "default" : String.valueOf(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if both DSMRMeterDescriptor are equal. I.e.:
|
||||
* - meterType is the same
|
||||
* - channel is the same
|
||||
* - identification is the same
|
||||
*
|
||||
* @param other DSMRMeterDescriptor to check
|
||||
* @return true if both objects are equal, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (!(other instanceof DSMRMeterDescriptor)) {
|
||||
return false;
|
||||
}
|
||||
DSMRMeterDescriptor o = (DSMRMeterDescriptor) other;
|
||||
|
||||
return meterType == o.meterType && channel == o.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(meterType, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Meter type: " + meterType + ", channel: " + channel + ']';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
/**
|
||||
* This class describes the kind of meters the binding supports
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
public enum DSMRMeterKind {
|
||||
INVALID,
|
||||
DEVICE,
|
||||
MAIN_ELECTRICITY,
|
||||
GAS,
|
||||
HEATING,
|
||||
COOLING,
|
||||
WATER,
|
||||
GENERIC,
|
||||
GJ,
|
||||
M3,
|
||||
SLAVE_ELECTRICITY1,
|
||||
SLAVE_ELECTRICITY2;
|
||||
|
||||
/**
|
||||
* @return Returns the i18n label key for this meter.
|
||||
*/
|
||||
public String getLabelKey() {
|
||||
return "@text/meterKind." + name().toLowerCase() + ".label";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.meter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Supported meters
|
||||
*
|
||||
* @author M. Volaart - Initial contribution
|
||||
*/
|
||||
public enum DSMRMeterType {
|
||||
// Don't auto format the enum list. For readability the format for the enum is:
|
||||
// First line parameters; DSMRMeterKind and CosemObjectType (identification object type)
|
||||
// 2nd line and further required CosemObjectType
|
||||
// optional CosemObjectType start on a new line
|
||||
//@formatter:off
|
||||
|
||||
/** DSMR V2 / V3 Device meter type (used for device (and not meter specific) related messages) */
|
||||
DEVICE_V2_V3(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN,
|
||||
CosemObjectType.P1_TEXT_CODE, CosemObjectType.P1_TEXT_STRING),
|
||||
|
||||
/** DSMR V4 Device meter type (used for device (and not meter specific) related messages) */
|
||||
DEVICE_V4(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN,
|
||||
CosemObjectType.P1_TEXT_CODE, CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_VERSION_OUTPUT,
|
||||
CosemObjectType.P1_TIMESTAMP),
|
||||
|
||||
/** DSMR V5 Device meter type (used for device (and not meter specific) related messages) */
|
||||
DEVICE_V5(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_VERSION_OUTPUT, CosemObjectType.P1_TIMESTAMP },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.P1_TEXT_STRING_LONG }),
|
||||
|
||||
/** ACE4000 Electricity */
|
||||
ELECTRICITY_ACE4000(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.METER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF0,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF0,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_ACTIVE_IMPORT_POWER, CosemObjectType.EMETER_SWITCH_POSITION },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_DELIVERY_TARIFF0_ANTIFRAUD,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF1_ANTIFRAUD, CosemObjectType.EMETER_DELIVERY_TARIFF2_ANTIFRAUD,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TRESHOLD_A }),
|
||||
|
||||
/** ACE4000 Gas meter */
|
||||
GAS_ACE4000(DSMRMeterKind.GAS, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_EQUIPMENT_IDENTIFIER, CosemObjectType.GMETER_24H_DELIVERY_V2,
|
||||
CosemObjectType.GMETER_VALVE_POSITION_V2_2),
|
||||
|
||||
/** ACE4000 Heating meter */
|
||||
HEATING_ACE4000(DSMRMeterKind.HEATING, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_EQUIPMENT_IDENTIFIER, CosemObjectType.HMETER_VALUE_V2),
|
||||
|
||||
/** ACE4000 Cooling meter */
|
||||
COOLING_ACE4000(DSMRMeterKind.COOLING, CosemObjectType.UNKNOWN,
|
||||
CosemObjectType.CMETER_VALUE_V2),
|
||||
|
||||
/** ACE4000 Water meter */
|
||||
WATER_ACE4000(DSMRMeterKind.WATER, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_EQUIPMENT_IDENTIFIER, CosemObjectType.WMETER_VALUE_V2),
|
||||
|
||||
/** ACE4000 first Slave electricity meter */
|
||||
SLAVE_ELECTRICITY1_ACE4000(DSMRMeterKind.SLAVE_ELECTRICITY1, CosemObjectType.UNKNOWN,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF0, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF0,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF2,
|
||||
CosemObjectType.EMETER_TARIFF_INDICATOR, CosemObjectType.EMETER_ACTIVE_IMPORT_POWER,
|
||||
CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_SWITCH_POSITION),
|
||||
|
||||
/** ACE4000 second Slave electricity meter */
|
||||
SLAVE_ELECTRICITY2_ACE4000(DSMRMeterKind.SLAVE_ELECTRICITY2, CosemObjectType.UNKNOWN,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF0, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF0,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF1, CosemObjectType.EMETER_PRODUCTION_TARIFF2,
|
||||
CosemObjectType.EMETER_TARIFF_INDICATOR, CosemObjectType.EMETER_ACTIVE_IMPORT_POWER,
|
||||
CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_SWITCH_POSITION),
|
||||
|
||||
/** DSMR V2.1 Electricity meter */
|
||||
ELECTRICITY_V2_1(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER_V2_X,
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER_V2_X, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_TRESHOLD_A_V2_1, CosemObjectType.EMETER_SWITCH_POSITION_V2_1,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY),
|
||||
|
||||
/** DSMR V2.1 Gas meter */
|
||||
GAS_V2_1(DSMRMeterKind.GAS, CosemObjectType.GMETER_EQUIPMENT_IDENTIFIER_V2,
|
||||
CosemObjectType.GMETER_EQUIPMENT_IDENTIFIER_V2, CosemObjectType.GMETER_24H_DELIVERY_V2,
|
||||
CosemObjectType.GMETER_24H_DELIVERY_COMPENSATED_V2, CosemObjectType.GMETER_VALVE_POSITION_V2_1),
|
||||
|
||||
/** DSMR V2.2 Electricity meter */
|
||||
ELECTRICITY_V2_2(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER_V2_X,
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER_V2_X, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.METER_VALVE_SWITCH_POSITION,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY),
|
||||
|
||||
/** DSMR V2.2 Gas meter */
|
||||
GAS_V2_2(DSMRMeterKind.GAS, CosemObjectType.GMETER_EQUIPMENT_IDENTIFIER_V2,
|
||||
CosemObjectType.GMETER_EQUIPMENT_IDENTIFIER_V2, CosemObjectType.GMETER_24H_DELIVERY_V2,
|
||||
CosemObjectType.GMETER_24H_DELIVERY_COMPENSATED_V2, CosemObjectType.GMETER_VALVE_POSITION_V2_2),
|
||||
|
||||
/** DSMR V2.2 Heating meter */
|
||||
HEATING_V2_2(DSMRMeterKind.HEATING, CosemObjectType.HMETER_EQUIPMENT_IDENTIFIER_V2_2,
|
||||
CosemObjectType.HMETER_EQUIPMENT_IDENTIFIER_V2_2, CosemObjectType.HMETER_VALUE_V2),
|
||||
|
||||
/** DSMR V2.2 Cooling meter */
|
||||
COOLING_V2_2(DSMRMeterKind.COOLING, CosemObjectType.CMETER_EQUIPMENT_IDENTIFIER_V2_2,
|
||||
CosemObjectType.CMETER_EQUIPMENT_IDENTIFIER_V2_2, CosemObjectType.CMETER_VALUE_V2),
|
||||
|
||||
/** DSMR V2.2 Water meter */
|
||||
WATER_V2_2(DSMRMeterKind.WATER, CosemObjectType.WMETER_EQUIPMENT_IDENTIFIER_V2_2,
|
||||
CosemObjectType.WMETER_EQUIPMENT_IDENTIFIER_V2_2, CosemObjectType.WMETER_VALUE_V2),
|
||||
|
||||
/** DSMR V3.0 Electricity meter */
|
||||
ELECTRICITY_V3_0(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION},
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_TRESHOLD_A, CosemObjectType.EMETER_TRESHOLD_KWH,
|
||||
CosemObjectType.EMETER_SWITCH_POSITION}),
|
||||
|
||||
/** DSMR V3.0 Gas meter */
|
||||
GAS_V3_0(DSMRMeterKind.GAS, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.GMETER_VALUE_V3},
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.METER_VALVE_SWITCH_POSITION}),
|
||||
|
||||
/** DSMR V3.0 Water meter */
|
||||
WATER_V3_0(DSMRMeterKind.WATER, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.WMETER_VALUE_V3, CosemObjectType.METER_VALVE_SWITCH_POSITION),
|
||||
|
||||
/** DSMR V3.0 GJ meter (heating, cooling) */
|
||||
GJ_V3_0(DSMRMeterKind.GJ, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.GJMETER_VALUE_V3, CosemObjectType.METER_VALVE_SWITCH_POSITION),
|
||||
|
||||
/** DSMR V3.0 Generic meter */
|
||||
GENERIC_V3_0(DSMRMeterKind.GENERIC, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.GENMETER_VALUE_V3, CosemObjectType.METER_VALVE_SWITCH_POSITION),
|
||||
|
||||
/** DSMR V4.0 Electricity meter */
|
||||
ELECTRICITY_V4_0(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_SWITCH_POSITION,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1 },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_POWER_FAILURE_LOG, CosemObjectType.EMETER_VOLTAGE_SAGS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L3, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SWELLS_L3 }),
|
||||
|
||||
/** DSMR V4 m3 meter (gas, water) */
|
||||
M3_V4(DSMRMeterKind.M3, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.M3METER_VALUE, CosemObjectType.METER_VALVE_SWITCH_POSITION),
|
||||
|
||||
/** DSMR V4 GJ meter (heating, cooling) */
|
||||
GJ_V4(DSMRMeterKind.GJ, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.GJMETER_VALUE_V4, CosemObjectType.METER_VALVE_SWITCH_POSITION),
|
||||
|
||||
/** DSMR V4 Slave Electricity meter */
|
||||
SLAVE_ELECTRICITY_V4(DSMRMeterKind.SLAVE_ELECTRICITY1, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.EMETER_VALUE, CosemObjectType.EMETER_SWITCH_POSITION),
|
||||
|
||||
/** DSMR V4.0.4 Electricity meter */
|
||||
ELECTRICITY_V4_0_4(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_SWITCH_POSITION,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L1, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L1,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L1 },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_POWER_FAILURE_LOG, CosemObjectType.EMETER_VOLTAGE_SAGS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L3, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SWELLS_L3, CosemObjectType.EMETER_INSTANT_CURRENT_L2,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L3 }),
|
||||
|
||||
/** DSMR V4.2 Electricity meter (specification not available, implemented by reverse engineering */
|
||||
ELECTRICITY_V4_2(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L1, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L1,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L1 },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_POWER_FAILURE_LOG, CosemObjectType.EMETER_VOLTAGE_SAGS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L3, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SWELLS_L3, CosemObjectType.EMETER_INSTANT_CURRENT_L2,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L3 }),
|
||||
|
||||
/** DSMR V5.0 Electricity meter */
|
||||
ELECTRICITY_V5_0(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_LONG_POWER_FAILURES,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L1,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L1, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L1,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L1, CosemObjectType.EMETER_INSTANT_VOLTAGE_L1 },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_POWER_FAILURE_LOG, CosemObjectType.EMETER_VOLTAGE_SAGS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L3, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SWELLS_L3, CosemObjectType.EMETER_INSTANT_CURRENT_L2,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L3, CosemObjectType.EMETER_INSTANT_VOLTAGE_L2,
|
||||
CosemObjectType.EMETER_INSTANT_VOLTAGE_L3 }),
|
||||
|
||||
/** DSMR V5.0 m3 meter (gas, water) */
|
||||
M3_V5_0(DSMRMeterKind.M3, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.M3METER_VALUE),
|
||||
|
||||
/** DSMR V5.0 GJ meter (heating, cooling) */
|
||||
GJ_V5_0(DSMRMeterKind.GJ, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.GJMETER_VALUE_V4),
|
||||
|
||||
/** DSMR V5.0 Slave Electricity meter */
|
||||
SLAVE_ELECTRICITY_V5_0(DSMRMeterKind.SLAVE_ELECTRICITY1, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.METER_DEVICE_TYPE, CosemObjectType.METER_EQUIPMENT_IDENTIFIER,
|
||||
CosemObjectType.EMETER_VALUE),
|
||||
|
||||
/** Luxembourg "Smarty" V1.0 Electricity meter */
|
||||
ELECTRICITY_SMARTY_V1_0(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER_V2_X,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER_V2_X, CosemObjectType.EMETER_DELIVERY_TARIFF0,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF0, CosemObjectType.EMETER_TOTAL_IMPORTED_ENERGY_REGISTER_Q,
|
||||
CosemObjectType.EMETER_TOTAL_EXPORTED_ENERGY_REGISTER_Q, CosemObjectType.EMETER_ACTUAL_DELIVERY,
|
||||
CosemObjectType.EMETER_ACTUAL_PRODUCTION, CosemObjectType.EMETER_ACTUAL_REACTIVE_DELIVERY,
|
||||
CosemObjectType.EMETER_ACTUAL_REACTIVE_PRODUCTION, CosemObjectType.EMETER_ACTIVE_THRESHOLD_SMAX,
|
||||
CosemObjectType.EMETER_SWITCH_POSITION },
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_POWER_FAILURES, CosemObjectType.EMETER_VOLTAGE_SAGS_L1,
|
||||
CosemObjectType.EMETER_VOLTAGE_SAGS_L2, CosemObjectType.EMETER_VOLTAGE_SAGS_L3,
|
||||
CosemObjectType.EMETER_VOLTAGE_SWELLS_L1, CosemObjectType.EMETER_VOLTAGE_SWELLS_L2,
|
||||
CosemObjectType.EMETER_VOLTAGE_SWELLS_L3, CosemObjectType.EMETER_INSTANT_CURRENT_L1,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L2, CosemObjectType.EMETER_INSTANT_CURRENT_L3,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L1, CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L2,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L1,
|
||||
CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L2, CosemObjectType.EMETER_INSTANT_POWER_PRODUCTION_L3,
|
||||
CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L1, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L2,
|
||||
CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_DELIVERY_L3, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L1,
|
||||
CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L2, CosemObjectType.EMETER_INSTANT_REACTIVE_POWER_PRODUCTION_L3,
|
||||
}),
|
||||
/** Belgium Smart Meter for the e-MUCS specification */
|
||||
DEVICE_EMUCS_V1_0(DSMRMeterKind.DEVICE, CosemObjectType.UNKNOWN,
|
||||
CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_TEXT_STRING, CosemObjectType.P1_EMUCS_VERSION_OUTPUT,
|
||||
CosemObjectType.P1_TIMESTAMP),
|
||||
|
||||
/** Belgium Smart Electricity Meter for the e-MUCS specification */
|
||||
ELECTRICITY_EMUCS_V1_0(DSMRMeterKind.MAIN_ELECTRICITY, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.EMETER_DELIVERY_TARIFF1,
|
||||
CosemObjectType.EMETER_DELIVERY_TARIFF2, CosemObjectType.EMETER_PRODUCTION_TARIFF1,
|
||||
CosemObjectType.EMETER_PRODUCTION_TARIFF2, CosemObjectType.EMETER_TARIFF_INDICATOR,
|
||||
CosemObjectType.EMETER_ACTUAL_DELIVERY, CosemObjectType.EMETER_ACTUAL_PRODUCTION,
|
||||
CosemObjectType.EMETER_TRESHOLD_KWH, CosemObjectType.EMETER_FUSE_THRESHOLD_A,
|
||||
CosemObjectType.EMETER_SWITCH_POSITION},
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L1, CosemObjectType.EMETER_INSTANT_CURRENT_L2,
|
||||
CosemObjectType.EMETER_INSTANT_CURRENT_L3, CosemObjectType.EMETER_INSTANT_VOLTAGE_L1,
|
||||
CosemObjectType.EMETER_INSTANT_VOLTAGE_L2, CosemObjectType.EMETER_INSTANT_VOLTAGE_L3
|
||||
}),
|
||||
|
||||
/** Belgium Smart Gas Meter for the e-MUCS specification */
|
||||
GAS_EMUCS_V1_0(DSMRMeterKind.GAS, CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER,
|
||||
new CosemObjectType[] {
|
||||
CosemObjectType.EMETER_EQUIPMENT_IDENTIFIER, CosemObjectType.METER_DEVICE_TYPE,
|
||||
CosemObjectType.GMETER_LAST_VALUE, CosemObjectType.METER_VALVE_SWITCH_POSITION });
|
||||
// @formatter:on
|
||||
|
||||
public static final Set<ThingTypeUID> METER_THING_TYPES = Arrays.asList(DSMRMeterType.values()).stream()
|
||||
.map(DSMRMeterType::getThingTypeUID).collect(Collectors.toSet());
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRMeterType.class);
|
||||
|
||||
/**
|
||||
* Meter kind
|
||||
*/
|
||||
public final DSMRMeterKind meterKind;
|
||||
|
||||
/**
|
||||
* Required objects for this meter type
|
||||
*/
|
||||
public final CosemObjectType[] requiredCosemObjects;
|
||||
|
||||
/**
|
||||
* Additional object this meter type can receive
|
||||
*/
|
||||
public final CosemObjectType[] optionalCosemObjects;
|
||||
|
||||
/**
|
||||
* All objects this meter type can receive (convenience for {requiredCosemObjects, optionalCosemObjects})
|
||||
*/
|
||||
public final CosemObjectType[] supportedCosemObjects;
|
||||
|
||||
/**
|
||||
* Which CosemObjectType is used to identify this meter
|
||||
*/
|
||||
public final CosemObjectType cosemObjectTypeMeterId;
|
||||
|
||||
/**
|
||||
* Creates a new enum
|
||||
*
|
||||
* @param meterKind kind of meter
|
||||
* @param cosemObjectTypeMeterId identifier cosem object
|
||||
* @param requiredCosemObjects list of objects that are present in this meter type
|
||||
*/
|
||||
DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
|
||||
CosemObjectType... requiredCosemObjects) {
|
||||
this(meterKind, cosemObjectTypeMeterId, requiredCosemObjects, new CosemObjectType[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new enum
|
||||
*
|
||||
* @param meterKind kind of meter
|
||||
* @param cosemObjectTypeMeterId identifier cosem object
|
||||
* @param requiredCosemObjects list of objects that are present in this meter type
|
||||
* @param optionalCosemObjects list of objects that are optional present in this meter type
|
||||
*/
|
||||
DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
|
||||
CosemObjectType[] requiredCosemObjects, CosemObjectType[] optionalCosemObjects) {
|
||||
this.meterKind = meterKind;
|
||||
this.cosemObjectTypeMeterId = cosemObjectTypeMeterId;
|
||||
this.requiredCosemObjects = requiredCosemObjects;
|
||||
this.optionalCosemObjects = optionalCosemObjects;
|
||||
|
||||
supportedCosemObjects = new CosemObjectType[requiredCosemObjects.length + optionalCosemObjects.length];
|
||||
System.arraycopy(requiredCosemObjects, 0, supportedCosemObjects, 0, requiredCosemObjects.length);
|
||||
System.arraycopy(optionalCosemObjects, 0, supportedCosemObjects, requiredCosemObjects.length,
|
||||
optionalCosemObjects.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this DSMRMeterType is compatible for the Cosem Objects.
|
||||
*
|
||||
* If successful the real OBIS identification message (including the actual channel and identification value)
|
||||
* is returned.
|
||||
* If the meter is compatible but the meter type has no identification message, a message is created using the
|
||||
* UNKNOWN OBISMsgType and no value.
|
||||
* If the meter is not compatible, null is returned
|
||||
*
|
||||
*
|
||||
* @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
|
||||
* @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter
|
||||
*/
|
||||
public DSMRMeterDescriptor isCompatible(Map<CosemObjectType, CosemObject> availableCosemObjects) {
|
||||
DSMRMeterDescriptor meterDescriptor = null;
|
||||
|
||||
for (CosemObjectType objectType : requiredCosemObjects) {
|
||||
if (!availableCosemObjects.containsKey(objectType)) {
|
||||
logger.trace("Required objectType {} not found", objectType);
|
||||
return null;
|
||||
} else {
|
||||
logger.trace("FOUND Required objectType {}", objectType);
|
||||
}
|
||||
CosemObject cosemObject = availableCosemObjects.get(objectType);
|
||||
|
||||
// Checking by reference is possible here due to comparing enums
|
||||
if (cosemObjectTypeMeterId != CosemObjectType.UNKNOWN && objectType == cosemObjectTypeMeterId) {
|
||||
meterDescriptor = new DSMRMeterDescriptor(this, cosemObject.getObisIdentifier().getGroupB());
|
||||
}
|
||||
}
|
||||
// Meter type is compatible, check if an identification exists
|
||||
if (meterDescriptor == null && cosemObjectTypeMeterId == CosemObjectType.UNKNOWN) {
|
||||
logger.trace("Meter type {} has no identification, but is compatible", this);
|
||||
meterDescriptor = new DSMRMeterDescriptor(this, DSMRMeterConstants.UNKNOWN_CHANNEL);
|
||||
} else if (meterDescriptor != null) {
|
||||
logger.trace("Meter type is compatible and has the following meter type:{}", this);
|
||||
}
|
||||
return meterDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ThingTypeUID for this meterType
|
||||
*
|
||||
* @return {@link ThingTypeUID} containing the unique identifier for this meter type
|
||||
*/
|
||||
public ThingTypeUID getThingTypeUID() {
|
||||
return new ThingTypeUID(DSMRBindingConstants.BINDING_ID, name().toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="dsmr" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>DSMR Binding</name>
|
||||
<description>This binding integrates Dutch and Luxembourg Smart Meters</description>
|
||||
<author>Marcel Volaart, Hilbrand Bouwkamp</author>
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:dsmr:bridgesettings">
|
||||
<parameter name="serialPort" type="text" required="true">
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<label>Serial Port</label>
|
||||
<description>The serial port where the P1 port of the Smart Meter is connected (e.g. Linux: /dev/ttyUSB0, Windows:
|
||||
COM1)</description>
|
||||
</parameter>
|
||||
<parameter name="receivedTimeout" type="integer" min="1">
|
||||
<default>30</default>
|
||||
<label>Received Timeout</label>
|
||||
<description>The time period within results are expected in seconds</description>
|
||||
</parameter>
|
||||
<parameter name="baudrate" type="integer">
|
||||
<advanced>true</advanced>
|
||||
<label>Baud Rate</label>
|
||||
<description>Set the baud rate in case auto detect fails.</description>
|
||||
<options>
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600">9600</option>
|
||||
<option value="19200">19200</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="57600">57600</option>
|
||||
<option value="115200">115200</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="databits" type="integer">
|
||||
<advanced>true</advanced>
|
||||
<label>Data Bits</label>
|
||||
<description>Set the data bits in case auto detect fails.</description>
|
||||
<options>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="parity" type="text">
|
||||
<advanced>true</advanced>
|
||||
<label>Parity</label>
|
||||
<description>Set the parity in case auto detect fails.</description>
|
||||
<options>
|
||||
<option value="E">E(ven)</option>
|
||||
<option value="O">O(dd)</option>
|
||||
<option value="N">N(ormal)</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="stopbits" type="text">
|
||||
<advanced>true</advanced>
|
||||
<label>Stop Bits</label>
|
||||
<description>Set the stop bits in case auto detect fails.</description>
|
||||
<options>
|
||||
<option value="1">1</option>
|
||||
<option value="1.5">1.5</option>
|
||||
<option value="2">2</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:dsmr:smartybridgesettings">
|
||||
<parameter name="serialPort" type="text" required="true">
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<label>Serial Port</label>
|
||||
<description>The serial port where the P1 port of the Smart Meter is connected (e.g. Linux: /dev/ttyUSB0, Windows:
|
||||
COM1)</description>
|
||||
</parameter>
|
||||
<parameter name="decryptionKey" type="text" required="true">
|
||||
<label>Decryption Key</label>
|
||||
<description>The Luxembourgian Smart meter decryption key. Ask for your energy grid operator for your Smart meter P1
|
||||
key.</description>
|
||||
</parameter>
|
||||
<parameter name="receivedTimeout" type="integer" min="1">
|
||||
<default>30</default>
|
||||
<label>Received Timeout</label>
|
||||
<description>The time period within results are expected in seconds</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:dsmr:meterdescriptor">
|
||||
<parameter name="refresh" type="integer" min="1">
|
||||
<default>60</default>
|
||||
<label>Refresh</label>
|
||||
<description>The time interval the data is refreshed in seconds</description>
|
||||
</parameter>
|
||||
<parameter name="channel" type="integer">
|
||||
<advanced>true</advanced>
|
||||
<label>Channel</label>
|
||||
<description><![CDATA[ The DSMR-device channel for this meter (M-Bus channel).
|
||||
The binding will auto detect this value. In normal situations it is not necessary to adapt this value.
|
||||
If the auto detection failed or if physical changes are made to the meter setup (changed water, gas, heating) meters
|
||||
it can be necessary to update the M-Bus channel.]]></description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,32 @@
|
||||
# Text used in the source code.
|
||||
|
||||
# meter kind names
|
||||
meterKind.invalid.label = Invalid Meter
|
||||
meterKind.device.label = Generic DSMR Device
|
||||
meterKind.main_electricity.label = Main Electricity Meter
|
||||
meterKind.gas.label = Gas Meter
|
||||
meterKind.heating.label = Heating Meter
|
||||
meterKind.cooling.label = Cooling Meter
|
||||
meterKind.water.label = Water Meter
|
||||
meterKind.generic.label = Generic Meter
|
||||
meterKind.gj.label = GJ Meter
|
||||
meterKind.m3.label = M3 Meter
|
||||
meterKind.slave_electricity1.label = Slave Electricity Meter
|
||||
meterKind.slave_electricity2.label = Slave Electricity Meter 2
|
||||
|
||||
# Connector error messages
|
||||
error.bridge.nodata = Not receiving data from meter.
|
||||
error.configuration.invalidmetertype = The thing could not be initialized. Delete and re-add thing if the problem persists.
|
||||
error.configuration.invalidsmartykey = The given Smarty decyption key is to short. The decyption key must be 32 characters long.
|
||||
error.thing.nodata = Not receiving data from meter.
|
||||
error.connector.dont_exists = Serial port does not exist.
|
||||
error.connector.in_use = Serial port is already in use.
|
||||
error.connector.internal_error = Unexpected error, possible bug. Please report.
|
||||
error.connector.not_compatible = Serial port is not compatible.
|
||||
error.connector.read_error = Read error.
|
||||
|
||||
# thing types
|
||||
thing-type.dsmr.dsmrBridge.label = Smart Meter
|
||||
thing-type.dsmr.dsmrBridge.description = The Dutch Smart Meter (DSMR)
|
||||
thing-type.dsmr.smartyBridge.label = Smarty Meter
|
||||
thing-type.dsmr.smartyBridge.description = The Luxembourgian Smart Meter 'Smarty'
|
||||
@@ -0,0 +1,331 @@
|
||||
# binding
|
||||
binding.dsmr.name = Slimme Meter Binding
|
||||
binding.dsmr.description = Dit is de binding voor de Nederlandse Slimme Meter (DSMR).
|
||||
|
||||
# meter kind names
|
||||
meterKind.invalid.label = Onbekende Meter
|
||||
meterKind.device.label = Algemene DSMR Meter
|
||||
meterKind.main_electricity.label = Elektriciteitsmeter
|
||||
meterKind.gas.label = Gasmeter
|
||||
meterKind.heating.label = Verwamingsmeter
|
||||
meterKind.cooling.label = Koelingsmeter
|
||||
meterKind.water.label = Watermeter
|
||||
meterKind.generic.label = Algemene Meter
|
||||
meterKind.gj.label = GJ Meter
|
||||
meterKind.m3.label = M3 Meter
|
||||
meterKind.slave_electricity1.label = Extra Elektriciteitsmeter 1
|
||||
meterKind.slave_electricity2.label = Extra Elektriciteitsmeter 2
|
||||
|
||||
# Connector error messages
|
||||
error.bridge.nodata = Geen gegevens ontvangen van de meter.
|
||||
error.configuration.invalidmetertype = Het ding kon niet worden geïnitialiseerd. Verwijder en voeg het ding opnieuw toe als het probleem zich blijft voordoen.
|
||||
error.configuration.invalidsmartykey = De opgegeven Smarty decyptie key is niet correct. De decyptie key moet 32 karakters lang zijn.
|
||||
error.thing.nodata = Geen gegevens ontvangen van de meter.
|
||||
error.connector.dont_exists = Seriële poort bestaat niet.
|
||||
error.connector.in_use = Seriële poort is al in gebruik.
|
||||
error.connector.internal_error = Onverwachte fout, mogelijk een fout in de binding. Maak hier een melding van.
|
||||
error.connector.not_compatible = Seriële poort is niet compatibel.
|
||||
error.connector.read_error = Leesfout.
|
||||
|
||||
# thing types
|
||||
thing-type.dsmr.dsmrBridge.label = Slimme Meter
|
||||
thing-type.dsmr.dsmrBridge.description = De Slimme Meter (DSMR Nederland, e-MUCS Belgie)
|
||||
thing-type.dsmr.smartyBridge.label = Smarty Meter
|
||||
thing-type.dsmr.smartyBridge.description = De luxemburgse Slimme Meter 'Smarty'
|
||||
|
||||
thing-type.dsmr.cooling_ace4000.label = Koudemeter (ACE4000)
|
||||
thing-type.dsmr.cooling_ace4000.description = Dit is een koudemeter die voldoet aan de ACE4000 GTMM Mk3 standaard.
|
||||
thing-type.dsmr.cooling_v2_2.label = Koudemeter (DSMR V2.2)
|
||||
thing-type.dsmr.cooling_v2_2.description = Dit is een koudemeter die voldoet aan de DSMR V2.2 standaard.
|
||||
thing-type.dsmr.device_v2_v3.label = Meter (DSMR V2.x or V3.0)
|
||||
thing-type.dsmr.device_v2_v3.description = Meter die voldoet aan de DSMR V2.x or V3 standaard.
|
||||
thing-type.dsmr.device_v4.label = Meter (DSMR V4.x)
|
||||
thing-type.dsmr.device_v4.description = Meter die voldoet aan de DSMR V4 standaard.
|
||||
thing-type.dsmr.device_v5.label = Meter (DSMR V5)
|
||||
thing-type.dsmr.device_v5.description = Meter die voldoet aan de DSMR V5 standaard.
|
||||
thing-type.dsmr.electricity_ace4000.label = Elektriciteitsmeter (ACE4000)
|
||||
thing-type.dsmr.electricity_ace4000.description = Dit is een elektriciteitsmeter die voldoet aan de ACE4000 GTMM Mk3 standaard.
|
||||
thing-type.dsmr.electricity_v2_1.label = Elektriciteitsmeter (DSMR V2.1)
|
||||
thing-type.dsmr.electricity_v2_1.description = Dit is een elektriciteitsmeter die voldoet aan de DSMR V2.1 standaard.
|
||||
thing-type.dsmr.electricity_v2_2.label = Elektriciteitsmeter (DSMR V2.2)
|
||||
thing-type.dsmr.electricity_v2_2.description = Dit is een elektriciteitsmeter die voldoet aan de DSMR V2.2 standaard.
|
||||
thing-type.dsmr.electricity_v3_0.label = Elektriciteitsmeter (DSMR V3)
|
||||
thing-type.dsmr.electricity_v3_0.description = Dit is een elektriciteitsmeter die voldoet aan de DSMR V3 standaard.
|
||||
thing-type.dsmr.electricity_v4_0.label = Elektriciteitsmeter (DSMR V4.0)
|
||||
thing-type.dsmr.electricity_v4_0.description = Dit is een elektriciteitsmeter die voldoet aan de DSMR V4.0 standaard.
|
||||
thing-type.dsmr.electricity_v4_0_4.label = Elektriciteitsmeter (DSMR V4.0.4)
|
||||
thing-type.dsmr.electricity_v4_0_4.description = Dit is een elektriciteitsmeter die voldoet aan de DSMR V4.0.4 standaard.
|
||||
thing-type.dsmr.electricity_v4_2.label = Elektriciteitsmeter (DSMR V4.2)
|
||||
thing-type.dsmr.electricity_v4_2.description = Dit is een elektriciteitsmeter die voldoet aan de DSMR V4.2 standaard.
|
||||
thing-type.dsmr.electricity_v5_0.label = Elektriciteitsmeter (DSMR V5.0)
|
||||
thing-type.dsmr.electricity_v5_0.description = Dit is een elektriciteitsmeter die voldoet aan de DSMR V5.0 standaard.
|
||||
thing-type.dsmr.slave_electricity1_ace4000.label = Extra elektriciteitsmeter 1 (ACE4000)
|
||||
thing-type.dsmr.slave_electricity1_ace4000.description = Dit is de extra elektriciteitsmeter 1 die voldoet aan de ACE4000 GTMM Mk3 standaard.
|
||||
thing-type.dsmr.slave_electricity2_ace4000.label = Extra Elektriciteitsmeter 2 (ACE4000)
|
||||
thing-type.dsmr.slave_electricity2_ace4000.description = Dit is de extra elektriciteitsmeter 2 die voldoet aan de ACE4000 GTMM Mk3 standaard.
|
||||
thing-type.dsmr.slave_electricity_v4.label = Extra Elektriciteitsmeter (DSMR V4.x)
|
||||
thing-type.dsmr.slave_electricity_v4.description = Dit is de extra elektriciteitsmeter die voldoet aan de DSMR 4.x standaard.
|
||||
thing-type.dsmr.slave_electricity_v5.label = Extra Elektriciteitsmeter (DSMR V5.x)
|
||||
thing-type.dsmr.slave_electricity_v5.description = Dit is de extra elektriciteitsmeter die voldoet aan de DSMR 5.x standaard.
|
||||
thing-type.dsmr.gas_ace4000.label = Gasmeter (ACE4000)
|
||||
thing-type.dsmr.gas_ace4000.description = Dit is een gasmeter die voldoet aan de ACE4000 GTMM Mk3 standaard.
|
||||
thing-type.dsmr.gas_v2_1.label = Gasmeter (DSMR V2.1)
|
||||
thing-type.dsmr.gas_v2_1.description = Dit is een gasmeter die voldoet aan de DSMR V2.1 standaard.
|
||||
thing-type.dsmr.gasv2_2.label = Gasmeter (DSMR V2.2)
|
||||
thing-type.dsmr.gasv2_2.description = Dit is een gasmeter die voldoet aan de DSMR V2.2 standaard.
|
||||
thing-type.dsmr.gas_v3_0.label = Gasmeter (DSMR V3.0)
|
||||
thing-type.dsmr.gas_v3_0.description = Dit is een gasmeter die voldoet aan de DSMR V3.0 standaard.
|
||||
thing-type.dsmr.generic_v3_0.label = Generiek Meter (DSMR V3.0)
|
||||
thing-type.dsmr.generic_v3_0.description = Dit is een generieke meter die voldoet aan de DSMR V3.0 standaard.
|
||||
thing-type.dsmr.gj_v3_0.label = Energiemeter (DSMR V3.0)
|
||||
thing-type.dsmr.gj_v3_0.description = Dit is een Giga Joule meter die voldoet aan de DSMR V3.0 standaard.
|
||||
thing-type.dsmr.gj_v4.label = Energiemeter (DSMR V4.x)
|
||||
thing-type.dsmr.gj_v4.description = Dit is een Giga Joule meter die voldoet aan de DSMR V4.x standaard.
|
||||
thing-type.dsmr.gj_v5_0.label = Energiemeter (DSMR V5.0)
|
||||
thing-type.dsmr.gj_v5_0.description = Dit is een enegriemeter die voldoet aan de DSMR V5.0 standaard.
|
||||
thing-type.dsmr.heating_ace4000.label = Warmtemeter (ACE4000)
|
||||
thing-type.dsmr.heating_ace4000.description = Dit is een warmtemeter die voldoet aan de ACE4000 GTMM Mk3 standaard.
|
||||
thing-type.dsmr.heating_v2_2.label = Warmtemeter (DSMR V2.2)
|
||||
thing-type.dsmr.heating_v2_2.description = Dit is een warmtemeter die voldoet aan de DSMR V2.2 standaard.
|
||||
thing-type.dsmr.m3_v4.label = Volumemeter (DSMR V4.x)
|
||||
thing-type.dsmr.m3_v4.description = Dit is een m3 meter die voldoet aan de DSMR V4.x standaard.
|
||||
thing-type.dsmr.m3_v5_0.label = Volumemeter (DSMR V5.0)
|
||||
thing-type.dsmr.m3_v5_0.description = Dit is een volumemeter (m3) die voldoet aan de DSMR V5.0 standaard.
|
||||
thing-type.dsmr.water_ace4000.label = Watermeter (ACE4000)
|
||||
thing-type.dsmr.water_ace4000.description = Dit is een watermeter die voldoet aan de ACE4000 GTMM Mk3 standaard.
|
||||
thing-type.dsmr.water_v2_2.label = Watermeter (DSMR V2.2)
|
||||
thing-type.dsmr.water_v2_2.description = Dit is een watermeter die voldoet aan de DSMR V2.2 standaard.
|
||||
thing-type.dsmr.water_v3_0.label = Watermeter (DSMR V3.0)
|
||||
thing-type.dsmr.water_v3_0.description = Dit is een watermeter die voldoet aan de DSMR V3.0 standaard.
|
||||
|
||||
# bridge configuration settings
|
||||
thing-type.config.dsmr.bridgesettings.serialPortAdvanced.label = Seriële Poort Instellingen
|
||||
thing-type.config.dsmr.bridgesettings.serialPortAdvanced.description = In het geval automatische detectie van seriële poort faalt kan hiermee de poort handmatig worden ingesteld.
|
||||
thing-type.config.dsmr.bridgesettings.serialPort.label = Seriële Poort
|
||||
thing-type.config.dsmr.bridgesettings.serialPort.description = De seriële poort waar de P1 poort van de slimme meter op is aangesloten. (Linux: /dev/ttyUSB0, Windows: COM1)
|
||||
thing-type.config.dsmr.bridgesettings.receivedTimeout.label = Ontvangst Time-out
|
||||
thing-type.config.dsmr.bridgesettings.receivedTimeout.description = De tijdsperiode waarbinnen nieuwe berichten verwacht worden.
|
||||
thing-type.config.dsmr.bridgesettings.baudrate.label = Baudrate
|
||||
thing-type.config.dsmr.bridgesettings.baudrate.description = De seriële poort baudrate (4800, 9600, 19200, 38400, 57600 of 115200).
|
||||
thing-type.config.dsmr.bridgesettings.databits.label = Data Bits
|
||||
thing-type.config.dsmr.bridgesettings.databits.description = De seriële poort data bits (5,6,7 of 8).
|
||||
thing-type.config.dsmr.bridgesettings.parity.label = Pariteit
|
||||
thing-type.config.dsmr.bridgesettings.parity.description = De seriële poort pariteit (E(ven), O(dd) of N(ormal)).
|
||||
thing-type.config.dsmr.bridgesettings.stopbits.label = Stop Bits
|
||||
thing-type.config.dsmr.bridgesettings.stopbits.description = De seriële poort stop bits (1, 1.5 of 2).
|
||||
|
||||
# meter configuration settings
|
||||
thing-type.config.dsmr.meterdescriptor.refresh.label = Gegevensverversen
|
||||
thing-type.config.dsmr.meterdescriptor.refresh.description = De tijdsinverval waarmee de gegevens worden ververst.
|
||||
thing-type.config.dsmr.meterdescriptor.channel.label = Kanaal
|
||||
thing-type.config.dsmr.meterdescriptor.channel.description = Het DSMR-apparaat kanaal voor deze meter (M-Bus kanaal). De binding zal deze waarde automatisch detecteren. In normale omstandigheden is het niet nodig deze waarde aan te passen. Als automatisch detecteren faalt of er zijn wijzigingen aan de meteropstelling gemaakt (veranderd water, gas, verwaming) kan het nodig zijn om het M-Bus kanaal aan te passen.
|
||||
|
||||
# channels
|
||||
|
||||
channel-type.dsmr.coolingValueType.label = Koelingsverbruik
|
||||
channel-type.dsmr.coolingValueType.description = Het totaal aantal GJ verbruikte koeling.
|
||||
|
||||
channel-type.dsmr.deliveryType.label = Elektriciteitsverbruik
|
||||
channel-type.dsmr.deliveryType.description = Het totale elektriciteitsverbruik.
|
||||
channel-type.dsmr.deliveryTariff0Type.label = Elektriciteitsverbruik Tarief 0
|
||||
channel-type.dsmr.deliveryTariff0Type.description = Het totale elektriciteitsverbruik voor tarief 0.
|
||||
channel-type.dsmr.deliveryTariff1Type.label = Elektriciteitsverbruik Daltarief
|
||||
channel-type.dsmr.deliveryTariff1Type.description = Het totale elektriciteitsverbruik voor dal/nacht tarief 1.
|
||||
channel-type.dsmr.deliveryTariff1BelgiumType.label = Elektriciteitsverbruik Piektarief
|
||||
channel-type.dsmr.deliveryTariff1BelgiumType.description = Het totale elektriciteitsverbruik voor piek/dag tarief 1.
|
||||
channel-type.dsmr.deliveryTariff2Type.label = Elektriciteitsverbruik Piektarief
|
||||
channel-type.dsmr.deliveryTariff2Type.description = Het totale elektriciteitsverbruik voor piek/dag tarief 2.
|
||||
channel-type.dsmr.deliveryTariff2BelgiumType.label = Elektriciteitsverbruik Daltarief
|
||||
channel-type.dsmr.deliveryTariff2BelgiumType.description = Het totale elektriciteitsverbruik voor dal/nacht tarief 2.
|
||||
channel-type.dsmr.deliveryTariff0AntiFraudType.label = Elektriciteitsverbruik Tarief 0
|
||||
channel-type.dsmr.deliveryTariff0AntiFraudType.description = Het totale elektriciteitsverbruik voor tarief 0 (anti-fraude).
|
||||
channel-type.dsmr.deliveryTariff1AntiFraudType.label = Elektriciteitsverbruik Daltarief
|
||||
channel-type.dsmr.deliveryTariff1AntiFraudType.description = Het totale elektriciteitsverbruik voor dal/nacht tarief 1 (anti-fraude).
|
||||
channel-type.dsmr.deliveryTariff1AntiFraudType.label = Elektriciteitsverbruik Piektarief
|
||||
channel-type.dsmr.deliveryTariff1AntiFraudType.description = Het totale elektriciteitsverbruik voor piek/dag tarief 2 (anti-fraude).
|
||||
channel-type.dsmr.productionTariff0Type.label = Teruggeleverde Elektriciteit Tarief 0
|
||||
channel-type.dsmr.productionTariff0Type.description = De totale teruggeleverde elektriciteit voor tarief 0.
|
||||
channel-type.dsmr.productionTariff1Type.label = Teruggeleverde Elektriciteit Daltarief
|
||||
channel-type.dsmr.productionTariff1Type.description = De totale teruggeleverde elektriciteit voor dal/nacht tarief (1).
|
||||
channel-type.dsmr.productionTariff1BelgiumType.label = Injectie Piektarief
|
||||
channel-type.dsmr.productionTariff1BelgiumType.description = De totale injectie elektriciteit voor piek/dag tarief (1).
|
||||
channel-type.dsmr.productionTariff2Type.label = Teruggeleverde Elektriciteit Piektarief
|
||||
channel-type.dsmr.productionTariff2Type.description = De totale teruggeleverde elektriciteit voor piek/dag tarief (2).
|
||||
channel-type.dsmr.productionTariff2BelgiumType.label = Injectie Daltarief
|
||||
channel-type.dsmr.productionTariff2BelgiumType.description = De totale injectie elektriciteit voor dal/nacht tarief (2).
|
||||
channel-type.dsmr.tariffIndicatorType.label = Tarief Indicator
|
||||
channel-type.dsmr.tariffIndicatorType.description = De tarief indicatie van het huidge tarief (1 = dal/nacht, 2 = piek/dag).
|
||||
channel-type.dsmr.activeImportPowerType.label = Opgetelde Huidige Elektriciteit
|
||||
channel-type.dsmr.activeImportPowerType.description = De huidige opgetelde elektriciteit.
|
||||
channel-type.dsmr.actualDeliveryType.label = Huidige Elektriciteitverbruik
|
||||
channel-type.dsmr.actualDeliveryType.description = De huidige elektriciteitverbruik.
|
||||
channel-type.dsmr.actualProductionType.label = Huidige Teruggeleverde Elektriciteit
|
||||
channel-type.dsmr.actualProductionType.description = Huidige teruggeleverde elektriciteit.
|
||||
channel-type.dsmr.actualTresholdAType.label = Huidige Drempel Stroom
|
||||
channel-type.dsmr.actualTresholdAType.description = De huidige drempel Stroom.
|
||||
channel-type.dsmr.actualFuseThresholdAType.label = Huidige Zekering Drempel
|
||||
channel-type.dsmr.actualFuseThresholdAType.description = De huidige drempel van de zekering.
|
||||
channel-type.dsmr.actualTresholdkWType.label = Huidige Drempel
|
||||
channel-type.dsmr.actualTresholdkWType.description = De huidige drempel.
|
||||
channel-type.dsmr.switchPositionType.label = Schakelaarstand
|
||||
channel-type.dsmr.switchPositionType.description = De stand van de schakelaar.
|
||||
channel-type.dsmr.powerFailuresType.label = Aantal Stroomuitvallen
|
||||
channel-type.dsmr.powerFailuresType.description = Het aatal maal dat de stroom is uitgevallen.
|
||||
channel-type.dsmr.longPowerFailuresType.label = Aantal Lange Stroomuitvallen
|
||||
channel-type.dsmr.longPowerFailuresType.description = Het aantal maal dat de stroom lang is uitgevallen.
|
||||
channel-type.dsmr.powerFailureLogEntriesType.label = Stroomuitval Registraties
|
||||
channel-type.dsmr.powerFailureLogEntriesType.description = Aantal registraties van de stroomuitval.
|
||||
channel-type.dsmr.powerFailureLogEndType.label = Stroomuitval Einde
|
||||
channel-type.dsmr.powerFailureLogEndType.description = Tijdstip waarop de stroomuitval eindigde. Voor de laatste 10 incidenten is er een eigen kanaal (emeter_power_failure_log_timestamp*x*, *x* = 0 - 9)
|
||||
channel-type.dsmr.powerFailureLogDurationType.label = Stroomuitvalduur
|
||||
channel-type.dsmr.powerFailureLogDurationType.description = Duur van de stroomstoring. Voor de laatste 10 incidenten is er een eigen kanaal (emeter_power_failure_log_duration*x*, *x* = 0 - 9)
|
||||
channel-type.dsmr.voltageSagsL1Type.label = Aantal Spanningsdips L1
|
||||
channel-type.dsmr.voltageSagsL1Type.description = Het aantal spanningsdips fase L1.
|
||||
channel-type.dsmr.voltageSagsL2Type.label = Aantal Spanningsdips L2
|
||||
channel-type.dsmr.voltageSagsL2Type.description = Het aantal spanningsdips fase L2.
|
||||
channel-type.dsmr.voltageSagsL3Type.label = Aantal Spanningsdips L3
|
||||
channel-type.dsmr.voltageSagsL3Type.description = Het aantal spanningsdips fase L3.
|
||||
channel-type.dsmr.voltageSwellsL1Type.label = Aantal Spanningspieken L1
|
||||
channel-type.dsmr.voltageSwellsL1Type.description = Het aantal spanningspieken fase L1.
|
||||
channel-type.dsmr.voltageSwellsL2Type.label = Aantal Spanningspieken L2
|
||||
channel-type.dsmr.voltageSwellsL2Type.description = Het aantal spanningspieken fase L2.
|
||||
channel-type.dsmr.voltageSwellsL3Type.label = Aantal Spanningspieken L3
|
||||
channel-type.dsmr.voltageSwellsL3Type.description = Het aantal spanningspieken fase L3.
|
||||
channel-type.dsmr.instantCurrentL1Type.label = Stroomverbruik L1
|
||||
channel-type.dsmr.instantCurrentL1Type.description = Het huidige stroomverbruik fase L1.
|
||||
channel-type.dsmr.instantCurrentL2Type.label = Stroomverbruik L2
|
||||
channel-type.dsmr.instantCurrentL2Type.description = Het huidige stroomverbruik fase L2.
|
||||
channel-type.dsmr.instantCurrentL3Type.label = Stroomverbruik L3
|
||||
channel-type.dsmr.instantCurrentL3Type.description = Het huidige stroomverbruik fase L3.
|
||||
channel-type.dsmr.instantPowerDeliveryL1Type.label = Vermogen L1
|
||||
channel-type.dsmr.instantPowerDeliveryL1Type.description = Het huidige vermogen fase L1.
|
||||
channel-type.dsmr.instantPowerDeliveryL2Type.label = Vermogen L2
|
||||
channel-type.dsmr.instantPowerDeliveryL2Type.description = Het huidige vermogen fase L2.
|
||||
channel-type.dsmr.instantPowerDeliveryL3Type.label = Vermogen L3
|
||||
channel-type.dsmr.instantPowerDeliveryL3Type.description = Het huidige vermogen fase L3.
|
||||
channel-type.dsmr.instantPowerProductionL1Type.label = Vermogenproductie L1
|
||||
channel-type.dsmr.instantPowerProductionL1Type.description = Het huidige vermogenproductie fase L1.
|
||||
channel-type.dsmr.instantPowerProductionL2Type.label = Vermogenproductie L2
|
||||
channel-type.dsmr.instantPowerProductionL2Type.description = Het huidige vermogenproductie fase L2.
|
||||
channel-type.dsmr.instantPowerProductionL3Type.label = Vermogenproductie L3
|
||||
channel-type.dsmr.instantPowerProductionL3Type.description = Het huidige vermogenproductie fase L3.
|
||||
channel-type.dsmr.instantVoltageL1Type.label = Spanning L1
|
||||
channel-type.dsmr.instantVoltageL1Type.description = De huidige spanning fase L1.
|
||||
channel-type.dsmr.instantVoltageL2Type.label = Spanning L2
|
||||
channel-type.dsmr.instantVoltageL2Type.description = De huidige spanning fase L2.
|
||||
channel-type.dsmr.instantVoltageL3Type.label = Spanning L3
|
||||
channel-type.dsmr.instantVoltageL3Type.description = De huidige spanning fase L3.
|
||||
|
||||
channel-type.dsmr.gasDelivery24HType.label = Gasverbruik 24 Uur
|
||||
channel-type.dsmr.gasDelivery24HType.description = Het totaal aantal m3 gasverbruik in de afgelopen 24 uur.
|
||||
channel-type.dsmr.gasCompensatedDelivery24HType.label = Gecompenseerd Gasverbruik 24 Uur
|
||||
channel-type.dsmr.gasCompensatedDelivery24HType.description = Het totaal gecompenseerd gasverbruik in kubieke meter (m3) in de afgelopen 24 uur.
|
||||
channel-type.dsmr.gasDeliveryType.label = Gasverbruik
|
||||
channel-type.dsmr.gasDeliveryType.description = Het totaal aantal kubieke meter (m3) gasverbruik in de afgelopen periode.
|
||||
channel-type.dsmr.gasLastDeliveryType.label = Gasverbruik
|
||||
channel-type.dsmr.gasLastDeliveryType.description = Het totaal niet temperatuur gecorrigeerd aantal kubieke meter (m3) gasverbruik.
|
||||
channel-type.dsmr.gasLastTimestampType.label = Tijd Gasmeting
|
||||
channel-type.dsmr.gasLastTimestampType.description = Tijd van de laatste niet temperatuur gecorrigeerd gasverbruik registratie.
|
||||
|
||||
channel-type.dsmr.gasValvePositionType.label = Gasklepstand
|
||||
channel-type.dsmr.gasValvePositionType.description = De stand van de gasklep.
|
||||
|
||||
channel-type.dsmr.deviceType.label = Apparaattype
|
||||
channel-type.dsmr.deviceType.description = Het soort apparaat.
|
||||
channel-type.dsmr.equipmentIdType.label = Apparatuur ID
|
||||
channel-type.dsmr.equipmentIdType.description = Identificatenummer van het apparaat.
|
||||
channel-type.dsmr.p1TextStringType.label = Tekstbericht
|
||||
channel-type.dsmr.p1TextStringType.description = Tekstbericht van het apparaat.
|
||||
channel-type.dsmr.p1TextCodeType.label = Tekstcode
|
||||
channel-type.dsmr.p1TextCodeType.description = Tekstcode van het apparaat.
|
||||
channel-type.dsmr.p1VersionType.label = P1 Versie
|
||||
channel-type.dsmr.p1VersionType.description = Versie informatie van de uitvoer het apparaat (Meestal refereert dit aan de DSMR standaard).
|
||||
channel-type.dsmr.p1TimestampType.label = Tijd
|
||||
channel-type.dsmr.p1TimestampType.description = Tijd van de laatste meteraflezing.
|
||||
|
||||
channel-type.dsmr.gjValueType.label = Energieverbruik
|
||||
channel-type.dsmr.gjValueType.description = Het totale energieverbruik in de afgelopen periode.
|
||||
channel-type.dsmr.gjValvePositionType.label = Klepstand
|
||||
channel-type.dsmr.gjValvePositionType.description = De stand van de klep.
|
||||
|
||||
channel-type.dsmr.heatingValueType.label = Warmteverbruik
|
||||
channel-type.dsmr.heatingValueType.description = Het totale warmteverbruik in de afgelopen periode.
|
||||
|
||||
channel-type.waterValueType.label = Waterverbruik
|
||||
channel-type.waterValueType.description = Het totale waterverbruik in de afgelopen periode.
|
||||
channel-type.waterValvePositionType.label = Waterklepstand
|
||||
channel-type.waterValvePositionType.description = De stand van de waterklepafsluiter.
|
||||
|
||||
|
||||
Total imported energy register (P+) 1, 0:1.8.0.255 2 3 F9(3,3) kWh x
|
||||
Total exported energy register (P-) 1, 0:2.8.0.255 2 3 F9(3,3) kWh x
|
||||
Total imported energy register (Q+) 1, 0:3.8.0.255 2 3 F9(3,3) kvarh x
|
||||
Total exported energy register (Q-) 1, 0:4.8.0.255 2 3 F9(3,3) kvarh x
|
||||
Instantaneous imported active power (P+) 1, 0:1.7.0.255 2 3 F5(3,3) kW x
|
||||
Instantaneous exported active power (P-) 1, 0:2.7.0.255 2 3 F5(3,3) kW x
|
||||
Instantaneous imported reactive power (Q+) 1, 0:3.7.0.255 2 3 F5(3,3) kvar x
|
||||
Instantaneous exported reactive power (Q-) 1, 0:4.7.0.255 2 3 F5(3,3) kvar x
|
||||
Active threshold (SMAX) 0, 0:17.0.0.255 3 71 F4(1,1) kVA x
|
||||
Breaker control state 0, 0:96.3.10.255 3 70 I1 - x
|
||||
Number of power failures 0, 0:96.7.21.255 2 1 F5(0,0) -
|
||||
Number of voltage sags L1 0, 0:32.32.0.255 2 1 F5(0,0) -
|
||||
Number of voltage sags L2 0, 0:52.32.0.255 2 1 F5(0,0)
|
||||
|
||||
Number of voltage sags L3
|
||||
0, 0:72.32.0.255
|
||||
Number of voltage swells L1
|
||||
0, 0:32.36.0.255
|
||||
Number of voltage swells L2
|
||||
0, 0:52.36.0.255
|
||||
Number of voltage swells L3
|
||||
0, 0:72.36.0.255
|
||||
Long message E
|
||||
-meter
|
||||
0, 0:96.13.0.255
|
||||
Long message channel x
|
||||
0, 0:96.13.x.255
|
||||
Instantaneous current L1
|
||||
1, 0:31.7.0.255
|
||||
Instantaneous current L2
|
||||
1, 0:51.7.0.255
|
||||
Instantaneous current L3
|
||||
1, 0:71.7.0.255
|
||||
Instantaneous active power (P+) L1
|
||||
1, 0:21.7.0.255
|
||||
Instantaneous active power (P+) L2
|
||||
1, 0:41.7.0.255
|
||||
Instantaneous active power (P+) L3
|
||||
1, 0:61.7.0.255
|
||||
Instantaneous active power (P-) L1
|
||||
1, 0:22.7.0.255
|
||||
Instantaneous active power (P-) L2
|
||||
1, 0:42.7.0.255
|
||||
Instantaneous active power (P-) L3
|
||||
1, 0:62.7.0.255
|
||||
Instantaneous reactive power (Q+) L1
|
||||
1, 0:23.7.0.255
|
||||
Instantaneous reactive power (Q+) L2
|
||||
1, 0:43.7.0.255
|
||||
Instantaneous reactive power (Q+) L3
|
||||
1, 0:63.7.0.255
|
||||
Instantaneous reactive power (Q-) L1
|
||||
1, 0:24.7.0.255
|
||||
Instantaneous reactive power (Q-) L2
|
||||
1, 0:44.7.0.255
|
||||
Instantaneous reactive power (Q-) L3
|
||||
1, 0:64.7.0.255
|
||||
Device type channel x
|
||||
0, x:24.1.0.255
|
||||
Equipment Identifier channel x
|
||||
0, x:96.1.0.255
|
||||
Last Index capture
|
||||
-time channel x
|
||||
0, x:24.2.1.255
|
||||
Last Index gas channel x
|
||||
0, x:24.2.1.255
|
||||
valve position gas channel x
|
||||
0, x:24.4.0.255
|
||||
Last Index water channel x
|
||||
0, x:24.2.1.255
|
||||
Last Index heat channel x
|
||||
0, x:24.2.1.255
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="coolingValueType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Cooling Delivery (GJ)</label>
|
||||
<description>The total amount of cooling used.</description>
|
||||
<state pattern="%.3f GJ" readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,358 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
<channel-type id="deliveryType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery</label>
|
||||
<description>The total amount of electricity used.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff0Type">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery Tariff 0</label>
|
||||
<description>The total amount of electricity used for tariff 0.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff1Type">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery Tariff 1</label>
|
||||
<description>The total amount of electricity used for tariff 1.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff2Type">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery Tariff 2</label>
|
||||
<description>The total amount of electricity used for tariff 2.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff1BelgiumType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery Tariff 1</label>
|
||||
<description>The total amount of electricity used for tariff 1.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff2BelgiumType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery Tariff 2</label>
|
||||
<description>The total amount of electricity used for tariff 2.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff0AntiFraudType" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery Tariff 0</label>
|
||||
<description>The total amount of electricity used for tariff 0 (anti fraud).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff1AntiFraudType" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Delivery Tariff 1</label>
|
||||
<description>The total amount of electricity used for tariff 1 (anti fraud).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deliveryTariff2AntiFraudType" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery Tariff 2</label>
|
||||
<description>The total amount of electricity used for tariff 2 (anti fraud).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="productionTariff0Type" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Production Tariff 0</label>
|
||||
<description>The total amount of electricity produced for tariff 0.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="productionTariff1Type" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Production Tariff 1</label>
|
||||
<description>The total amount of electricity produced for tariff 1.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="productionTariff2Type" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Production Tariff 2</label>
|
||||
<description>The total amount of electricity produced for tariff 2.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="productionTariff1BelgiumType" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Production Tariff 1</label>
|
||||
<description>The total amount of electricity produced for tariff 1.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="productionTariff2BelgiumType" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Production Tariff 2</label>
|
||||
<description>The total amount of electricity produced for tariff 2.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalImportedEnergyRegisterPType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Imported Energy (P+)</label>
|
||||
<description>The Total imported energy register (P+).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalExportedEnergyRegisterPType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Exported Energy (P+)</label>
|
||||
<description>The Total exported energy register (P-).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalImportedEnergyRegisterQType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Imported Energy (Q+)</label>
|
||||
<description>The Total imported energy register (Q+).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="totalExportedEnergyRegisterQType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Exported Energy (Q-)</label>
|
||||
<description>The Total exported energy register (Q-).</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="tariffIndicatorType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Tariff Indicator</label>
|
||||
<description>The current tariff indicator.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="activeImportPowerType" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Aggregate Active Import Power</label>
|
||||
<description>The aggregate active import power.</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="actualDeliveryType">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Actual Power Delivery</label>
|
||||
<description>The current power delivery.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="actualProductionType" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Actual Power Production</label>
|
||||
<description>The current power production.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="actualReactiveDeliveryType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Actual Reactive Power Delivery</label>
|
||||
<description>The current reactive power delivery.</description>
|
||||
<state pattern="%.3f kvar" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="actualReactiveProductionType" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Actual Reactive Power Production</label>
|
||||
<description>The current reactive power production.</description>
|
||||
<state pattern="%.3f kvar" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="actualTresholdAType" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Actual Threshold Current</label>
|
||||
<description>The actual threshold.</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="actualFuseThresholdAType" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Actual Fuse Threshold</label>
|
||||
<description>The actual fuse threshold.</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="actualTresholdkWType" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Actual Threshold</label>
|
||||
<description>The actual threshold.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="activeThresholdSmax" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Active Threshold</label>
|
||||
<description>Active threshold (SMAX).</description>
|
||||
<state pattern="%.3f kVA" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="switchPositionType" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Switch Position</label>
|
||||
<description>The switch position.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="powerFailuresType" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Power Failures</label>
|
||||
<description>The number of power failures.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="longPowerFailuresType" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Long Power Failures</label>
|
||||
<description>The number of long power failures.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="powerFailureLogEntriesType" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Power Failure Log Entries</label>
|
||||
<description>Number of log entries.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="powerFailureLogEndType" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Power Failure End</label>
|
||||
<description>Timestamp when the power failure ended. There can be multiple log entries.
|
||||
Each entry has its own channel
|
||||
(emeter_power_failure_log_timestamp*x*, *x* = 0 - 9)</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="powerFailureLogDurationType" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Power Failure Duration</label>
|
||||
<description>Duration of the power failure.
|
||||
Each entry has its own channel (emeter_power_failure_log_duration*x*, *x* =
|
||||
0 - 9)</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="voltageSagsL1Type" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Voltage Sags L1</label>
|
||||
<description>The number of voltage sags L1.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="voltageSagsL2Type" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Voltage Sags L2</label>
|
||||
<description>The number of voltage sags L2.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="voltageSagsL3Type" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Voltage Sags L3</label>
|
||||
<description>The number of voltage sags L3.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="voltageSwellsL1Type" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Voltage Swells L1</label>
|
||||
<description>The number of voltage swells L1.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="voltageSwellsL2Type" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Voltage Swells L2</label>
|
||||
<description>The number of voltage swells L2.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="voltageSwellsL3Type" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Voltage Swells L3</label>
|
||||
<description>The number of voltage swells L3.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantCurrentL1Type" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Instant Current L1</label>
|
||||
<description>The instant current L1.</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantCurrentL2Type" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Instant Current L2</label>
|
||||
<description>The instant current L2.</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantCurrentL3Type" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Instant Current L3</label>
|
||||
<description>The instant current L3.</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantPowerDeliveryL1Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Power Delivery L1</label>
|
||||
<description>The instant power delivery L1.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantPowerDeliveryL2Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Power Delivery L2</label>
|
||||
<description>The instant power delivery L2.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantPowerDeliveryL3Type" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Instant Power Delivery L3</label>
|
||||
<description>The instant power delivery L3.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantPowerProductionL1Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Power Production L1</label>
|
||||
<description>The instant power production L1.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantPowerProductionL2Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Power Production L2</label>
|
||||
<description>The instant power production L2.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantPowerProductionL3Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Power Production L3</label>
|
||||
<description>The instant power production L3.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantReactivePowerDeliveryL1Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Reactive Power Delivery L1</label>
|
||||
<description>The instant reactive power delivery L1.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantReactivePowerDeliveryL2Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Reactive Power Delivery L2</label>
|
||||
<description>The instant reactive power delivery L2.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantReactivePowerDeliveryL3Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Reactive Power Delivery L3</label>
|
||||
<description>The instant reactive power delivery L3.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantReactivePowerProductionL1Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Reactive Power Production L1</label>
|
||||
<description>The instant reactive power production L1.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantReactivePowerProductionL2Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Reactive Power Production L2</label>
|
||||
<description>The instant reactive power production L2.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantReactivePowerProductionL3Type" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Instant Reactive Power Production L3</label>
|
||||
<description>The instant reactive power production L3.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantVoltageL1Type" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Instant Voltage L1</label>
|
||||
<description>The instant voltage L1.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantVoltageL2Type" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Instant Voltage L2</label>
|
||||
<description>The instant voltage L2.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="instantVoltageL3Type" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Instant Voltage L3</label>
|
||||
<description>The instant voltage L3.</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="gasDelivery24HType">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Gas Delivery 24 Hour</label>
|
||||
<description>The total amount of gas used in the past 24 hour.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="gasCompensatedDelivery24HType">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Compensated Gas Delivery 24 Hour</label>
|
||||
<description>The total compensated amount of gas used in the past 24 hour.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="gasDeliveryType">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Gas Delivery</label>
|
||||
<description>The total amount used in the past period.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="gasLastDeliveryType">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Gas Delivery</label>
|
||||
<description>Last value of not temperature corrected gas volume.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="gasLastTimestampType">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Timestamp</label>
|
||||
<description>Timestamp of the last gas meter capture time.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="gasValvePositionType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Gas Valve Position</label>
|
||||
<description>The gas valve switch position.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="deviceType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Device Type</label>
|
||||
<description>The meter device type.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="equipmentIdType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Equipment ID</label>
|
||||
<description>Equipment identifier of the device.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="p1TextStringType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Text Message</label>
|
||||
<description>Text message from the device.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="p1TextCodeType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Text Code</label>
|
||||
<description>Text code from the device.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="p1VersionType">
|
||||
<item-type>String</item-type>
|
||||
<label>Version</label>
|
||||
<description>Version information for the device output (This mostly refers to the DSMR specification level).</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="p1TimestampType">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Timestamp</label>
|
||||
<description>Timestamp of the last meter reading.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="genValueType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Delivery</label>
|
||||
<description>The total amount delivered in the past period.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="genValvePositionType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Valve Position</label>
|
||||
<description>The valve switch position.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="gjValueType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Delivery</label>
|
||||
<description>The total amount delivered in the past period.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="gjValvePositionType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Valve Position</label>
|
||||
<description>The valve switch position.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="heatingValueType">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Heating Delivery</label>
|
||||
<description>The total amount of heating used in the past period.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="m3ValueType">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Delivery</label>
|
||||
<description>The total amount delivered in the past period.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="m3ValvePositionType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Valve Position</label>
|
||||
<description>The valve switch position.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="waterValueType">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Water Delivery</label>
|
||||
<description>The total amount of water used in the past period.</description>
|
||||
<state pattern="%.3f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="waterValvePositionType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Water Valve Position</label>
|
||||
<description>The water valve switch position.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="dsmrBridge">
|
||||
<label>Smart Meter</label>
|
||||
<description>The Dutch/Belgium Smart Meter (DSMR/e-MUCS)</description>
|
||||
|
||||
<config-description-ref uri="thing-type:dsmr:bridgesettings"/>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="cooling_ace4000" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Cooling Meter (ACE4000)</label>
|
||||
<description>This is a cooling meter that complies to the ACE4000 GTMM Mk3 specification.</description>
|
||||
<channels>
|
||||
<channel id="cmeter_value_v2" typeId="coolingValueType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="cooling_v2_2" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Cooling Meter (DSMR V2.2)</label>
|
||||
<description>This is a cooling meter that complies to the DSMR V2.2 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="cmeter_equipment_identifier_v2_2" typeId="equipmentIdType"/>
|
||||
<channel id="cmeter_value_v2" typeId="coolingValueType"/>
|
||||
<channel id="cmeter_value_v2_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="device_v2_v3" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Device Meter (DSMR V2.x or V3.0)</label>
|
||||
<description>This is the device meter that complies to the DSMR V2.x or V3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="p1_text_code" typeId="p1TextCodeType"/>
|
||||
<channel id="p1_text_string" typeId="p1TextStringType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="device_v4" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
<bridge-type-ref id="smartyBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Device Meter (DSMR V4.x)</label>
|
||||
<description>This is the device meter that complies to the DSMR V4 specification.</description>
|
||||
<channels>
|
||||
<channel id="p1_text_code" typeId="p1TextCodeType"/>
|
||||
<channel id="p1_text_string" typeId="p1TextStringType"/>
|
||||
<channel id="p1_version_output" typeId="p1VersionType"/>
|
||||
<channel id="p1_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="device_v5" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
<bridge-type-ref id="smartyBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Device Meter (DSMR V5)</label>
|
||||
<description>This is the device meter that complies to the DSMR V5 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="p1_text_string" typeId="p1TextStringType"/>
|
||||
<channel id="p1_version_output" typeId="p1VersionType"/>
|
||||
<channel id="p1_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="device_emucs_v1_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Device Meter (e-MUCS V1.0)</label>
|
||||
<description>This is the device meter that complies to the e-MUCS 1.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="p1_text_string" typeId="p1TextStringType"/>
|
||||
<channel id="p1_emucs_version_output" typeId="p1VersionType"/>
|
||||
<channel id="p1_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_ace4000" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (ACE4000)</label>
|
||||
<description>This is an electricity meter that complies to the ACE4000 GTMM Mk3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff0" typeId="deliveryTariff0Type"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_delivery_tariff0_antifraud" typeId="deliveryTariff0AntifraudType"/>
|
||||
<channel id="emeter_delivery_tariff1_antifraud" typeId="deliveryTariff1AntifraudType"/>
|
||||
<channel id="emeter_delivery_tariff2_antifraud" typeId="deliveryTariff2AntifraudType"/>
|
||||
<channel id="emeter_production_tariff0" typeId="productionTariff0Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_active_import_power" typeId="activeImportPowerType"/>
|
||||
<channel id="emeter_treshold_a" typeId="actualTresholdAType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_v2_1" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (DSMR V2.1)</label>
|
||||
<description>This is an electricity meter that complies to the DSMR V2.1 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier_v2_x" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_treshold_a_v2_1" typeId="actualTresholdAType"/>
|
||||
<channel id="emeter_switch_position_v2_1" typeId="switchPositionType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_v2_2" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (DSMR V2.2)</label>
|
||||
<description>This is an electricity meter that complies to the DSMR V2.2 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier_v2_x" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_treshold_a" typeId="actualTresholdAType"/>
|
||||
<channel id="meter_switch_position" typeId="switchPositionType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_v3_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (DSMR V3)</label>
|
||||
<description>This is an electricity meter that complies to the DSMR V3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_treshold_a" typeId="actualTresholdAType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
<channel id="emeter_actual_production" typeId="actualProductionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_v4_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (DSMR V4.0)</label>
|
||||
<description>This is an electricity meter that complies to the DSMR V4.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_treshold_kwh" typeId="actualTresholdkWType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
<channel id="emeter_actual_production" typeId="actualProductionType"/>
|
||||
<channel id="emeter_power_failures" typeId="powerFailuresType"/>
|
||||
<channel id="emeter_long_power_failures" typeId="longPowerFailuresType"/>
|
||||
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration0" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp1" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration1" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp2" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration2" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp3" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration3" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp4" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration4" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp5" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration5" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp6" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration6" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp7" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration7" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp8" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration8" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp9" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration9" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_voltage_sags_l1" typeId="voltageSagsL1Type"/>
|
||||
<channel id="emeter_voltage_sags_l2" typeId="voltageSagsL2Type"/>
|
||||
<channel id="emeter_voltage_sags_l3" typeId="voltageSagsL3Type"/>
|
||||
<channel id="emeter_voltage_swells_l1" typeId="voltageSwellsL1Type"/>
|
||||
<channel id="emeter_voltage_swells_l2" typeId="voltageSwellsL2Type"/>
|
||||
<channel id="emeter_voltage_swells_l3" typeId="voltageSwellsL3Type"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_v4_0_4" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (DSMR V4.0.4)</label>
|
||||
<description>This is an electricity meter that complies to the DSMR V4.0.4 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_treshold_kwh" typeId="actualTresholdkWType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
<channel id="emeter_actual_production" typeId="actualProductionType"/>
|
||||
<channel id="emeter_power_failures" typeId="powerFailuresType"/>
|
||||
<channel id="emeter_long_power_failures" typeId="longPowerFailuresType"/>
|
||||
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration0" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp1" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration1" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp2" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration2" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp3" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration3" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp4" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration4" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp5" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration5" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp6" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration6" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp7" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration7" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp8" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration8" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp9" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration9" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_voltage_sags_l1" typeId="voltageSagsL1Type"/>
|
||||
<channel id="emeter_voltage_sags_l2" typeId="voltageSagsL2Type"/>
|
||||
<channel id="emeter_voltage_sags_l3" typeId="voltageSagsL3Type"/>
|
||||
<channel id="emeter_voltage_swells_l1" typeId="voltageSwellsL1Type"/>
|
||||
<channel id="emeter_voltage_swells_l2" typeId="voltageSwellsL2Type"/>
|
||||
<channel id="emeter_voltage_swells_l3" typeId="voltageSwellsL3Type"/>
|
||||
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
|
||||
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
|
||||
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l1" typeId="instantPowerDeliveryL1Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l2" typeId="instantPowerDeliveryL2Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l3" typeId="instantPowerDeliveryL3Type"/>
|
||||
<channel id="emeter_instant_power_production_l1" typeId="instantPowerProductionL1Type"/>
|
||||
<channel id="emeter_instant_power_production_l2" typeId="instantPowerProductionL2Type"/>
|
||||
<channel id="emeter_instant_power_production_l3" typeId="instantPowerProductionL3Type"/>
|
||||
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_v4_2" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (DSMR V4.2)</label>
|
||||
<description>This is an electricity meter that complies to the DSMR V4.2 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
<channel id="emeter_actual_production" typeId="actualProductionType"/>
|
||||
<channel id="emeter_power_failures" typeId="powerFailuresType"/>
|
||||
<channel id="emeter_long_power_failures" typeId="longPowerFailuresType"/>
|
||||
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration0" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp1" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration1" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp2" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration2" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp3" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration3" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp4" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration4" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp5" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration5" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp6" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration6" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp7" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration7" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp8" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration8" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp9" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration9" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_voltage_sags_l1" typeId="voltageSagsL1Type"/>
|
||||
<channel id="emeter_voltage_sags_l2" typeId="voltageSagsL2Type"/>
|
||||
<channel id="emeter_voltage_sags_l3" typeId="voltageSagsL3Type"/>
|
||||
<channel id="emeter_voltage_swells_l1" typeId="voltageSwellsL1Type"/>
|
||||
<channel id="emeter_voltage_swells_l2" typeId="voltageSwellsL2Type"/>
|
||||
<channel id="emeter_voltage_swells_l3" typeId="voltageSwellsL3Type"/>
|
||||
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
|
||||
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
|
||||
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l1" typeId="instantPowerDeliveryL1Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l2" typeId="instantPowerDeliveryL2Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l3" typeId="instantPowerDeliveryL3Type"/>
|
||||
<channel id="emeter_instant_power_production_l1" typeId="instantPowerProductionL1Type"/>
|
||||
<channel id="emeter_instant_power_production_l2" typeId="instantPowerProductionL2Type"/>
|
||||
<channel id="emeter_instant_power_production_l3" typeId="instantPowerProductionL3Type"/>
|
||||
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_v5_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (DSMR V5.0)</label>
|
||||
<description>This is an electricity meter that complies to the DSMR V5.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
<channel id="emeter_actual_production" typeId="actualProductionType"/>
|
||||
<channel id="emeter_power_failures" typeId="powerFailuresType"/>
|
||||
<channel id="emeter_long_power_failures" typeId="longPowerFailuresType"/>
|
||||
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration0" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp1" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration1" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp2" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration2" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp3" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration3" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp4" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration4" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp5" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration5" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp6" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration6" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp7" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration7" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp8" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration8" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp9" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration9" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_voltage_sags_l1" typeId="voltageSagsL1Type"/>
|
||||
<channel id="emeter_voltage_sags_l2" typeId="voltageSagsL2Type"/>
|
||||
<channel id="emeter_voltage_sags_l3" typeId="voltageSagsL3Type"/>
|
||||
<channel id="emeter_voltage_swells_l1" typeId="voltageSwellsL1Type"/>
|
||||
<channel id="emeter_voltage_swells_l2" typeId="voltageSwellsL2Type"/>
|
||||
<channel id="emeter_voltage_swells_l3" typeId="voltageSwellsL3Type"/>
|
||||
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
|
||||
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
|
||||
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l1" typeId="instantPowerDeliveryL1Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l2" typeId="instantPowerDeliveryL2Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l3" typeId="instantPowerDeliveryL3Type"/>
|
||||
<channel id="emeter_instant_power_production_l1" typeId="instantPowerProductionL1Type"/>
|
||||
<channel id="emeter_instant_power_production_l2" typeId="instantPowerProductionL2Type"/>
|
||||
<channel id="emeter_instant_power_production_l3" typeId="instantPowerProductionL3Type"/>
|
||||
<channel id="emeter_instant_voltage_l1" typeId="instantVoltageL1Type"/>
|
||||
<channel id="emeter_instant_voltage_l2" typeId="instantVoltageL2Type"/>
|
||||
<channel id="emeter_instant_voltage_l3" typeId="instantVoltageL3Type"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_emucs_v1_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (e-MUCS V1.0)</label>
|
||||
<description>This is an electricity meter that complies to the e-MUCS V1.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1BelgiumType"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2BelgiumType"/>
|
||||
<channel id="emeter_production_tariff1" typeId="productionTariff1BelgiumType"/>
|
||||
<channel id="emeter_production_tariff2" typeId="productionTariff2BelgiumType"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
<channel id="emeter_actual_production" typeId="actualProductionType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
<channel id="emeter_fuse_threshold_a" typeId="actualFuseThresholdAType"/>
|
||||
<channel id="emeter_treshold_kw" typeId="actualTresholdkWType"/>
|
||||
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
|
||||
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
|
||||
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>
|
||||
<channel id="emeter_instant_voltage_l1" typeId="instantVoltageL1Type"/>
|
||||
<channel id="emeter_instant_voltage_l2" typeId="instantVoltageL2Type"/>
|
||||
<channel id="emeter_instant_voltage_l3" typeId="instantVoltageL3Type"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="slave_electricity1_ace4000" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Slave Electricity Meter 1 (ACE4000)</label>
|
||||
<description>This is the first slave electricity meter that complies to the ACE4000 GTMM Mk3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_delivery_tariff0" typeId="deliveryTariff0Type"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff0" typeId="deliveryTariff0Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_active_import_power" typeId="activeImportPowerType"/>
|
||||
<channel id="emeter_treshold_a" typeId="actualTresholdAType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="slave_electricity2_ace4000" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Slave Electricity Meter 2 (ACE4000)</label>
|
||||
<description>This is the second slave electricity meter that complies to the ACE4000 GTMM Mk3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_delivery_tariff0" typeId="deliveryTariff0Type"/>
|
||||
<channel id="emeter_delivery_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_delivery_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_production_tariff0" typeId="deliveryTariff0Type"/>
|
||||
<channel id="emeter_production_tariff1" typeId="deliveryTariff1Type"/>
|
||||
<channel id="emeter_production_tariff2" typeId="deliveryTariff2Type"/>
|
||||
<channel id="emeter_tariff_indicator" typeId="tariffIndicatorType"/>
|
||||
<channel id="emeter_active_import_power" typeId="activeImportPowerType"/>
|
||||
<channel id="emeter_treshold_a" typeId="actualTresholdAType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="slave_electricity_v4" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Slave Electricity Meter (DSMR V4.x)</label>
|
||||
<description>This is the slave electricity meter that complies to the DSMR 4.x specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_value" typeId="deliveryType"/>
|
||||
<channel id="emeter_value_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="slave_electricity_v5" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Slave Electricity Meter (DSMR V5.x)</label>
|
||||
<description>This is the slave electricity meter that complies to the DSMR 5.x specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_value" typeId="deliveryType"/>
|
||||
<channel id="emeter_value_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="electricity_smarty_v1_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="smartyBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Electricity Meter (Smarty V1.0)</label>
|
||||
<description>This is an electricity meter that complies to the Luxembourg's Smarty V1.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="emeter_equipment_identifier_v2_x" typeId="equipmentIdType"/>
|
||||
<channel id="emeter_delivery_tariff0" typeId="totalImportedEnergyRegisterPType"/>
|
||||
<channel id="emeter_production_tariff0" typeId="totalExportedEnergyRegisterPType"/>
|
||||
<channel id="emeter_total_imported_energy_register_q" typeId="totalImportedEnergyRegisterQType"/>
|
||||
<channel id="emeter_total_exported_energy_register_q" typeId="totalExportedEnergyRegisterQType"/>
|
||||
<channel id="emeter_actual_delivery" typeId="actualDeliveryType"/>
|
||||
<channel id="emeter_actual_production" typeId="actualProductionType"/>
|
||||
<channel id="emeter_actual_reactive_delivery" typeId="actualReactiveDeliveryType"/>
|
||||
<channel id="emeter_actual_reactive_production" typeId="actualReactiveProductionType"/>
|
||||
<channel id="emeter_active_threshold_smax" typeId="activeThresholdSmax"/>
|
||||
<channel id="emeter_switch_position" typeId="switchPositionType"/>
|
||||
<channel id="emeter_power_failure_log_entries" typeId="powerFailureLogEntriesType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp0" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration0" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp1" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration1" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp2" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration2" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp3" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration3" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp4" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration4" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp5" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration5" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp6" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration6" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp7" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration7" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp8" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration8" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_power_failure_log_timestamp9" typeId="powerFailureLogEndType"/>
|
||||
<channel id="emeter_power_failure_log_duration9" typeId="powerFailureLogDurationType"/>
|
||||
<channel id="emeter_voltage_sags_l1" typeId="voltageSagsL1Type"/>
|
||||
<channel id="emeter_voltage_sags_l2" typeId="voltageSagsL2Type"/>
|
||||
<channel id="emeter_voltage_sags_l3" typeId="voltageSagsL3Type"/>
|
||||
<channel id="emeter_voltage_swells_l1" typeId="voltageSwellsL1Type"/>
|
||||
<channel id="emeter_voltage_swells_l2" typeId="voltageSwellsL2Type"/>
|
||||
<channel id="emeter_voltage_swells_l3" typeId="voltageSwellsL3Type"/>
|
||||
<channel id="emeter_instant_current_l1" typeId="instantCurrentL1Type"/>
|
||||
<channel id="emeter_instant_current_l2" typeId="instantCurrentL2Type"/>
|
||||
<channel id="emeter_instant_current_l3" typeId="instantCurrentL3Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l1" typeId="instantPowerDeliveryL1Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l2" typeId="instantPowerDeliveryL2Type"/>
|
||||
<channel id="emeter_instant_power_delivery_l3" typeId="instantPowerDeliveryL3Type"/>
|
||||
<channel id="emeter_instant_power_production_l1" typeId="instantPowerProductionL1Type"/>
|
||||
<channel id="emeter_instant_power_production_l2" typeId="instantPowerProductionL2Type"/>
|
||||
<channel id="emeter_instant_power_production_l3" typeId="instantPowerProductionL3Type"/>
|
||||
<channel id="emeter_instant_reactive_power_delivery_l1" typeId="instantReactivePowerDeliveryL1Type"/>
|
||||
<channel id="emeter_instant_reactive_power_delivery_l2" typeId="instantReactivePowerDeliveryL2Type"/>
|
||||
<channel id="emeter_instant_reactive_power_delivery_l3" typeId="instantReactivePowerDeliveryL3Type"/>
|
||||
<channel id="emeter_instant_reactive_power_production_l1" typeId="instantReactivePowerProductionL1Type"/>
|
||||
<channel id="emeter_instant_reactive_power_production_l2" typeId="instantReactivePowerProductionL2Type"/>
|
||||
<channel id="emeter_instant_reactive_power_production_l3" typeId="instantReactivePowerProductionL3Type"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gas_ace4000" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Gas Meter (ACE4000)</label>
|
||||
<description>This is a gas meter that complies to the ACE4000 GTMM Mk3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="gmeter_24h_delivery_v2" typeId="gasDelivery24HType"/>
|
||||
<channel id="gmeter_24h_delivery_v2_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="gmeter_valve_position_v2_2" typeId="gasValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gas_v2_1" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Gas Meter (DSMR V2.1)</label>
|
||||
<description>This is a gas meter that complies to the DSMR V2.1 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="gmeter_equipment_identifier_v2" typeId="equipmentIdType"/>
|
||||
<channel id="gmeter_24h_delivery_v2" typeId="gasDelivery24HType"/>
|
||||
<channel id="gmeter_24h_delivery_v2_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="gmeter_24h_delivery_compensated_v2" typeId="gasCompensatedDelivery24HType"/>
|
||||
<channel id="gmeter_24h_delivery_compensated_v2_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="gmeter_valve_position_v2_1" typeId="gasValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gasv2_2" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Gas Meter (DSMR V2.2)</label>
|
||||
<description>This is a gas meter that complies to the DSMR V2.2 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="gmeter_equipment_identifier_v2" typeId="equipmentIdType"/>
|
||||
<channel id="gmeter_24h_delivery_v2" typeId="gasDelivery24HType"/>
|
||||
<channel id="gmeter_24h_delivery_v2_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="gmeter_24h_delivery_compensated_v2" typeId="gasCompensatedDelivery24HType"/>
|
||||
<channel id="gmeter_24h_delivery_compensated_v2_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="gmeter_valve_position_v2_2" typeId="gasValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gas_v3_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Gas Meter (DSMR V3.0)</label>
|
||||
<description>This is a gas meter that complies to the DSMR V3.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="gmeter_value_v3" typeId="gasDeliveryType"/>
|
||||
<channel id="gmeter_value_v3_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="meter_valve_switch_position" typeId="gasValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gas_emucs_v1_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Gas Meter (e-MUCS V1.0)</label>
|
||||
<description>This is a gas meter that complies to the e-MUCS V1.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="gmeter_last_value" typeId="gasLastDeliveryType"/>
|
||||
<channel id="gmeter_last_value_timestamp" typeId="gasLastTimestampType"/>
|
||||
<channel id="meter_valve_switch_position" typeId="gasValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="generic_v3_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Generic Meter (DSMR V3.0)</label>
|
||||
<description>This is a generic meter that complies to the DSMR V3.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="genmeter_value_v3" typeId="genValueType"/>
|
||||
<channel id="meter_valve_switch_position" typeId="genValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gj_v3_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>GJ Meter (DSMR V3.0)</label>
|
||||
<description>This is a Giga Joule meter that complies to the DSMR V3.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="gjmeter_value_v3" typeId="gjValueType"/>
|
||||
<channel id="meter_valve_switch_position" typeId="gjValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gj_v4" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>GJ Meter (DSMR V4.x)</label>
|
||||
<description>This is a Giga Joule meter that complies to the DSMR V4.x specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="gjmeter_value" typeId="gjValueType"/>
|
||||
<channel id="gjmeter_value_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="meter_valve_switch_position" typeId="gjValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gj_v5_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>GJ Meter (DSMR V5.0)</label>
|
||||
<description>This is a Giga Joule meter that complies to the DSMR V5.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="gjmeter_value" typeId="gjValueType"/>
|
||||
<channel id="gjmeter_value_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="heating_ace4000" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Heating Meter (ACE4000)</label>
|
||||
<description>This is a heating meter that complies to the ACE4000 GTMM Mk3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="hmeter_value_v2" typeId="heatingValueType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="heating_v2_2" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Heating Meter (DSMR V2.2)</label>
|
||||
<description>This is a heating meter that complies to the DSMR V2.2 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="hmeter_equipment_identifier_v2_2" typeId="equipmentIdType"/>
|
||||
<channel id="hmeter_value_v2" typeId="heatingValueType"/>
|
||||
<channel id="hmeter_value_v2_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="m3_v4" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>M3 Meter (DSMR V4.x)</label>
|
||||
<description>This is a m3 meter that complies to the DSMR V4.x specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="m3meter_value" typeId="m3ValueType"/>
|
||||
<channel id="m3meter_value_timestamp" typeId="p1TimestampType"/>
|
||||
<channel id="meter_valve_switch_position" typeId="m3ValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="m3_v5_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>M3 Meter (DSMR V5.0)</label>
|
||||
<description>This is a m3 meter that complies to the DSMR V5.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="m3meter_value" typeId="m3ValueType"/>
|
||||
<channel id="m3meter_value_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="water_ace4000" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Water Meter (ACE4000)</label>
|
||||
<description>This is a water meter that complies to the ACE4000 GTMM Mk3 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="wmeter_value_v2" typeId="waterValueType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="water_v2_2" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Water Meter (DSMR V2.2)</label>
|
||||
<description>This is a water meter that complies to the DSMR V2.2 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="wmeter_equipment_identifier_v2_2" typeId="equipmentIdType"/>
|
||||
<channel id="wmeter_value_v2" typeId="waterValueType"/>
|
||||
<channel id="wmeter_value_v2_timestamp" typeId="p1TimestampType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="water_v3_0" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dsmrBridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Water Meter (DSMR V3.0)</label>
|
||||
<description>This is a water meter that complies to the DSMR V3.0 specification.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="meter_device_type" typeId="deviceType"/>
|
||||
<channel id="meter_equipment_identifier" typeId="equipmentIdType"/>
|
||||
<channel id="wmeter_value_v3" typeId="waterValueType"/>
|
||||
<channel id="meter_valve_switch_position" typeId="waterValvePositionType"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:dsmr:meterdescriptor"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dsmr"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="smartyBridge">
|
||||
<label>Smarty Meter</label>
|
||||
<description>The Luxembourgian Smart Meter 'Smarty'</description>
|
||||
|
||||
<config-description-ref uri="thing-type:dsmr:smartybridgesettings"/>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
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.P1TelegramParser;
|
||||
|
||||
/**
|
||||
* Util class to read test input telegrams.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public final class TelegramReaderUtil {
|
||||
private static final String TELEGRAM_EXT = ".telegram";
|
||||
|
||||
private TelegramReaderUtil() {
|
||||
// Util class
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the raw bytes of the telegram given the file relative to this package and returns the objects.
|
||||
*
|
||||
* @param telegramName name of the telegram file to read
|
||||
* @return The raw bytes of a telegram
|
||||
*/
|
||||
public static byte[] readRawTelegram(String telegramName) {
|
||||
try (InputStream is = TelegramReaderUtil.class.getResourceAsStream(telegramName + TELEGRAM_EXT)) {
|
||||
return IOUtils.toByteArray(is);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException reading telegram data: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a telegram given the file relative to this package and returns the objects.
|
||||
*
|
||||
* @param telegramName name of the telegram file to read
|
||||
* @param expectedTelegramState expected state of the telegram read
|
||||
* @return a P1Telegram object
|
||||
*/
|
||||
public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) {
|
||||
AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
|
||||
byte[] telegram = readRawTelegram(telegramName);
|
||||
P1TelegramParser parser = new P1TelegramParser(p1Telegram::set);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
|
||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice.DeviceState;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEventListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
|
||||
/**
|
||||
* Test class for {@link DSMRSerialAutoDevice}.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public class DSMRSerialAutoDeviceTest {
|
||||
|
||||
private static final String DUMMY_PORTNAME = "/dev/dummy-serial";
|
||||
private static final String TELEGRAM_NAME = "dsmr_50";
|
||||
|
||||
@Mock
|
||||
private SerialPortIdentifier mockIdentifier;
|
||||
@Mock
|
||||
private ScheduledExecutorService scheduler;
|
||||
@Mock
|
||||
private SerialPort mockSerialPort;
|
||||
|
||||
private SerialPortManager serialPortManager = new SerialPortManager() {
|
||||
@Override
|
||||
public SerialPortIdentifier getIdentifier(String name) {
|
||||
assertEquals("Expect the passed serial port name", DUMMY_PORTNAME, name);
|
||||
return mockIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SerialPortIdentifier> getIdentifiers() {
|
||||
return Stream.empty();
|
||||
}
|
||||
};
|
||||
private SerialPortEventListener serialPortEventListener;
|
||||
|
||||
@Before
|
||||
public void setUp() throws PortInUseException, TooManyListenersException {
|
||||
initMocks(this);
|
||||
doAnswer(a -> {
|
||||
serialPortEventListener = a.getArgument(0);
|
||||
return null;
|
||||
}).when(mockSerialPort).addEventListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandlingDataAndRestart() throws IOException, PortInUseException {
|
||||
mockValidSerialPort();
|
||||
AtomicReference<P1Telegram> telegramRef = new AtomicReference<>(null);
|
||||
DSMREventListener listener = new DSMREventListener() {
|
||||
@Override
|
||||
public void handleTelegramReceived(P1Telegram telegram) {
|
||||
telegramRef.set(telegram);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
|
||||
fail("No handleErrorEvent Expected" + connectorErrorEvent);
|
||||
}
|
||||
};
|
||||
try (InputStream inputStream = new ByteArrayInputStream(TelegramReaderUtil.readRawTelegram(TELEGRAM_NAME))) {
|
||||
when(mockSerialPort.getInputStream()).thenReturn(inputStream);
|
||||
DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
|
||||
new DSMRTelegramListener(), scheduler, 1);
|
||||
device.start();
|
||||
assertSame("Expect to be starting discovery state", DeviceState.DISCOVER_SETTINGS, device.getState());
|
||||
serialPortEventListener
|
||||
.serialEvent(new MockSerialPortEvent(mockSerialPort, SerialPortEvent.BI, false, true));
|
||||
assertSame("Expect to be still in discovery state", DeviceState.DISCOVER_SETTINGS, device.getState());
|
||||
serialPortEventListener
|
||||
.serialEvent(new MockSerialPortEvent(mockSerialPort, SerialPortEvent.DATA_AVAILABLE, false, true));
|
||||
assertSame("Expect to be in normal state", DeviceState.NORMAL, device.getState());
|
||||
device.restart();
|
||||
assertSame("Expect not to start rediscovery when in normal state", DeviceState.NORMAL, device.getState());
|
||||
device.stop();
|
||||
}
|
||||
assertNotNull("Expected to have read a telegram", telegramRef.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleError() throws IOException, PortInUseException {
|
||||
AtomicReference<DSMRConnectorErrorEvent> eventRef = new AtomicReference<>(null);
|
||||
DSMREventListener listener = new DSMREventListener() {
|
||||
@Override
|
||||
public void handleTelegramReceived(P1Telegram telegram) {
|
||||
fail("No telegram expected:" + telegram);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
|
||||
eventRef.set(connectorErrorEvent);
|
||||
}
|
||||
};
|
||||
try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) {
|
||||
when(mockSerialPort.getInputStream()).thenReturn(inputStream);
|
||||
// Trigger device to go into error stage with port in use.
|
||||
doAnswer(a -> {
|
||||
throw new PortInUseException();
|
||||
}).when(mockIdentifier).open(eq(DSMRBindingConstants.DSMR_PORT_NAME), anyInt());
|
||||
DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
|
||||
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());
|
||||
// Trigger device to restart
|
||||
mockValidSerialPort();
|
||||
device.restart();
|
||||
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, 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());
|
||||
}
|
||||
}
|
||||
|
||||
private void mockValidSerialPort() throws PortInUseException {
|
||||
doReturn(mockSerialPort).when(mockIdentifier).open(anyString(), anyInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock class implementing {@link SerialPortEvent}.
|
||||
*/
|
||||
private static class MockSerialPortEvent implements SerialPortEvent {
|
||||
private final int eventType;
|
||||
private final boolean newValue;
|
||||
|
||||
public MockSerialPortEvent(SerialPort mockSerialPort, int eventType, boolean oldValue, boolean newValue) {
|
||||
this.eventType = eventType;
|
||||
this.newValue = newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getNewValue() {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
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<>("");
|
||||
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)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.device.p1telegram;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
||||
|
||||
/**
|
||||
* Test class for {@link P1TelegramParser}.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@RunWith(value = Parameterized.class)
|
||||
public class P1TelegramParserTest {
|
||||
|
||||
// @formatter:off
|
||||
@Parameters(name = "{0}")
|
||||
public static final List<Object[]> data() {
|
||||
return Arrays.asList(new Object[][] {
|
||||
{ "ace4000", 59, },
|
||||
{ "dsmr_40", 39, },
|
||||
{ "dsmr_42", 39, },
|
||||
{ "dsmr_50", 41, },
|
||||
{ "flu5", 21, },
|
||||
{ "Iskra_AM550", 41, },
|
||||
{ "Landis_Gyr_E350", 10, },
|
||||
{ "Landis_Gyr_ZCF110", 25, },
|
||||
{ "Sagemcom_XS210", 41, },
|
||||
{ "smarty", 28, },
|
||||
{ "smarty_with_units", 23, },
|
||||
});
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
@Parameter(0)
|
||||
public String telegramName;
|
||||
|
||||
@Parameter(1)
|
||||
public int numberOfCosemObjects;
|
||||
|
||||
@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(co -> co.getCosemValues().size()).sum());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
||||
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.meter.DSMRMeterDescriptor;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||
|
||||
/**
|
||||
* Test class for {@link DSMRMeterDetector}.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@RunWith(value = Parameterized.class)
|
||||
public class DSMRMeterDetectorTest {
|
||||
|
||||
// @formatter:off
|
||||
@Parameters(name = "{0}")
|
||||
public static final List<Object[]> data() {
|
||||
return Arrays.asList(new Object[][] {
|
||||
{ "ace4000", EnumSet.of( ELECTRICITY_ACE4000, GAS_ACE4000)},
|
||||
{ "dsmr_40", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
|
||||
{ "dsmr_42", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
|
||||
{ "dsmr_50", EnumSet.of( DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)},
|
||||
{ "flu5", EnumSet.of( DEVICE_EMUCS_V1_0, ELECTRICITY_EMUCS_V1_0, GAS_EMUCS_V1_0)},
|
||||
{ "Iskra_AM550", EnumSet.of( DEVICE_V5, ELECTRICITY_V5_0, M3_V5_0)},
|
||||
{ "Landis_Gyr_E350", EnumSet.of( DEVICE_V2_V3, ELECTRICITY_V3_0)},
|
||||
{ "Landis_Gyr_ZCF110", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2, M3_V5_0)},
|
||||
{ "Sagemcom_XS210", EnumSet.of( DEVICE_V4, ELECTRICITY_V4_2)},
|
||||
{ "smarty", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0)},
|
||||
{ "smarty_with_units", EnumSet.of( DEVICE_V5, ELECTRICITY_SMARTY_V1_0, M3_V4)},
|
||||
});
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
@Parameter(0)
|
||||
public String telegramName;
|
||||
|
||||
@Parameter(1)
|
||||
public Set<DSMRMeterType> expectedMeters;
|
||||
|
||||
@Test
|
||||
public void testDetectMeters() {
|
||||
P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
|
||||
DSMRMeterDetector detector = new DSMRMeterDetector();
|
||||
Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> entry = detector
|
||||
.detectMeters(telegram);
|
||||
Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
|
||||
|
||||
assertEquals("Should detect correct number of meters: " + Arrays.toString(detectMeters.toArray()),
|
||||
expectedMeters.size(), detectMeters.size());
|
||||
assertEquals("Should not have any undetected cosem objects: ", Collections.emptyMap(), entry.getValue());
|
||||
assertEquals("Should not have any unknown cosem objects", Collections.emptyList(),
|
||||
telegram.getUnknownCosemObjects());
|
||||
for (DSMRMeterType meter : expectedMeters) {
|
||||
assertEquals(
|
||||
String.format("Meter '%s' not found: %s", meter,
|
||||
Arrays.toString(detectMeters.toArray(new DSMRMeterDescriptor[0]))),
|
||||
1, detectMeters.stream().filter(e -> e.getMeterType() == meter).count());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||
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.handler.DSMRBridgeHandler;
|
||||
import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
|
||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* Test class for {@link DSMRMeterDiscoveryService}.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public class DSMRMeterDiscoveryServiceTest {
|
||||
|
||||
private static final String EXPECTED_CONFIGURED_TELEGRAM = "dsmr_50";
|
||||
private static final String UNREGISTERED_METER_TELEGRAM = "unregistered_meter";
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private DSMRBridgeHandler bridge;
|
||||
@Mock
|
||||
private Thing thing;
|
||||
@Mock
|
||||
private DSMRMeterHandler meterHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() throws PortInUseException, TooManyListenersException {
|
||||
initMocks(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if discovery reports when the user has incorrectly configured the binding with the wrong meter types.
|
||||
* Some meters are a subset of other meters so it won't generates errors in usage, but some values will not be
|
||||
* available to the user with the subset meter.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidConfiguredMeters() {
|
||||
P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM, TelegramState.OK);
|
||||
AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
|
||||
AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
|
||||
DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService(bridge) {
|
||||
@Override
|
||||
protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
|
||||
List<DSMRMeterType> unconfiguredMeters) {
|
||||
super.reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
|
||||
invalidConfiguredRef.set(invalidConfigured);
|
||||
unconfiguredRef.set(unconfiguredMeters);
|
||||
}
|
||||
};
|
||||
|
||||
// Mock the invalid configuration by reading a telegram that is valid for a meter that is a subset of the
|
||||
// expected meter.
|
||||
List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0)
|
||||
.stream().map(mt -> new DSMRMeterDescriptor(mt, 0)).collect(Collectors.toList());
|
||||
List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing).collect(Collectors.toList());
|
||||
AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
|
||||
when((meterHandler).getMeterDescriptor()).then(a -> {
|
||||
if (detectMetersRef.get() == null || !detectMetersRef.get().hasNext()) {
|
||||
detectMetersRef.set(invalidConfiguredMeterDescriptors.iterator());
|
||||
}
|
||||
return detectMetersRef.get().next();
|
||||
});
|
||||
when(thing.getHandler()).thenReturn(meterHandler);
|
||||
when(bridge.getThing().getUID()).thenReturn(new ThingUID("dsmr:dsmrBridge:22e5393c"));
|
||||
when(bridge.getThing().getThings()).thenReturn(things);
|
||||
|
||||
service.telegramReceived(expected);
|
||||
assertNotNull("Should have invalid configured meters", invalidConfiguredRef.get());
|
||||
assertTrue("Should have found specific invalid meter",
|
||||
invalidConfiguredRef.get().contains(DSMRMeterType.ELECTRICITY_V4_2));
|
||||
assertNotNull("Should have undetected meters", unconfiguredRef.get());
|
||||
assertTrue("Should have found specific undetected meter",
|
||||
unconfiguredRef.get().contains(DSMRMeterType.ELECTRICITY_V5_0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if discovery correctly reports if a meter was detected that has not been registered with the energy
|
||||
* provider. This meter doesn't report all values in telegram and therefore is not recognized as a specific
|
||||
* meter. But reports with an empty equipment identifier.
|
||||
*/
|
||||
@Test
|
||||
public void testUnregisteredMeters() {
|
||||
P1Telegram telegram = TelegramReaderUtil.readTelegram(UNREGISTERED_METER_TELEGRAM, TelegramState.OK);
|
||||
AtomicBoolean unregisteredMeter = new AtomicBoolean(false);
|
||||
DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService(bridge) {
|
||||
@Override
|
||||
protected void reportUnregisteredMeters() {
|
||||
super.reportUnregisteredMeters();
|
||||
unregisteredMeter.set(true);
|
||||
}
|
||||
};
|
||||
when(bridge.getThing().getUID()).thenReturn(new ThingUID("dsmr:dsmrBridge:22e5393c"));
|
||||
when(bridge.getThing().getThings()).thenReturn(Collections.emptyList());
|
||||
|
||||
service.telegramReceived(telegram);
|
||||
assertTrue("Should have found an unregistered meter", unregisteredMeter.get());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user