]> git.basschouten.com Git - openhab-addons.git/blob
06bf01f874016c86bfc6f3237ccba5edc05f7035
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.*;
16
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.stream.Stream;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
25 import org.openhab.binding.dsmr.internal.device.DSMREventListener;
26 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
27 import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
28 import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
29 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
30 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
31 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
32 import org.openhab.core.config.discovery.DiscoveryResult;
33 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
34 import org.openhab.core.config.discovery.DiscoveryService;
35 import org.openhab.core.i18n.LocaleProvider;
36 import org.openhab.core.i18n.TranslationProvider;
37 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
38 import org.openhab.core.io.transport.serial.SerialPortManager;
39 import org.openhab.core.thing.ThingTypeUID;
40 import org.openhab.core.thing.ThingUID;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Reference;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47  * This implements the discovery service for detecting new DSMR Meters.
48  *
49  * The service will iterate over the available serial ports and open the given serial port and wait for telegrams. If
50  * the port is already owned because it's already detected this service will ignore it. But it will report a warning in
51  * case the port was locked due to a crash.
52  * After {@link #BAUDRATE_SWITCH_TIMEOUT_SECONDS} seconds it will switch the baud rate and wait again for telegrams.
53  * When that doesn't produce any results the service will give up (assuming no DSMR Bridge is present).
54  *
55  * If a telegram is received with at least 1 Cosem Object a bridge is assumed available and a Thing is added (regardless
56  * if there were problems receiving the telegram) and the discovery is stopped.
57  *
58  * If there are communication problems the service will give an warning and give up
59  *
60  * @author M. Volaart - Initial contribution
61  * @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
62  */
63 @NonNullByDefault
64 @Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
65 public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements DSMREventListener {
66
67     /**
68      * The timeout used to switch baudrate if no valid data is received within that time frame.
69      */
70     private static final int BAUDRATE_SWITCH_TIMEOUT_SECONDS = 25;
71
72     private final Logger logger = LoggerFactory.getLogger(DSMRBridgeDiscoveryService.class);
73
74     /**
75      * Serial Port Manager.
76      */
77     private @NonNullByDefault({}) SerialPortManager serialPortManager;
78
79     /**
80      * DSMR Device that is scanned when discovery process in progress.
81      */
82     private @Nullable DSMRDeviceRunnable currentScannedDevice;
83
84     /**
85      * Name of the serial port that is scanned when discovery process in progress.
86      */
87     private String currentScannedPortName = "";
88
89     /**
90      * Keeps a boolean during time discovery process in progress.
91      */
92     private boolean scanning;
93
94     /**
95      * Starts a new discovery scan.
96      *
97      * All available Serial Ports are scanned for P1 telegrams.
98      */
99     @Override
100     protected void startScan() {
101         logger.debug("Started DSMR discovery scan");
102         scanning = true;
103         Stream<SerialPortIdentifier> portEnum = serialPortManager.getIdentifiers();
104
105         // Traverse each available serial port
106         portEnum.forEach(portIdentifier -> {
107             if (scanning) {
108                 currentScannedPortName = portIdentifier.getName();
109                 if (portIdentifier.isCurrentlyOwned()) {
110                     logger.trace("Possible port to check:{}, owned:{} by:{}", currentScannedPortName,
111                             portIdentifier.isCurrentlyOwned(), portIdentifier.getCurrentOwner());
112                     if (DSMR_PORT_NAME.equals(portIdentifier.getCurrentOwner())) {
113                         logger.debug("The port {} is owned by this binding. If no DSMR meters will be found it "
114                                 + "might indicate the port is locked by an older instance of this binding. "
115                                 + "Restart the system to unlock the port.", currentScannedPortName);
116                     }
117                 } else {
118                     logger.debug("Start discovery on serial port: {}", currentScannedPortName);
119                     //
120                     final DSMRTelegramListener telegramListener = new DSMRTelegramListener("");
121                     final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
122                             portIdentifier.getName(), this, telegramListener, scheduler,
123                             BAUDRATE_SWITCH_TIMEOUT_SECONDS);
124                     device.setLenientMode(true);
125                     currentScannedDevice = new DSMRDeviceRunnable(device, this);
126                     currentScannedDevice.run();
127                 }
128             }
129         });
130     }
131
132     @Override
133     protected synchronized void stopScan() {
134         scanning = false;
135         stopSerialPortScan();
136         super.stopScan();
137         logger.debug("Stop DSMR discovery scan");
138     }
139
140     /**
141      * Stops the serial port device.
142      */
143     private void stopSerialPortScan() {
144         logger.debug("Stop discovery scan on port [{}].", currentScannedPortName);
145         if (currentScannedDevice != null) {
146             currentScannedDevice.stop();
147         }
148         currentScannedDevice = null;
149         currentScannedPortName = "";
150     }
151
152     /**
153      * Handle if telegrams are received.
154      *
155      * If there are cosem objects received a new bridge will we discovered.
156      *
157      * @param telegram the received telegram
158      */
159     @Override
160     public void handleTelegramReceived(P1Telegram telegram) {
161         List<CosemObject> cosemObjects = telegram.getCosemObjects();
162
163         if (logger.isDebugEnabled()) {
164             logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
165         }
166         if (telegram.getTelegramState() == TelegramState.INVALID_ENCRYPTION_KEY) {
167             bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
168             stopSerialPortScan();
169         } else if (!cosemObjects.isEmpty()) {
170             ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
171             meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
172             stopSerialPortScan();
173         }
174     }
175
176     /**
177      * Creates a bridge.
178      *
179      * @return The {@link ThingUID} of the newly created bridge
180      */
181     private ThingUID bridgeDiscovered(ThingTypeUID bridgeThingTypeUID) {
182         ThingUID thingUID = new ThingUID(bridgeThingTypeUID, Integer.toHexString(currentScannedPortName.hashCode()));
183         final boolean smarty = THING_TYPE_SMARTY_BRIDGE.equals(bridgeThingTypeUID);
184         final String label = String.format("@text/thing-type.dsmr.%s.label", smarty ? "smartyBridge" : "dsmrBridge");
185
186         // Construct the configuration for this meter
187         Map<String, Object> properties = new HashMap<>();
188         properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
189         if (smarty) {
190             properties.put(CONFIGURATION_DECRYPTION_KEY, CONFIGURATION_DECRYPTION_KEY_EMPTY);
191         }
192         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(bridgeThingTypeUID)
193                 .withProperties(properties).withLabel(label).build();
194
195         logger.debug("[{}] discovery result:{}", currentScannedPortName, discoveryResult);
196
197         thingDiscovered(discoveryResult);
198         return thingUID;
199     }
200
201     @Override
202     public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
203         logger.debug("[{}] Error on port during discovery: {}", currentScannedPortName, portEvent);
204         stopSerialPortScan();
205     }
206
207     @Reference
208     protected void setSerialPortManager(final SerialPortManager serialPortManager) {
209         this.serialPortManager = serialPortManager;
210     }
211
212     protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
213         this.serialPortManager = null;
214     }
215
216     @Reference
217     protected void setLocaleProvider(final LocaleProvider localeProvider) {
218         this.localeProvider = localeProvider;
219     }
220
221     protected void unsetLocaleProvider(final LocaleProvider localeProvider) {
222         this.localeProvider = null;
223     }
224
225     @Reference
226     protected void setTranslationProvider(TranslationProvider i18nProvider) {
227         this.i18nProvider = i18nProvider;
228     }
229
230     protected void unsetTranslationProvider(TranslationProvider i18nProvider) {
231         this.i18nProvider = null;
232     }
233 }