]> git.basschouten.com Git - openhab-addons.git/blob
a742eb1208fc854a8f87ebcc8a307d1a05df86ba
[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 java.util.Collection;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import java.util.Objects;
20 import java.util.Set;
21 import java.util.stream.Collectors;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
26 import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
27 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
28 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
29 import org.openhab.binding.dsmr.internal.handler.DSMRBridgeHandler;
30 import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
31 import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
32 import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.binding.ThingHandler;
36 import org.openhab.core.thing.binding.ThingHandlerService;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * This implements the discovery service for new DSMR Meters on a active DSMR bridge.
42  *
43  * @author M. Volaart - Initial contribution
44  * @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
45  */
46 @NonNullByDefault
47 public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P1TelegramListener, ThingHandlerService {
48
49     private final Logger logger = LoggerFactory.getLogger(DSMRMeterDiscoveryService.class);
50
51     /**
52      * The {@link DSMRBridgeHandler} instance
53      */
54     private @NonNullByDefault({}) DSMRBridgeHandler dsmrBridgeHandler;
55
56     /**
57      * Constructs a new {@link DSMRMeterDiscoveryService} attached to the give bridge handler.
58      *
59      * @param dsmrBridgeHandler The bridge handler this discovery service is attached to
60      */
61     public DSMRMeterDiscoveryService() {
62         super();
63         this.i18nProvider = DSMRI18nProviderTracker.i18nProvider;
64         this.localeProvider = DSMRI18nProviderTracker.localeProvider;
65     }
66
67     @Override
68     public void deactivate() {
69         super.deactivate();
70     }
71
72     @Override
73     public @Nullable ThingHandler getThingHandler() {
74         return dsmrBridgeHandler;
75     }
76
77     @Override
78     public void setThingHandler(ThingHandler handler) {
79         if (handler instanceof DSMRBridgeHandler) {
80             dsmrBridgeHandler = (DSMRBridgeHandler) handler;
81         }
82     }
83
84     @Override
85     protected void startScan() {
86         logger.debug("Start discovery on existing DSMR bridge.");
87         dsmrBridgeHandler.setLenientMode(true);
88         dsmrBridgeHandler.registerDSMRMeterListener(this);
89     }
90
91     @Override
92     protected synchronized void stopScan() {
93         logger.debug("Stop discovery on existing DSMR bridge.");
94         dsmrBridgeHandler.setLenientMode(false);
95         super.stopScan();
96         dsmrBridgeHandler.unregisterDSMRMeterListener(this);
97     }
98
99     @Override
100     public void telegramReceived(P1Telegram telegram) {
101         if (logger.isDebugEnabled()) {
102             logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
103         }
104         final Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectedMeters = meterDetector
105                 .detectMeters(telegram);
106         verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue());
107         validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(),
108                 detectedMeters.getKey().stream().map(md -> md.getMeterType()).collect(Collectors.toSet()));
109         detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
110     }
111
112     protected void verifyUnregisteredCosemObjects(P1Telegram telegram,
113             Map<CosemObjectType, CosemObject> undetectedCosemObjects) {
114         if (!undetectedCosemObjects.isEmpty()) {
115             if (undetectedCosemObjects.entrySet().stream()
116                     .anyMatch(e -> e.getKey() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
117                             && e.getValue().getCosemValues().entrySet().stream().anyMatch(
118                                     cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) {
119                 // Unregistered meter detected. log to the user.
120                 reportUnregisteredMeters();
121             } else {
122                 reportUnrecognizedCosemObjects(undetectedCosemObjects);
123                 logger.info("There are unrecognized cosem values in the data received from the meter,"
124                         + " which means some meters might not be detected. Please report your raw data as reference: {}",
125                         telegram.getRawTelegram());
126             }
127         }
128         if (!telegram.getUnknownCosemObjects().isEmpty()) {
129             logger.info("There are unrecognized cosem values in the data received from the meter,"
130                     + " which means you have values that can't be read by a channel: {}. Please report them and your raw data as reference: {}",
131                     telegram.getUnknownCosemObjects().stream()
132                             .map(e -> String.format("obis id:%s, value:%s", e.getKey(), e.getValue()))
133                             .collect(Collectors.joining(", ")),
134                     telegram.getRawTelegram());
135         }
136     }
137
138     /**
139      * Called when Unrecognized cosem objects where found. This can be a bug or a new meter not yet supported.
140      *
141      * @param unidentifiedCosemObjects Map with the unrecognized.
142      */
143     protected void reportUnrecognizedCosemObjects(Map<CosemObjectType, CosemObject> unidentifiedCosemObjects) {
144         unidentifiedCosemObjects
145                 .forEach((k, v) -> logger.info("Unrecognized cosem object '{}' found in the data: {}", k, v));
146     }
147
148     /**
149      * Called when a meter equipment identifier is found that has an empty value. This
150      */
151     protected void reportUnregisteredMeters() {
152         logger.info(
153                 "An unregistered meter has been found. Probably a new meter. Retry discovery once the meter is registered with the energy provider.");
154     }
155
156     /**
157      * Validates if the meters configured by the user match with what is detected in the telegram. Some meters are a
158      * subset of other meters and therefore an invalid configured meter does work, but not all available data is
159      * available to the user.
160      *
161      * @param things The list of configured things
162      * @param configuredMeterTypes The set of meters detected in the telegram
163      */
164     private void validateConfiguredMeters(List<Thing> things, Set<DSMRMeterType> configuredMeterTypes) {
165         // @formatter:off
166         final Set<DSMRMeterType> configuredMeters = things.stream()
167                 .map(Thing::getHandler)
168                 .filter(DSMRMeterHandler.class::isInstance)
169                 .map(DSMRMeterHandler.class::cast)
170                 .map(DSMRMeterHandler::getMeterDescriptor)
171                 .filter(Objects::nonNull)
172                 .map(d -> d.getMeterType())
173                 .collect(Collectors.toSet());
174         // @formatter:on
175         // Create list of all configured meters that are not in the detected list. If not empty meters might not be
176         // correctly configured.
177         final List<DSMRMeterType> invalidConfigured = configuredMeters.stream()
178                 .filter(dm -> !configuredMeterTypes.contains(dm)).collect(Collectors.toList());
179         // Create a list of all detected meters not yet configured.
180         final List<DSMRMeterType> unconfiguredMeters = configuredMeterTypes.stream()
181                 .filter(dm -> !configuredMeters.contains(dm)).collect(Collectors.toList());
182
183         if (!invalidConfigured.isEmpty()) {
184             reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
185         }
186     }
187
188     /**
189      * Called when the validation finds in inconsistency between configured meters.
190      *
191      * @param invalidConfigured The list of invalid configured meters
192      * @param unconfiguredMeters The list of meters that were detected, but not configured
193      */
194     protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
195             List<DSMRMeterType> unconfiguredMeters) {
196         logger.info(
197                 "Possible incorrect meters configured. These are configured: {}."
198                         + "But the following unconfigured meters are found in the data received from the meter: {}",
199                 invalidConfigured.stream().map(m -> m.name()).collect(Collectors.joining(", ")),
200                 unconfiguredMeters.stream().map(m -> m.name()).collect(Collectors.joining(", ")));
201     }
202 }