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