2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.dsmr.internal.discovery;
15 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
16 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
17 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY_EMPTY;
18 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_SERIAL_PORT;
19 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.DSMR_PORT_NAME;
20 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_DSMR_BRIDGE;
21 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
23 import java.util.HashMap;
24 import java.util.List;
26 import java.util.stream.Stream;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
31 import org.openhab.binding.dsmr.internal.device.DSMREventListener;
32 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
33 import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
34 import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
35 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
36 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
37 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
38 import org.openhab.core.config.discovery.DiscoveryResult;
39 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
40 import org.openhab.core.config.discovery.DiscoveryService;
41 import org.openhab.core.i18n.LocaleProvider;
42 import org.openhab.core.i18n.TranslationProvider;
43 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
44 import org.openhab.core.io.transport.serial.SerialPortManager;
45 import org.openhab.core.thing.ThingTypeUID;
46 import org.openhab.core.thing.ThingUID;
47 import org.osgi.service.component.annotations.Activate;
48 import org.osgi.service.component.annotations.Component;
49 import org.osgi.service.component.annotations.Reference;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * This implements the discovery service for detecting new DSMR Meters.
56 * The service will iterate over the available serial ports and open the given serial port and wait for telegrams. If
57 * the port is already owned because it's already detected this service will ignore it. But it will report a warning in
58 * case the port was locked due to a crash.
59 * After {@link #BAUDRATE_SWITCH_TIMEOUT_SECONDS} seconds it will switch the baud rate and wait again for telegrams.
60 * When that doesn't produce any results the service will give up (assuming no DSMR Bridge is present).
62 * If a telegram is received with at least 1 Cosem Object a bridge is assumed available and a Thing is added (regardless
63 * if there were problems receiving the telegram) and the discovery is stopped.
65 * If there are communication problems the service will give a warning and give up
67 * @author M. Volaart - Initial contribution
68 * @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
71 @Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
72 public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements DSMREventListener {
75 * The timeout used to switch baudrate if no valid data is received within that time frame.
77 private static final int BAUDRATE_SWITCH_TIMEOUT_SECONDS = 25;
79 private final Logger logger = LoggerFactory.getLogger(DSMRBridgeDiscoveryService.class);
82 * Serial Port Manager.
84 private final SerialPortManager serialPortManager;
87 * DSMR Device that is scanned when discovery process in progress.
89 private @Nullable DSMRDeviceRunnable currentScannedDevice;
92 * Name of the serial port that is scanned when discovery process in progress.
94 private String currentScannedPortName = "";
97 * Keeps a boolean during time discovery process in progress.
99 private boolean scanning;
102 public DSMRBridgeDiscoveryService(final @Reference TranslationProvider i18nProvider,
103 final @Reference LocaleProvider localeProvider, final @Reference SerialPortManager serialPortManager) {
104 this.i18nProvider = i18nProvider;
105 this.localeProvider = localeProvider;
106 this.serialPortManager = serialPortManager;
110 * Starts a new discovery scan.
112 * All available Serial Ports are scanned for P1 telegrams.
115 protected void startScan() {
116 logger.debug("Started DSMR discovery scan");
118 final Stream<SerialPortIdentifier> portEnum = serialPortManager.getIdentifiers();
120 // Traverse each available serial port
121 portEnum.forEach(portIdentifier -> {
123 currentScannedPortName = portIdentifier.getName();
124 if (portIdentifier.isCurrentlyOwned()) {
125 logger.trace("Possible port to check:{}, owned:{} by:{}", currentScannedPortName,
126 portIdentifier.isCurrentlyOwned(), portIdentifier.getCurrentOwner());
127 if (DSMR_PORT_NAME.equals(portIdentifier.getCurrentOwner())) {
128 logger.debug("The port {} is owned by this binding. If no DSMR meters will be found it "
129 + "might indicate the port is locked by an older instance of this binding. "
130 + "Restart the system to unlock the port.", currentScannedPortName);
133 logger.debug("Start discovery on serial port: {}", currentScannedPortName);
135 final DSMRTelegramListener telegramListener = new DSMRTelegramListener("",
136 CONFIGURATION_ADDITIONAL_KEY);
137 final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
138 portIdentifier.getName(), this, telegramListener, scheduler,
139 BAUDRATE_SWITCH_TIMEOUT_SECONDS);
140 device.setLenientMode(true);
141 currentScannedDevice = new DSMRDeviceRunnable(device, this);
142 currentScannedDevice.run();
149 protected synchronized void stopScan() {
151 stopSerialPortScan();
153 logger.debug("Stop DSMR discovery scan");
157 * Stops the serial port device.
159 private void stopSerialPortScan() {
160 logger.debug("Stop discovery scan on port [{}].", currentScannedPortName);
161 if (currentScannedDevice != null) {
162 currentScannedDevice.stop();
164 currentScannedDevice = null;
165 currentScannedPortName = "";
169 * Handle if telegrams are received.
171 * If there are cosem objects received a new bridge will we discovered.
173 * @param telegram the received telegram
176 public void handleTelegramReceived(final P1Telegram telegram) {
177 final List<CosemObject> cosemObjects = telegram.getCosemObjects();
179 if (logger.isDebugEnabled()) {
180 logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
182 if (telegram.getTelegramState() == TelegramState.INVALID_ENCRYPTION_KEY) {
183 bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
184 stopSerialPortScan();
185 } else if (!cosemObjects.isEmpty()) {
186 final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
187 meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
188 stopSerialPortScan();
195 * @return The {@link ThingUID} of the newly created bridge
197 private ThingUID bridgeDiscovered(final ThingTypeUID bridgeThingTypeUID) {
198 final ThingUID thingUID = new ThingUID(bridgeThingTypeUID,
199 Integer.toHexString(currentScannedPortName.hashCode()));
200 final boolean smarty = THING_TYPE_SMARTY_BRIDGE.equals(bridgeThingTypeUID);
201 final String label = String.format("@text/thing-type.dsmr.%s.label", smarty ? "smartyBridge" : "dsmrBridge");
203 // Construct the configuration for this meter
204 final Map<String, Object> properties = new HashMap<>();
205 properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
207 properties.put(CONFIGURATION_DECRYPTION_KEY, CONFIGURATION_DECRYPTION_KEY_EMPTY);
208 properties.put(CONFIGURATION_ADDITIONAL_KEY, CONFIGURATION_ADDITIONAL_KEY);
210 final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
211 .withThingType(bridgeThingTypeUID).withProperties(properties).withLabel(label).build();
213 logger.debug("[{}] discovery result:{}", currentScannedPortName, discoveryResult);
215 thingDiscovered(discoveryResult);
220 public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
221 logger.debug("[{}] Error on port during discovery: {}", currentScannedPortName, portEvent);
222 stopSerialPortScan();