]> git.basschouten.com Git - openhab-addons.git/blob
64c7606445e6ee6fab84cb2d9158314220a6891a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.dsmr.internal.discovery;
14
15 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
16 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT;
17 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
18 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY_EMPTY;
19 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_SERIAL_PORT;
20 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.DSMR_PORT_NAME;
21 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_DSMR_BRIDGE;
22 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
23
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.stream.Stream;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
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.DSMRErrorStatus;
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.P1TelegramListener;
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;
52
53 /**
54  * This implements the discovery service for detecting new DSMR Meters.
55  *
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).
61  *
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.
64  *
65  * If there are communication problems the service will give a warning and give up
66  *
67  * @author M. Volaart - Initial contribution
68  * @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
69  */
70 @NonNullByDefault
71 @Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
72 public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements P1TelegramListener {
73
74     /**
75      * The timeout used to switch baudrate if no valid data is received within that time frame.
76      */
77     private static final int BAUDRATE_SWITCH_TIMEOUT_SECONDS = 25;
78
79     private final Logger logger = LoggerFactory.getLogger(DSMRBridgeDiscoveryService.class);
80
81     /**
82      * Serial Port Manager.
83      */
84     private final SerialPortManager serialPortManager;
85
86     /**
87      * DSMR Device that is scanned when discovery process in progress.
88      */
89     private @Nullable DSMRDeviceRunnable currentScannedDevice;
90
91     /**
92      * Name of the serial port that is scanned when discovery process in progress.
93      */
94     private String currentScannedPortName = "";
95
96     /**
97      * Keeps a boolean during time discovery process in progress.
98      */
99     private boolean scanning;
100
101     @Activate
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;
107     }
108
109     /**
110      * Starts a new discovery scan.
111      *
112      * All available Serial Ports are scanned for P1 telegrams.
113      */
114     @Override
115     protected void startScan() {
116         logger.debug("Started DSMR discovery scan");
117         scanning = true;
118         final Stream<SerialPortIdentifier> portEnum = serialPortManager.getIdentifiers();
119
120         // Traverse each available serial port
121         portEnum.forEach(portIdentifier -> {
122             if (scanning) {
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("""
129                                 The port {} is owned by this binding. If no DSMR meters will be found it \
130                                 might indicate the port is locked by an older instance of this binding. \
131                                 Restart the system to unlock the port.\
132                                 """, currentScannedPortName);
133                     }
134                 } else {
135                     logger.debug("Start discovery on serial port: {}", currentScannedPortName);
136                     //
137                     final DSMRTelegramListener telegramListener = new DSMRTelegramListener("",
138                             CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
139                     final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
140                             portIdentifier.getName(), this, telegramListener, scheduler,
141                             BAUDRATE_SWITCH_TIMEOUT_SECONDS);
142                     device.setLenientMode(true);
143                     currentScannedDevice = new DSMRDeviceRunnable(device, this);
144                     currentScannedDevice.run();
145                 }
146             }
147         });
148     }
149
150     @Override
151     protected synchronized void stopScan() {
152         scanning = false;
153         stopSerialPortScan();
154         super.stopScan();
155         logger.debug("Stop DSMR discovery scan");
156     }
157
158     /**
159      * Stops the serial port device.
160      */
161     private void stopSerialPortScan() {
162         logger.debug("Stop discovery scan on port [{}].", currentScannedPortName);
163         if (currentScannedDevice != null) {
164             currentScannedDevice.stop();
165         }
166         currentScannedDevice = null;
167         currentScannedPortName = "";
168     }
169
170     /**
171      * Handle if telegrams are received.
172      *
173      * If there are cosem objects received a new bridge will we discovered.
174      *
175      * @param telegram the received telegram
176      */
177     @Override
178     public void telegramReceived(final P1Telegram telegram) {
179         final List<CosemObject> cosemObjects = telegram.getCosemObjects();
180
181         if (logger.isDebugEnabled()) {
182             logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
183         }
184         final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
185         meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
186         stopSerialPortScan();
187     }
188
189     @Override
190     public void onError(final DSMRErrorStatus portEvent, final String message) {
191         if (portEvent == DSMRErrorStatus.INVALID_DECRYPTION_KEY) {
192             bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
193         } else {
194             logger.debug("[{}] Error on port during discovery: {} - {}", currentScannedPortName, portEvent, message);
195         }
196         stopSerialPortScan();
197     }
198
199     /**
200      * Creates a bridge.
201      *
202      * @return The {@link ThingUID} of the newly created bridge
203      */
204     private ThingUID bridgeDiscovered(final ThingTypeUID bridgeThingTypeUID) {
205         final ThingUID thingUID = new ThingUID(bridgeThingTypeUID,
206                 Integer.toHexString(currentScannedPortName.hashCode()));
207         final boolean smarty = THING_TYPE_SMARTY_BRIDGE.equals(bridgeThingTypeUID);
208         final String label = String.format("@text/thing-type.dsmr.%s.label", smarty ? "smartyBridge" : "dsmrBridge");
209
210         // Construct the configuration for this meter
211         final Map<String, Object> properties = new HashMap<>();
212         properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
213         if (smarty) {
214             properties.put(CONFIGURATION_DECRYPTION_KEY, CONFIGURATION_DECRYPTION_KEY_EMPTY);
215             properties.put(CONFIGURATION_ADDITIONAL_KEY, CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
216         }
217         final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
218                 .withThingType(bridgeThingTypeUID).withProperties(properties).withLabel(label).build();
219
220         logger.debug("[{}] discovery result:{}", currentScannedPortName, discoveryResult);
221
222         thingDiscovered(discoveryResult);
223         return thingUID;
224     }
225 }