2 * Copyright (c) 2010-2021 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 java.util.Collection;
16 import java.util.List;
18 import java.util.Map.Entry;
19 import java.util.Objects;
21 import java.util.stream.Collectors;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
25 import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
26 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
27 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
28 import org.openhab.binding.dsmr.internal.handler.DSMRBridgeHandler;
29 import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
30 import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
31 import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
32 import org.openhab.core.i18n.LocaleProvider;
33 import org.openhab.core.i18n.TranslationProvider;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.Thing;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * This implements the discovery service for new DSMR Meters on a active DSMR bridge.
42 * @author M. Volaart - Initial contribution
43 * @author Hilbrand Bouwkamp - Refactored code to detect meters during actual discovery phase.
46 public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P1TelegramListener {
48 private final Logger logger = LoggerFactory.getLogger(DSMRMeterDiscoveryService.class);
51 * The {@link DSMRBridgeHandler} instance
53 private final DSMRBridgeHandler dsmrBridgeHandler;
56 * Constructs a new {@link DSMRMeterDiscoveryService} attached to the give bridge handler.
58 * @param dsmrBridgeHandler The bridge handler this discovery service is attached to
60 public DSMRMeterDiscoveryService(DSMRBridgeHandler dsmrBridgeHandler) {
61 this.dsmrBridgeHandler = dsmrBridgeHandler;
65 protected void startScan() {
66 logger.debug("Start discovery on existing DSMR bridge.");
67 dsmrBridgeHandler.setLenientMode(true);
68 dsmrBridgeHandler.registerDSMRMeterListener(this);
72 protected synchronized void stopScan() {
73 logger.debug("Stop discovery on existing DSMR bridge.");
74 dsmrBridgeHandler.setLenientMode(false);
76 dsmrBridgeHandler.unregisterDSMRMeterListener(this);
80 public void telegramReceived(P1Telegram telegram) {
81 if (logger.isDebugEnabled()) {
82 logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
84 final Entry<Collection<DSMRMeterDescriptor>, Map<CosemObjectType, CosemObject>> detectedMeters = meterDetector
85 .detectMeters(telegram);
86 verifyUnregisteredCosemObjects(telegram, detectedMeters.getValue());
87 validateConfiguredMeters(dsmrBridgeHandler.getThing().getThings(),
88 detectedMeters.getKey().stream().map(md -> md.getMeterType()).collect(Collectors.toSet()));
89 detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
92 protected void verifyUnregisteredCosemObjects(P1Telegram telegram,
93 Map<CosemObjectType, CosemObject> undetectedCosemObjects) {
94 if (!undetectedCosemObjects.isEmpty()) {
95 if (undetectedCosemObjects.entrySet().stream()
96 .anyMatch(e -> e.getKey() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
97 && e.getValue().getCosemValues().entrySet().stream().anyMatch(
98 cv -> cv.getValue() instanceof StringType && cv.getValue().toString().isEmpty()))) {
99 // Unregistered meter detected. log to the user.
100 reportUnregisteredMeters();
102 reportUnrecognizedCosemObjects(undetectedCosemObjects);
103 logger.info("There are unrecognized cosem values in the data received from the meter,"
104 + " which means some meters might not be detected. Please report your raw data as reference: {}",
105 telegram.getRawTelegram());
108 if (!telegram.getUnknownCosemObjects().isEmpty()) {
109 logger.info("There are unrecognized cosem values in the data received from the meter,"
110 + " which means you have values that can't be read by a channel: {}. Please report them and your raw data as reference: {}",
111 telegram.getUnknownCosemObjects().stream()
112 .map(e -> String.format("obis id:%s, value:%s", e.getKey(), e.getValue()))
113 .collect(Collectors.joining(", ")),
114 telegram.getRawTelegram());
119 * Called when Unrecognized cosem objects where found. This can be a bug or a new meter not yet supported.
121 * @param unidentifiedCosemObjects Map with the unrecognized.
123 protected void reportUnrecognizedCosemObjects(Map<CosemObjectType, CosemObject> unidentifiedCosemObjects) {
124 unidentifiedCosemObjects
125 .forEach((k, v) -> logger.info("Unrecognized cosem object '{}' found in the data: {}", k, v));
129 * Called when a meter equipment identifier is found that has an empty value. This
131 protected void reportUnregisteredMeters() {
133 "An unregistered meter has been found. Probably a new meter. Retry discovery once the meter is registered with the energy provider.");
137 * Validates if the meters configured by the user match with what is detected in the telegram. Some meters are a
138 * subset of other meters and therefore an invalid configured meter does work, but not all available data is
139 * available to the user.
141 * @param things The list of configured things
142 * @param configuredMeterTypes The set of meters detected in the telegram
144 private void validateConfiguredMeters(List<Thing> things, Set<DSMRMeterType> configuredMeterTypes) {
146 final Set<DSMRMeterType> configuredMeters = things.stream()
147 .map(Thing::getHandler)
148 .filter(DSMRMeterHandler.class::isInstance)
149 .map(DSMRMeterHandler.class::cast)
150 .map(DSMRMeterHandler::getMeterDescriptor)
151 .filter(Objects::nonNull)
152 .map(d -> d.getMeterType())
153 .collect(Collectors.toSet());
155 // Create list of all configured meters that are not in the detected list. If not empty meters might not be
156 // correctly configured.
157 final List<DSMRMeterType> invalidConfigured = configuredMeters.stream()
158 .filter(dm -> !configuredMeterTypes.contains(dm)).collect(Collectors.toList());
159 // Create a list of all detected meters not yet configured.
160 final List<DSMRMeterType> unconfiguredMeters = configuredMeterTypes.stream()
161 .filter(dm -> !configuredMeters.contains(dm)).collect(Collectors.toList());
163 if (!invalidConfigured.isEmpty()) {
164 reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
169 * Called when the validation finds in inconsistency between configured meters.
171 * @param invalidConfigured The list of invalid configured meters
172 * @param unconfiguredMeters The list of meters that were detected, but not configured
174 protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
175 List<DSMRMeterType> unconfiguredMeters) {
177 "Possible incorrect meters configured. These are configured: {}."
178 + "But the following unconfigured meters are found in the data received from the meter: {}",
179 invalidConfigured.stream().map(m -> m.name()).collect(Collectors.joining(", ")),
180 unconfiguredMeters.stream().map(m -> m.name()).collect(Collectors.joining(", ")));
183 public void setLocaleProvider(final LocaleProvider localeProvider) {
184 this.localeProvider = localeProvider;
187 public void unsetLocaleProvider() {
188 this.localeProvider = null;
191 public void setTranslationProvider(TranslationProvider i18nProvider) {
192 this.i18nProvider = i18nProvider;
195 public void unsetTranslationProvider() {
196 this.i18nProvider = null;