]> git.basschouten.com Git - openhab-addons.git/blob
fafd4966d77217cae3713d59860b0668f98d9642
[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.handler;
14
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.Map.Entry;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
24 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
25 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
26 import org.openhab.binding.dsmr.internal.meter.DSMRMeter;
27 import org.openhab.binding.dsmr.internal.meter.DSMRMeterConfiguration;
28 import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
29 import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.ThingStatusInfo;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.types.State;
39 import org.openhab.core.types.UnDefType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * The MeterHandler will create logic DSMR meter ThingTypes
45  *
46  * @author M. Volaart - Initial contribution
47  * @author Hilbrand Bouwkamp - Separated thing state update cycle from meter values received cycle
48  */
49 @NonNullByDefault
50 public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramListener {
51
52     private final Logger logger = LoggerFactory.getLogger(DSMRMeterHandler.class);
53
54     /**
55      * The DSMRMeter instance.
56      */
57     private @NonNullByDefault({}) DSMRMeter meter;
58
59     /**
60      * Last received cosem objects.
61      */
62     private List<CosemObject> lastReceivedValues = Collections.emptyList();
63
64     /**
65      * Reference to the meter watchdog.
66      */
67     private @NonNullByDefault({}) ScheduledFuture<?> meterWatchdog;
68
69     /**
70      * Creates a new MeterHandler for the given Thing.
71      *
72      * @param thing {@link Thing} to create the MeterHandler for
73      */
74     public DSMRMeterHandler(Thing thing) {
75         super(thing);
76     }
77
78     /**
79      * DSMR Meter don't support handling commands
80      */
81     @Override
82     public void handleCommand(ChannelUID channelUID, Command command) {
83         if (command == RefreshType.REFRESH) {
84             updateState();
85         }
86     }
87
88     /**
89      * Initializes a DSMR Meter
90      *
91      * This method will load the corresponding configuration
92      */
93     @Override
94     public void initialize() {
95         logger.debug("Initialize MeterHandler for Thing {}", getThing().getUID());
96         DSMRMeterType meterType;
97
98         try {
99             meterType = DSMRMeterType.valueOf(getThing().getThingTypeUID().getId().toUpperCase());
100         } catch (IllegalArgumentException iae) {
101             logger.warn(
102                     "{} could not be initialized due to an invalid meterType {}. Delete this Thing if the problem persists.",
103                     getThing(), getThing().getThingTypeUID().getId().toUpperCase());
104             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
105                     "@text/error.configuration.invalidmetertype");
106             return;
107         }
108         DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
109         DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, meterConfig.channel);
110         meter = new DSMRMeter(meterDescriptor);
111         meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh,
112                 TimeUnit.SECONDS);
113         updateStatus(ThingStatus.UNKNOWN);
114     }
115
116     @Override
117     public void dispose() {
118         if (meterWatchdog != null) {
119             meterWatchdog.cancel(false);
120             meterWatchdog = null;
121         }
122     }
123
124     /**
125      * Updates the state of all channels from the last received Cosem values from the meter. The lastReceivedValues are
126      * cleared after processing here so when it does contain values the next time this method is called and it contains
127      * values those are new values.
128      */
129     private synchronized void updateState() {
130         logger.trace("Update state for device: {}", getThing().getThingTypeUID().getId());
131         if (!lastReceivedValues.isEmpty()) {
132             for (CosemObject cosemObject : lastReceivedValues) {
133                 String channel = cosemObject.getType().name().toLowerCase();
134
135                 for (Entry<String, ? extends State> entry : cosemObject.getCosemValues().entrySet()) {
136                     if (!entry.getKey().isEmpty()) {
137                         /* CosemObject has a specific sub channel */
138                         channel += "_" + entry.getKey();
139                     }
140                     State newState = entry.getValue();
141                     logger.debug("Updating state for channel {} to value {}", channel, newState);
142                     updateState(channel, newState);
143                 }
144             }
145             if (getThing().getStatus() != ThingStatus.ONLINE) {
146                 updateStatus(ThingStatus.ONLINE);
147             }
148             lastReceivedValues = Collections.emptyList();
149         }
150     }
151
152     /**
153      * Callback for received meter values. When this method is called but the telegram has no values for this meter this
154      * meter is set to offline because something is wrong, possible the meter has been removed.
155      *
156      * @param telegram The received telegram
157      */
158     @Override
159     public void telegramReceived(P1Telegram telegram) {
160         lastReceivedValues = Collections.emptyList();
161         final DSMRMeter localMeter = meter;
162
163         if (localMeter == null) {
164             return;
165         }
166         List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects());
167
168         if (filteredValues.isEmpty()) {
169             if (getThing().getStatus() == ThingStatus.ONLINE) {
170                 setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.thing.nodata");
171             }
172         } else {
173             if (logger.isTraceEnabled()) {
174                 logger.trace("Received {} objects for {}", filteredValues.size(), getThing().getThingTypeUID().getId());
175             }
176             lastReceivedValues = filteredValues;
177             if (getThing().getStatus() != ThingStatus.ONLINE) {
178                 updateState();
179             }
180         }
181     }
182
183     @Override
184     public synchronized void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
185         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
186                 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
187             // Set status to offline --> Thing will become online after receiving meter values
188             setDeviceOffline(ThingStatusDetail.NONE, null);
189         } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
190             setDeviceOffline(ThingStatusDetail.BRIDGE_OFFLINE, null);
191         }
192     }
193
194     /**
195      * @return Returns the {@link DSMRMeterDescriptor} this object is configured with
196      */
197     public @Nullable DSMRMeterDescriptor getMeterDescriptor() {
198         return meter == null ? null : meter.getMeterDescriptor();
199     }
200
201     /**
202      * Convenience method to set the meter off line.
203      *
204      * @param status off line status
205      * @param details off line detailed message
206      */
207     private void setDeviceOffline(ThingStatusDetail status, @Nullable String details) {
208         updateStatus(ThingStatus.OFFLINE, status, details);
209         getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.NULL));
210     }
211 }