]> git.basschouten.com Git - openhab-addons.git/blob
f94e7932dcba38858412c55363a538e1cd1fbb17
[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.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
66     public DanfossAirUnitHandler(Thing thing) {
67         super(thing);
68     }
69
70     @Override
71     public void handleCommand(ChannelUID channelUID, Command command) {
72         if (command instanceof RefreshType) {
73             updateAllChannels();
74         } else {
75             try {
76                 DanfossAirUnit localAirUnit = this.airUnit;
77                 if (localAirUnit != null) {
78                     Channel channel = Channel.getByName(channelUID.getIdWithoutGroup());
79                     DanfossAirUnitWriteAccessor writeAccessor = channel.getWriteAccessor();
80                     if (writeAccessor != null) {
81                         updateState(channelUID, writeAccessor.access(localAirUnit, command));
82                     }
83                 } else {
84                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
85                             "@text/offline.connection-not-initialized");
86                     return;
87                 }
88             } catch (IllegalArgumentException e) {
89                 logger.debug("Ignoring unknown channel id: {}", channelUID.getIdWithoutGroup(), e);
90             } catch (IOException ioe) {
91                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, ioe.getMessage());
92             }
93         }
94     }
95
96     @Override
97     public void initialize() {
98         updateStatus(ThingStatus.UNKNOWN);
99         config = getConfigAs(DanfossAirUnitConfiguration.class);
100         valueCache = new ValueCache(config.updateUnchangedValuesEveryMillis);
101         removeDeprecatedChannels();
102         try {
103             var localCommunicationController = new DanfossAirUnitCommunicationController(
104                     InetAddress.getByName(config.host), TCP_PORT);
105             this.communicationController = localCommunicationController;
106             var localAirUnit = new DanfossAirUnit(localCommunicationController);
107             this.airUnit = localAirUnit;
108             scheduler.execute(() -> {
109                 try {
110                     thing.setProperty(PROPERTY_UNIT_NAME, localAirUnit.getUnitName());
111                     thing.setProperty(PROPERTY_SERIAL, localAirUnit.getUnitSerialNumber());
112                     startPolling();
113                     updateStatus(ThingStatus.ONLINE);
114                 } catch (IOException e) {
115                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
116                 }
117             });
118         } catch (UnknownHostException e) {
119             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
120                     "@text/offline.communication-error.unknown-host [\"" + config.host + "\"]");
121             return;
122         }
123     }
124
125     private void removeDeprecatedChannels() {
126         ChannelGroupUID mainChannelGroupUid = new ChannelGroupUID(thing.getUID(), ChannelGroup.MAIN.getGroupName());
127         ChannelUID manualFanSpeedChannelUid = new ChannelUID(mainChannelGroupUid,
128                 Channel.CHANNEL_MANUAL_FAN_SPEED.getChannelName());
129         if (this.isLinked(manualFanSpeedChannelUid)) {
130             ChannelUID manualFanStepChannelUid = new ChannelUID(mainChannelGroupUid,
131                     Channel.CHANNEL_MANUAL_FAN_STEP.getChannelName());
132             logger.warn("Channel '{}' is deprecated, please use '{}' instead.", manualFanSpeedChannelUid,
133                     manualFanStepChannelUid);
134         } else {
135             logger.debug("Removing deprecated unlinked channel '{}'.", manualFanSpeedChannelUid);
136             updateThing(editThing().withoutChannel(manualFanSpeedChannelUid).build());
137         }
138     }
139
140     private void updateAllChannels() {
141         DanfossAirUnit localAirUnit = this.airUnit;
142         if (localAirUnit == null) {
143             return;
144         }
145
146         logger.debug("Updating DanfossHRV data '{}'", getThing().getUID());
147
148         for (Channel channel : Channel.values()) {
149             if (Thread.interrupted()) {
150                 logger.debug("Polling thread interrupted...");
151                 return;
152             }
153             try {
154                 updateState(channel.getGroup().getGroupName(), channel.getChannelName(),
155                         channel.getReadAccessor().access(localAirUnit));
156                 if (getThing().getStatus() == ThingStatus.OFFLINE) {
157                     updateStatus(ThingStatus.ONLINE);
158                 }
159             } catch (UnexpectedResponseValueException e) {
160                 updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
161                 logger.debug(
162                         "Cannot update channel {}: an unexpected or invalid response has been received from the air unit: {}",
163                         channel.getChannelName(), e.getMessage());
164                 if (getThing().getStatus() == ThingStatus.OFFLINE) {
165                     updateStatus(ThingStatus.ONLINE);
166                 }
167             } catch (IOException e) {
168                 updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
169                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
170                 logger.debug("Cannot update channel {}: an error occurred retrieving the value: {}",
171                         channel.getChannelName(), e.getMessage());
172             }
173         }
174     }
175
176     @Override
177     public void dispose() {
178         logger.debug("Disposing Danfoss HRV handler '{}'", getThing().getUID());
179
180         stopPolling();
181
182         this.airUnit = null;
183
184         DanfossAirUnitCommunicationController localCommunicationController = this.communicationController;
185         if (localCommunicationController != null) {
186             localCommunicationController.disconnect();
187         }
188         this.communicationController = null;
189     }
190
191     private synchronized void startPolling() {
192         this.pollingJob = scheduler.scheduleWithFixedDelay(this::updateAllChannels, POLLING_INTERVAL_SECONDS,
193                 config.refreshInterval, TimeUnit.SECONDS);
194     }
195
196     private synchronized void stopPolling() {
197         ScheduledFuture<?> localPollingJob = this.pollingJob;
198         if (localPollingJob != null) {
199             localPollingJob.cancel(true);
200         }
201         this.pollingJob = null;
202     }
203
204     private void updateState(String groupId, String channelId, State state) {
205         ValueCache cache = valueCache;
206         if (cache == null) {
207             return;
208         }
209
210         if (cache.updateValue(channelId, state)) {
211             updateState(new ChannelUID(thing.getUID(), groupId, channelId), state);
212         }
213     }
214 }