]> git.basschouten.com Git - openhab-addons.git/blob
28e609a344bfee7c4811de77af1853fed71a85c8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.nikohomecontrol.internal.handler;
14
15 import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
16 import static org.openhab.core.library.unit.Units.KILOWATT_HOUR;
17 import static org.openhab.core.types.RefreshType.REFRESH;
18
19 import java.time.LocalDateTime;
20 import java.time.ZoneOffset;
21 import java.time.ZonedDateTime;
22 import java.time.format.DateTimeFormatter;
23 import java.util.HashMap;
24 import java.util.Map;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeter;
29 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeterEvent;
30 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
31 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.MeterType;
32 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcMeter1;
33 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcMeter2;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.unit.SIUnits;
37 import org.openhab.core.library.unit.Units;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.UnDefType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * The {@link NikoHomeControlMeterHandler} is responsible for handling commands, which are
50  * sent to one of the channels.
51  *
52  * @author Mark Herwege - Initial Contribution
53  */
54 @NonNullByDefault
55 public class NikoHomeControlMeterHandler extends NikoHomeControlBaseHandler implements NhcMeterEvent {
56
57     private final Logger logger = LoggerFactory.getLogger(NikoHomeControlMeterHandler.class);
58
59     private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
60
61     private volatile @Nullable NhcMeter nhcMeter;
62
63     public NikoHomeControlMeterHandler(Thing thing) {
64         super(thing);
65     }
66
67     @Override
68     void handleCommandSelection(ChannelUID channelUID, Command command) {
69         NhcMeter nhcMeter = this.nhcMeter;
70         if (nhcMeter == null) {
71             logger.debug("meter with ID {} not initialized", deviceId);
72             return;
73         }
74
75         if (REFRESH.equals(command)) {
76             switch (channelUID.getId()) {
77                 case CHANNEL_POWER:
78                     meterPowerEvent(nhcMeter.getPower());
79                     break;
80                 case CHANNEL_ENERGY:
81                 case CHANNEL_GAS:
82                 case CHANNEL_WATER:
83                 case CHANNEL_ENERGY_DAY:
84                 case CHANNEL_GAS_DAY:
85                 case CHANNEL_WATER_DAY:
86                 case CHANNEL_ENERGY_LAST:
87                 case CHANNEL_GAS_LAST:
88                 case CHANNEL_WATER_LAST:
89                     LocalDateTime lastReadingUTC = nhcMeter.getLastReading();
90                     if (lastReadingUTC != null) {
91                         meterReadingEvent(nhcMeter.getReading(), nhcMeter.getDayReading(), lastReadingUTC);
92                     }
93                     break;
94                 default:
95                     logger.debug("unexpected command for channel {}", channelUID.getId());
96             }
97         }
98     }
99
100     @Override
101     public void initialize() {
102         initialized = false;
103
104         NikoHomeControlMeterConfig config = getConfig().as(NikoHomeControlMeterConfig.class);
105         deviceId = config.meterId;
106
107         NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
108         if (bridgeHandler == null) {
109             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110                     "@text/offline.configuration-error.invalid-bridge-handler");
111             return;
112         }
113
114         updateStatus(ThingStatus.UNKNOWN);
115
116         Bridge bridge = getBridge();
117         if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
118             // We need to do this in a separate thread because we may have to wait for the
119             // communication to become active
120             commStartThread = scheduler.submit(this::startCommunication);
121         }
122     }
123
124     @Override
125     synchronized void startCommunication() {
126         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
127
128         if (nhcComm == null) {
129             return;
130         }
131
132         if (!nhcComm.communicationActive()) {
133             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
134                     "@text/offline.communication-error");
135             return;
136         }
137
138         NhcMeter nhcMeter = nhcComm.getMeters().get(deviceId);
139         if (nhcMeter == null) {
140             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
141                     "@text/offline.configuration-error.deviceId");
142             return;
143         }
144
145         MeterType meterType = nhcMeter.getType();
146         if (!(MeterType.ENERGY_LIVE.equals(meterType) || MeterType.ENERGY.equals(meterType)
147                 || MeterType.GAS.equals(meterType) || MeterType.WATER.equals(meterType))) {
148             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
149                     "@text/offline.configuration-error.meterType");
150             return;
151         }
152
153         nhcMeter.setEventHandler(this);
154
155         updateProperties(nhcMeter);
156
157         String location = nhcMeter.getLocation();
158         if (thing.getLocation() == null) {
159             thing.setLocation(location);
160         }
161
162         this.nhcMeter = nhcMeter;
163
164         initialized = true;
165         deviceInitialized();
166     }
167
168     @Override
169     void refresh() {
170         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
171         if (nhcComm != null) {
172             nhcComm.startMeter(deviceId, getConfig().as(NikoHomeControlMeterConfig.class).refresh);
173             // Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item
174             // linked to the channel
175             if (isLinked(CHANNEL_POWER)) {
176                 nhcComm.startMeterLive(deviceId);
177             }
178         }
179     }
180
181     @Override
182     public void dispose() {
183         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
184         if (nhcComm != null) {
185             nhcComm.stopMeterLive(deviceId);
186             nhcComm.stopMeter(deviceId);
187             NhcMeter meter = nhcComm.getMeters().get(deviceId);
188             if (meter != null) {
189                 meter.unsetEventHandler();
190             }
191         }
192         nhcMeter = null;
193         super.dispose();
194     }
195
196     private void updateProperties(NhcMeter nhcMeter) {
197         Map<String, String> properties = new HashMap<>();
198
199         if (nhcMeter instanceof NhcMeter1) {
200             NhcMeter1 meter = (NhcMeter1) nhcMeter;
201             properties.put("type", meter.getMeterType());
202             LocalDateTime referenceDate = meter.getReferenceDate();
203             if (referenceDate != null) {
204                 properties.put("startdateUTC", referenceDate.format(DATE_TIME_FORMAT));
205             }
206         } else if (nhcMeter instanceof NhcMeter2) {
207             NhcMeter2 meter = (NhcMeter2) nhcMeter;
208             properties.put(PROPERTY_DEVICE_TYPE, meter.getDeviceType());
209             properties.put(PROPERTY_DEVICE_TECHNOLOGY, meter.getDeviceTechnology());
210             properties.put(PROPERTY_DEVICE_MODEL, meter.getDeviceModel());
211         }
212
213         thing.setProperties(properties);
214     }
215
216     @Override
217     public void meterPowerEvent(@Nullable Integer power) {
218         NhcMeter nhcMeter = this.nhcMeter;
219         if (nhcMeter == null) {
220             logger.debug("meter with ID {} not initialized", deviceId);
221             return;
222         }
223
224         MeterType meterType = nhcMeter.getType();
225         if (meterType != MeterType.ENERGY_LIVE) {
226             logger.debug("meter with ID {} does not support live readings", deviceId);
227             return;
228         }
229
230         if (power == null) {
231             updateState(CHANNEL_POWER, UnDefType.UNDEF);
232         } else {
233             boolean invert = getConfig().as(NikoHomeControlMeterConfig.class).invert;
234             int value = (invert ? -1 : 1) * power;
235             updateState(CHANNEL_POWER, new QuantityType<>(value, Units.WATT));
236         }
237         updateStatus(ThingStatus.ONLINE);
238     }
239
240     @Override
241     public void meterReadingEvent(double meterReading, double meterReadingDay, LocalDateTime lastReadingUTC) {
242         NhcMeter nhcMeter = this.nhcMeter;
243         if (nhcMeter == null) {
244             logger.debug("meter with ID {} not initialized", deviceId);
245             return;
246         }
247
248         NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
249         if (bridgeHandler == null) {
250             logger.debug("Cannot update meter channels, no bridge handler");
251             return;
252         }
253         ZonedDateTime lastReading = lastReadingUTC.atZone(ZoneOffset.UTC)
254                 .withZoneSameInstant(bridgeHandler.getTimeZone());
255
256         boolean invert = getConfig().as(NikoHomeControlMeterConfig.class).invert;
257         double value = (invert ? -1 : 1) * meterReading;
258         double dayValue = (invert ? -1 : 1) * meterReadingDay;
259
260         MeterType meterType = nhcMeter.getType();
261         switch (meterType) {
262             case ENERGY_LIVE:
263             case ENERGY:
264                 updateState(CHANNEL_ENERGY, new QuantityType<>(value, KILOWATT_HOUR));
265                 updateState(CHANNEL_ENERGY_DAY, new QuantityType<>(dayValue, KILOWATT_HOUR));
266                 updateState(CHANNEL_ENERGY_LAST, new DateTimeType(lastReading));
267                 updateStatus(ThingStatus.ONLINE);
268                 break;
269             case GAS:
270                 updateState(CHANNEL_GAS, new QuantityType<>(value, SIUnits.CUBIC_METRE));
271                 updateState(CHANNEL_GAS_DAY, new QuantityType<>(dayValue, SIUnits.CUBIC_METRE));
272                 updateState(CHANNEL_GAS_LAST, new DateTimeType(lastReading));
273                 updateStatus(ThingStatus.ONLINE);
274                 break;
275             case WATER:
276                 updateState(CHANNEL_WATER, new QuantityType<>(value, SIUnits.CUBIC_METRE));
277                 updateState(CHANNEL_WATER_DAY, new QuantityType<>(dayValue, SIUnits.CUBIC_METRE));
278                 updateState(CHANNEL_WATER_LAST, new DateTimeType(lastReading));
279                 updateStatus(ThingStatus.ONLINE);
280                 break;
281             default:
282                 break;
283         }
284     }
285
286     @Override
287     public void channelLinked(ChannelUID channelUID) {
288         // Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item
289         // linked to the channel
290         if (!CHANNEL_POWER.equals(channelUID.getId())) {
291             return;
292         }
293         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
294         if (nhcComm != null) {
295             // This can be expensive, therefore do it in a job.
296             scheduler.submit(() -> {
297                 if (!nhcComm.communicationActive()) {
298                     restartCommunication(nhcComm);
299                 }
300
301                 if (nhcComm.communicationActive()) {
302                     nhcComm.startMeterLive(deviceId);
303                 }
304             });
305         }
306     }
307
308     @Override
309     public void channelUnlinked(ChannelUID channelUID) {
310         if (!CHANNEL_POWER.equals(channelUID.getId())) {
311             return;
312         }
313         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
314         if (nhcComm != null) {
315             // This can be expensive, therefore do it in a job.
316             scheduler.submit(() -> {
317                 if (!nhcComm.communicationActive()) {
318                     restartCommunication(nhcComm);
319                 }
320
321                 if (nhcComm.communicationActive()) {
322                     nhcComm.stopMeterLive(deviceId);
323                     // as this is momentary power production/consumption, we set it UNDEF as we do not get readings
324                     // anymore
325                     updateState(CHANNEL_POWER, UnDefType.UNDEF);
326                 }
327             });
328         }
329     }
330 }