]> git.basschouten.com Git - openhab-addons.git/blob
1d37544f0bc255eb534e5a687c442e038f194ac8
[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.fineoffsetweatherstation.internal.handler;
14
15 import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_GATEWAY;
16 import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_SENSOR;
17
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Optional;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26 import java.util.function.Function;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration;
31 import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetSensorConfiguration;
32 import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants;
33 import org.openhab.binding.fineoffsetweatherstation.internal.discovery.FineOffsetGatewayDiscoveryService;
34 import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
35 import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
36 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
37 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
38 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
39 import org.openhab.binding.fineoffsetweatherstation.internal.service.FineOffsetGatewayQueryService;
40 import org.openhab.core.i18n.LocaleProvider;
41 import org.openhab.core.i18n.TimeZoneProvider;
42 import org.openhab.core.i18n.TranslationProvider;
43 import org.openhab.core.thing.Bridge;
44 import org.openhab.core.thing.Channel;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingUID;
49 import org.openhab.core.thing.binding.BaseBridgeHandler;
50 import org.openhab.core.thing.binding.ThingHandler;
51 import org.openhab.core.thing.binding.builder.BridgeBuilder;
52 import org.openhab.core.thing.binding.builder.ChannelBuilder;
53 import org.openhab.core.thing.type.ChannelKind;
54 import org.openhab.core.thing.type.ChannelType;
55 import org.openhab.core.thing.type.ChannelTypeRegistry;
56 import org.openhab.core.thing.type.ChannelTypeUID;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.State;
59 import org.osgi.framework.Bundle;
60 import org.osgi.framework.FrameworkUtil;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 /**
65  * The {@link FineOffsetGatewayHandler} is responsible for handling commands, which are
66  * sent to one of the channels.
67  *
68  * @author Andreas Berger - Initial contribution
69  */
70 @NonNullByDefault
71 public class FineOffsetGatewayHandler extends BaseBridgeHandler {
72
73     private static final String PROPERTY_FREQUENCY = "frequency";
74
75     private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayHandler.class);
76     private final Bundle bundle;
77     private final ConversionContext conversionContext;
78
79     private @Nullable FineOffsetGatewayQueryService gatewayQueryService;
80
81     private final FineOffsetGatewayDiscoveryService gatewayDiscoveryService;
82     private final ChannelTypeRegistry channelTypeRegistry;
83     private final TranslationProvider translationProvider;
84     private final LocaleProvider localeProvider;
85
86     private final ThingUID bridgeUID;
87
88     private @Nullable Map<SensorGatewayBinding, SensorDevice> sensorDeviceMap;
89     private @Nullable ScheduledFuture<?> pollingJob;
90     private @Nullable ScheduledFuture<?> discoverJob;
91     private boolean disposed;
92
93     public FineOffsetGatewayHandler(Bridge bridge, FineOffsetGatewayDiscoveryService gatewayDiscoveryService,
94             ChannelTypeRegistry channelTypeRegistry, TranslationProvider translationProvider,
95             LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider) {
96         super(bridge);
97         bridgeUID = bridge.getUID();
98         this.gatewayDiscoveryService = gatewayDiscoveryService;
99         this.channelTypeRegistry = channelTypeRegistry;
100         this.translationProvider = translationProvider;
101         this.localeProvider = localeProvider;
102         this.bundle = FrameworkUtil.getBundle(FineOffsetGatewayDiscoveryService.class);
103         this.conversionContext = new ConversionContext(timeZoneProvider.getTimeZone());
104     }
105
106     @Override
107     public void handleCommand(ChannelUID channelUID, Command command) {
108     }
109
110     @Override
111     public void initialize() {
112         FineOffsetGatewayConfiguration config = getConfigAs(FineOffsetGatewayConfiguration.class);
113         gatewayQueryService = new FineOffsetGatewayQueryService(config, this::updateStatus, conversionContext);
114
115         updateStatus(ThingStatus.UNKNOWN);
116         fetchAndUpdateSensors();
117         disposed = false;
118         updateBridgeInfo();
119         startDiscoverJob();
120         startPollingJob();
121     }
122
123     private void fetchAndUpdateSensors() {
124         @Nullable
125         Map<SensorGatewayBinding, SensorDevice> deviceMap = query(FineOffsetGatewayQueryService::getRegisteredSensors);
126         sensorDeviceMap = deviceMap;
127         updateSensors();
128         if (deviceMap != null) {
129             gatewayDiscoveryService.addSensors(bridgeUID, deviceMap.values());
130         }
131     }
132
133     private void updateSensors() {
134         ((Bridge) thing).getThings().forEach(this::updateSensorThing);
135     }
136
137     private void updateSensorThing(Thing thing) {
138         Map<SensorGatewayBinding, SensorDevice> sensorMap = sensorDeviceMap;
139         if (!THING_TYPE_SENSOR.equals(thing.getThingTypeUID()) || sensorMap == null) {
140             return;
141         }
142         SensorGatewayBinding sensor = thing.getConfiguration().as(FineOffsetSensorConfiguration.class).sensor;
143         Optional.ofNullable(thing.getHandler()).filter(FineOffsetSensorHandler.class::isInstance)
144                 .map(FineOffsetSensorHandler.class::cast)
145                 .ifPresent(sensorHandler -> sensorHandler.updateSensorState(sensorMap.get(sensor)));
146     }
147
148     @Override
149     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
150         updateSensorThing(childThing);
151     }
152
153     private void updateLiveData() {
154         if (disposed) {
155             return;
156         }
157         List<MeasuredValue> data = query(FineOffsetGatewayQueryService::getLiveData);
158         if (data == null) {
159             return;
160         }
161
162         List<Channel> channels = new ArrayList<>();
163         for (MeasuredValue measuredValue : data) {
164             @Nullable
165             Channel channel = thing.getChannel(measuredValue.getChannelId());
166             if (channel == null) {
167                 channel = createChannel(measuredValue);
168                 if (channel != null) {
169                     channels.add(channel);
170                 }
171             } else {
172                 State state = measuredValue.getState();
173                 updateState(channel.getUID(), state);
174             }
175         }
176         if (!channels.isEmpty()) {
177             updateThing(editThing().withChannels(channels).build());
178         }
179     }
180
181     private @Nullable Channel createChannel(MeasuredValue measuredValue) {
182         ChannelTypeUID channelTypeId = measuredValue.getChannelTypeUID();
183         if (channelTypeId == null) {
184             logger.debug("cannot create channel for {}", measuredValue.getDebugName());
185             return null;
186         }
187         ChannelBuilder builder = ChannelBuilder.create(new ChannelUID(thing.getUID(), measuredValue.getChannelId()))
188                 .withKind(ChannelKind.STATE).withType(channelTypeId);
189         String channelKey = "thing-type." + FineOffsetWeatherStationBindingConstants.BINDING_ID + "."
190                 + THING_TYPE_GATEWAY.getId() + ".channel." + measuredValue.getChannelId();
191         String label = translationProvider.getText(bundle, channelKey + ".label", measuredValue.getDebugName(),
192                 localeProvider.getLocale());
193         if (label != null) {
194             builder.withLabel(label);
195         }
196         String description = translationProvider.getText(bundle, channelKey + ".description", null,
197                 localeProvider.getLocale());
198         if (description != null) {
199             builder.withDescription(description);
200         }
201         @Nullable
202         ChannelType type = channelTypeRegistry.getChannelType(channelTypeId);
203         if (type != null) {
204             builder.withAcceptedItemType(type.getItemType());
205         }
206         return builder.build();
207     }
208
209     private void updateBridgeInfo() {
210         @Nullable
211         String firmware = query(FineOffsetGatewayQueryService::getFirmwareVersion);
212         Map<String, String> properties = new HashMap<>(thing.getProperties());
213         if (firmware != null) {
214             var fwString = firmware.split("_V");
215             if (fwString.length > 1) {
216                 properties.put(Thing.PROPERTY_MODEL_ID, fwString[0]);
217                 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, fwString[1]);
218             }
219         }
220
221         SystemInfo systemInfo = query(FineOffsetGatewayQueryService::fetchSystemInfo);
222         if (systemInfo != null && systemInfo.getFrequency() != null) {
223             properties.put(PROPERTY_FREQUENCY, systemInfo.getFrequency() + " MHz");
224         }
225         if (!thing.getProperties().equals(properties)) {
226             BridgeBuilder bridge = editThing();
227             bridge.withProperties(properties);
228             updateThing(bridge.build());
229         }
230     }
231
232     private void startDiscoverJob() {
233         ScheduledFuture<?> job = discoverJob;
234         if (job == null || job.isCancelled()) {
235             int discoverInterval = thing.getConfiguration().as(FineOffsetGatewayConfiguration.class).discoverInterval;
236             discoverJob = scheduler.scheduleWithFixedDelay(this::fetchAndUpdateSensors, 0, discoverInterval,
237                     TimeUnit.SECONDS);
238         }
239     }
240
241     private void stopDiscoverJob() {
242         ScheduledFuture<?> job = this.discoverJob;
243         if (job != null) {
244             job.cancel(true);
245         }
246         this.discoverJob = null;
247     }
248
249     private void startPollingJob() {
250         ScheduledFuture<?> job = pollingJob;
251         if (job == null || job.isCancelled()) {
252             int pollingInterval = thing.getConfiguration().as(FineOffsetGatewayConfiguration.class).pollingInterval;
253             pollingJob = scheduler.scheduleWithFixedDelay(this::updateLiveData, 5, pollingInterval, TimeUnit.SECONDS);
254         }
255     }
256
257     private void stopPollingJob() {
258         ScheduledFuture<?> job = this.pollingJob;
259         if (job != null) {
260             job.cancel(true);
261         }
262         this.pollingJob = null;
263     }
264
265     private <T> @Nullable T query(Function<FineOffsetGatewayQueryService, T> delegate) {
266         @Nullable
267         FineOffsetGatewayQueryService queryService = this.gatewayQueryService;
268         if (queryService == null) {
269             return null;
270         }
271         return delegate.apply(queryService);
272     }
273
274     @Override
275     public void dispose() {
276         disposed = true;
277         @Nullable
278         FineOffsetGatewayQueryService queryService = this.gatewayQueryService;
279         if (queryService != null) {
280             try {
281                 queryService.close();
282             } catch (IOException e) {
283                 logger.debug("failed to close queryService", e);
284             }
285         }
286         this.gatewayQueryService = null;
287         this.sensorDeviceMap = null;
288         stopPollingJob();
289         stopDiscoverJob();
290     }
291 }