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