]> git.basschouten.com Git - openhab-addons.git/blob
7717699c0d9e67f53277beaf5f0833311b0ecb7b
[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 java.util.Collection;
16 import java.util.List;
17 import java.util.Map.Entry;
18 import java.util.Objects;
19 import java.util.Set;
20 import java.util.stream.Collectors;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
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 an 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(final 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(final P1Telegram telegram) {
101         if (logger.isDebugEnabled()) {
102             logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
103         }
104         final Entry<Collection<DSMRMeterDescriptor>, List<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     @Override
113     public void onError(final DSMRErrorStatus state, final String message) {
114         logger.info("Telegram could not be parsed correctly, failed with state: {}, {}", state, message);
115     }
116
117     protected void verifyUnregisteredCosemObjects(final P1Telegram telegram, final List<CosemObject> list) {
118         if (!list.isEmpty()) {
119             if (list.stream()
120                     .anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
121                             && e.getCosemValues().entrySet().stream().anyMatch(
122                                     cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) {
123                 // Unregistered meter detected. log to the user.
124                 reportUnregisteredMeters();
125             } else {
126                 reportUnrecognizedCosemObjects(list);
127                 logger.info("There are unrecognized cosem values in the data received from the meter,"
128                         + " which means some meters might not be detected. Please report your raw data as reference: {}",
129                         telegram.getRawTelegram());
130             }
131         }
132         if (!telegram.getUnknownCosemObjects().isEmpty()) {
133             logger.info("There are unrecognized cosem values in the data received from the meter,"
134                     + " which means you have values that can't be read by a channel: {}. Please report them and your raw data as reference: {}",
135                     telegram.getUnknownCosemObjects().stream()
136                             .map(e -> String.format("obis id:%s, value:%s", e.getKey(), e.getValue()))
137                             .collect(Collectors.joining(", ")),
138                     telegram.getRawTelegram());
139         }
140     }
141
142     /**
143      * Called when Unrecognized cosem objects where found. This can be a bug or a new meter not yet supported.
144      *
145      * @param list Map with the unrecognized.
146      */
147     protected void reportUnrecognizedCosemObjects(final List<CosemObject> list) {
148         list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c));
149     }
150
151     /**
152      * Called when a meter equipment identifier is found that has an empty value. This
153      */
154     protected void reportUnregisteredMeters() {
155         logger.info(
156                 "An unregistered meter has been found. Probably a new meter. Retry discovery once the meter is registered with the energy provider.");
157     }
158
159     /**
160      * Validates if the meters configured by the user match with what is detected in the telegram. Some meters are a
161      * subset of other meters and therefore an invalid configured meter does work, but not all available data is
162      * available to the user.
163      *
164      * @param things The list of configured things
165      * @param configuredMeterTypes The set of meters detected in the telegram
166      */
167     private void validateConfiguredMeters(final List<Thing> things, final Set<DSMRMeterType> configuredMeterTypes) {
168         // @formatter:off
169         final Set<DSMRMeterType> configuredMeters = things.stream()
170                 .map(Thing::getHandler)
171                 .filter(DSMRMeterHandler.class::isInstance)
172                 .map(DSMRMeterHandler.class::cast)
173                 .map(DSMRMeterHandler::getMeterDescriptor)
174                 .filter(Objects::nonNull)
175                 .map(d -> d.getMeterType())
176                 .collect(Collectors.toSet());
177         // @formatter:on
178         // Create list of all configured meters that are not in the detected list. If not empty meters might not be
179         // correctly configured.
180         final List<DSMRMeterType> invalidConfigured = configuredMeters.stream()
181                 .filter(dm -> !configuredMeterTypes.contains(dm)).collect(Collectors.toList());
182         // Create a list of all detected meters not yet configured.
183         final List<DSMRMeterType> unconfiguredMeters = configuredMeterTypes.stream()
184                 .filter(dm -> !configuredMeters.contains(dm)).collect(Collectors.toList());
185
186         if (!invalidConfigured.isEmpty()) {
187             reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
188         }
189     }
190
191     /**
192      * Called when the validation finds in inconsistency between configured meters.
193      *
194      * @param invalidConfigured The list of invalid configured meters
195      * @param unconfiguredMeters The list of meters that were detected, but not configured
196      */
197     protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
198             final List<DSMRMeterType> unconfiguredMeters) {
199         logger.info(
200                 "Possible incorrect meters configured. These are configured: {}."
201                         + "But the following unconfigured meters are found in the data received from the meter: {}",
202                 invalidConfigured.stream().map(m -> m.name()).collect(Collectors.joining(", ")),
203                 unconfiguredMeters.stream().map(m -> m.name()).collect(Collectors.joining(", ")));
204     }
205 }