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