]> git.basschouten.com Git - openhab-addons.git/blob
6b69008c99927a723cb56987086d64f755eaf7ad
[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.danfossairunit.internal.handler;
14
15 import static org.openhab.binding.danfossairunit.internal.DanfossAirUnitBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.danfossairunit.internal.Channel;
26 import org.openhab.binding.danfossairunit.internal.DanfossAirUnit;
27 import org.openhab.binding.danfossairunit.internal.DanfossAirUnitCommunicationController;
28 import org.openhab.binding.danfossairunit.internal.DanfossAirUnitConfiguration;
29 import org.openhab.binding.danfossairunit.internal.DanfossAirUnitWriteAccessor;
30 import org.openhab.binding.danfossairunit.internal.UnexpectedResponseValueException;
31 import org.openhab.binding.danfossairunit.internal.ValueCache;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.State;
40 import org.openhab.core.types.UnDefType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * The {@link DanfossAirUnitHandler} is responsible for handling commands, which are
46  * sent to one of the channels.
47  *
48  * @author Ralf Duckstein - Initial contribution
49  * @author Robert Bach - heavy refactorings
50  * @author Jacob Laursen - Refactoring, bugfixes and enhancements
51  */
52 @NonNullByDefault
53 public class DanfossAirUnitHandler extends BaseThingHandler {
54
55     private static final int TCP_PORT = 30046;
56     private static final int POLLING_INTERVAL_SECONDS = 5;
57     private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitHandler.class);
58     private @NonNullByDefault({}) DanfossAirUnitConfiguration config;
59     private @Nullable ValueCache valueCache;
60     private @Nullable ScheduledFuture<?> pollingJob;
61     private @Nullable DanfossAirUnitCommunicationController communicationController;
62     private @Nullable DanfossAirUnit airUnit;
63
64     public DanfossAirUnitHandler(Thing thing) {
65         super(thing);
66     }
67
68     @Override
69     public void handleCommand(ChannelUID channelUID, Command command) {
70         if (command instanceof RefreshType) {
71             updateAllChannels();
72         } else {
73             try {
74                 DanfossAirUnit localAirUnit = this.airUnit;
75                 if (localAirUnit != null) {
76                     Channel channel = Channel.getByName(channelUID.getIdWithoutGroup());
77                     DanfossAirUnitWriteAccessor writeAccessor = channel.getWriteAccessor();
78                     if (writeAccessor != null) {
79                         updateState(channelUID, writeAccessor.access(localAirUnit, command));
80                     }
81                 } else {
82                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
83                             "@text/offline.connection-not-initialized");
84                     return;
85                 }
86             } catch (IllegalArgumentException e) {
87                 logger.debug("Ignoring unknown channel id: {}", channelUID.getIdWithoutGroup(), e);
88             } catch (IOException ioe) {
89                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, ioe.getMessage());
90             }
91         }
92     }
93
94     @Override
95     public void initialize() {
96         updateStatus(ThingStatus.UNKNOWN);
97         config = getConfigAs(DanfossAirUnitConfiguration.class);
98         valueCache = new ValueCache(config.updateUnchangedValuesEveryMillis);
99         try {
100             var localCommunicationController = new DanfossAirUnitCommunicationController(
101                     InetAddress.getByName(config.host), TCP_PORT);
102             this.communicationController = localCommunicationController;
103             var localAirUnit = new DanfossAirUnit(localCommunicationController);
104             this.airUnit = localAirUnit;
105             scheduler.execute(() -> {
106                 try {
107                     thing.setProperty(PROPERTY_UNIT_NAME, localAirUnit.getUnitName());
108                     thing.setProperty(PROPERTY_SERIAL, localAirUnit.getUnitSerialNumber());
109                     startPolling();
110                     updateStatus(ThingStatus.ONLINE);
111                 } catch (IOException e) {
112                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
113                 }
114             });
115         } catch (UnknownHostException e) {
116             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
117                     "@text/offline.communication-error.unknown-host [\"" + config.host + "\"]");
118             return;
119         }
120     }
121
122     private void updateAllChannels() {
123         DanfossAirUnit localAirUnit = this.airUnit;
124         if (localAirUnit == null) {
125             return;
126         }
127
128         logger.debug("Updating DanfossHRV data '{}'", getThing().getUID());
129
130         for (Channel channel : Channel.values()) {
131             if (Thread.interrupted()) {
132                 logger.debug("Polling thread interrupted...");
133                 return;
134             }
135             try {
136                 updateState(channel.getGroup().getGroupName(), channel.getChannelName(),
137                         channel.getReadAccessor().access(localAirUnit));
138                 if (getThing().getStatus() == ThingStatus.OFFLINE) {
139                     updateStatus(ThingStatus.ONLINE);
140                 }
141             } catch (UnexpectedResponseValueException e) {
142                 updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
143                 logger.debug(
144                         "Cannot update channel {}: an unexpected or invalid response has been received from the air unit: {}",
145                         channel.getChannelName(), e.getMessage());
146                 if (getThing().getStatus() == ThingStatus.OFFLINE) {
147                     updateStatus(ThingStatus.ONLINE);
148                 }
149             } catch (IOException e) {
150                 updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
151                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
152                 logger.debug("Cannot update channel {}: an error occurred retrieving the value: {}",
153                         channel.getChannelName(), e.getMessage());
154             }
155         }
156     }
157
158     @Override
159     public void dispose() {
160         logger.debug("Disposing Danfoss HRV handler '{}'", getThing().getUID());
161
162         stopPolling();
163
164         this.airUnit = null;
165
166         DanfossAirUnitCommunicationController localCommunicationController = this.communicationController;
167         if (localCommunicationController != null) {
168             localCommunicationController.disconnect();
169         }
170         this.communicationController = null;
171     }
172
173     private synchronized void startPolling() {
174         this.pollingJob = scheduler.scheduleWithFixedDelay(this::updateAllChannels, POLLING_INTERVAL_SECONDS,
175                 config.refreshInterval, TimeUnit.SECONDS);
176     }
177
178     private synchronized void stopPolling() {
179         ScheduledFuture<?> localPollingJob = this.pollingJob;
180         if (localPollingJob != null) {
181             localPollingJob.cancel(true);
182         }
183         this.pollingJob = null;
184     }
185
186     private void updateState(String groupId, String channelId, State state) {
187         ValueCache cache = valueCache;
188         if (cache == null) {
189             return;
190         }
191
192         if (cache.updateValue(channelId, state)) {
193             updateState(new ChannelUID(thing.getUID(), groupId, channelId), state);
194         }
195     }
196 }