]> git.basschouten.com Git - openhab-addons.git/blob
03878c26b711296ddc73b11221c4141111ce144f
[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.airgradient.internal.handler;
14
15 import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CHANNEL_CALIBRATION;
16 import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CHANNEL_LEDS_MODE;
17
18 import java.util.List;
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.eclipse.jetty.client.HttpClient;
26 import org.openhab.binding.airgradient.internal.communication.AirGradientCommunicationException;
27 import org.openhab.binding.airgradient.internal.communication.RemoteAPIController;
28 import org.openhab.binding.airgradient.internal.config.AirGradientAPIConfiguration;
29 import org.openhab.binding.airgradient.internal.model.Measure;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.types.State;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.google.gson.Gson;
43
44 /**
45  * The {@link AirGradientAPIHandler} is responsible for handling commands, which are
46  * sent to one of the channels.
47  *
48  * @author Jørgen Austvik - Initial contribution
49  */
50 @NonNullByDefault
51 public class AirGradientLocalHandler extends BaseThingHandler {
52
53     private final Logger logger = LoggerFactory.getLogger(AirGradientLocalHandler.class);
54
55     private @Nullable ScheduledFuture<?> pollingJob;
56     private final HttpClient httpClient;
57     private final Gson gson;
58
59     private @NonNullByDefault({}) RemoteAPIController apiController = null;
60     private @NonNullByDefault({}) AirGradientAPIConfiguration apiConfig = null;
61
62     public AirGradientLocalHandler(Thing thing, HttpClient httpClient) {
63         super(thing);
64         this.httpClient = httpClient;
65         this.gson = new Gson();
66     }
67
68     @Override
69     public void handleCommand(ChannelUID channelUID, Command command) {
70         logger.debug("Channel {}: {}", channelUID, command.toFullString());
71         if (command instanceof RefreshType) {
72             pollingCode();
73         } else if (CHANNEL_LEDS_MODE.equals(channelUID.getId())) {
74             if (command instanceof StringType stringCommand) {
75                 setLedModeOnDevice(stringCommand.toFullString());
76             } else {
77                 logger.warn("Received command {} for channel {}, but it needs a string command", command.toString(),
78                         channelUID.getId());
79             }
80         } else if (CHANNEL_CALIBRATION.equals(channelUID.getId())) {
81             if (command instanceof StringType stringCommand) {
82                 if ("co2".equals(stringCommand.toFullString())) {
83                     calibrateCo2OnDevice();
84                 } else {
85                     logger.warn(
86                             "Received unknown command {} for calibration on channel {}, which we don't know how to handle",
87                             command.toString(), channelUID.getId());
88                 }
89             }
90         } else {
91             // This is read only
92             logger.warn("Received command {} for channel {}, which we don't know how to handle", command.toString(),
93                     channelUID.getId());
94         }
95     }
96
97     @Override
98     public void initialize() {
99         apiConfig = getConfigAs(AirGradientAPIConfiguration.class);
100         if (!apiConfig.isValid()) {
101             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
102                     "Need to set hostname to a valid URL. Refresh interval needs to be a positive integer.");
103             return;
104         }
105
106         apiController = new RemoteAPIController(httpClient, gson, apiConfig);
107
108         // set the thing status to UNKNOWN temporarily and let the background task decide for the real status.
109         // the framework is then able to reuse the resources from the thing handler initialization.
110         // we set this upfront to reliably check status updates in unit tests.
111         updateStatus(ThingStatus.UNKNOWN);
112
113         pollingJob = scheduler.scheduleWithFixedDelay(this::pollingCode, 0, apiConfig.refreshInterval,
114                 TimeUnit.SECONDS);
115     }
116
117     protected void pollingCode() {
118         try {
119             List<Measure> measures = apiController.getMeasures();
120             updateStatus(ThingStatus.ONLINE);
121
122             if (measures.size() != 1) {
123                 logger.warn("Expecting single set of measures for local device, but got {} measures", measures.size());
124                 return;
125             }
126
127             updateProperties(MeasureHelper.createProperties(measures.get(0)));
128             Map<String, State> states = MeasureHelper.createStates(measures.get(0));
129             for (Map.Entry<String, State> entry : states.entrySet()) {
130                 if (isLinked(entry.getKey())) {
131                     updateState(entry.getKey(), entry.getValue());
132                 }
133             }
134         } catch (AirGradientCommunicationException agce) {
135             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, agce.getMessage());
136         }
137     }
138
139     private void setLedModeOnDevice(String mode) {
140         try {
141             apiController.setLedMode(getSerialNo(), mode);
142             updateStatus(ThingStatus.ONLINE);
143         } catch (AirGradientCommunicationException agce) {
144             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, agce.getMessage());
145         }
146     }
147
148     private void calibrateCo2OnDevice() {
149         try {
150             apiController.calibrateCo2(getSerialNo());
151             updateStatus(ThingStatus.ONLINE);
152         } catch (AirGradientCommunicationException agce) {
153             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, agce.getMessage());
154         }
155     }
156
157     /**
158      * Returns the serial number of this sensor.
159      *
160      * @return serial number of this sensor.
161      */
162     public String getSerialNo() {
163         String serialNo = thing.getProperties().get(Thing.PROPERTY_SERIAL_NUMBER);
164         if (serialNo == null) {
165             serialNo = "";
166         }
167
168         return serialNo;
169     }
170
171     @Override
172     public void dispose() {
173         ScheduledFuture<?> pollingJob = this.pollingJob;
174         if (pollingJob != null) {
175             pollingJob.cancel(true);
176             this.pollingJob = null;
177         }
178     }
179
180     protected void setConfiguration(AirGradientAPIConfiguration config) {
181         this.apiConfig = config;
182     }
183 }