]> git.basschouten.com Git - openhab-addons.git/blob
be57577106223f98f3360f71c550dc98d29cf17d
[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.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.Collection;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
27 import java.util.function.Consumer;
28 import java.util.function.Function;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration;
33 import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetSensorConfiguration;
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         Collection<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_GATEWAY.getId() + ".dynamic-channel." + measuredValue.getChannelPrefix();
193         String label = translationProvider.getText(bundle, channelKey + ".label", measuredValue.getDebugName(),
194                 localeProvider.getLocale(), measuredValue.getChannelNumber());
195         if (label != null) {
196             builder.withLabel(label);
197         }
198         String description = translationProvider.getText(bundle, channelKey + ".description", null,
199                 localeProvider.getLocale(), measuredValue.getChannelNumber());
200         if (description != null) {
201             builder.withDescription(description);
202         }
203         @Nullable
204         ChannelType type = channelTypeRegistry.getChannelType(channelTypeId);
205         if (type != null) {
206             builder.withAcceptedItemType(type.getItemType());
207         }
208         return builder.build();
209     }
210
211     private void updateBridgeInfo() {
212         @Nullable
213         String firmware = query(GatewayQueryService::getFirmwareVersion);
214         Map<String, String> properties = new HashMap<>(thing.getProperties());
215         if (firmware != null) {
216             var fwString = firmware.split("_?V");
217             if (fwString.length > 1) {
218                 properties.put(Thing.PROPERTY_MODEL_ID, fwString[0]);
219                 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, fwString[1]);
220             }
221         }
222
223         SystemInfo systemInfo = query(GatewayQueryService::fetchSystemInfo);
224         if (systemInfo != null && systemInfo.getFrequency() != null) {
225             properties.put(PROPERTY_FREQUENCY, systemInfo.getFrequency() + " MHz");
226         }
227         if (!thing.getProperties().equals(properties)) {
228             updateBridgeThing(bridgeBuilder -> bridgeBuilder.withProperties(properties));
229         }
230     }
231
232     private void updateBridgeThing(Consumer<BridgeBuilder> customizer) {
233         BridgeBuilder bridge = editThing();
234         customizer.accept(bridge);
235         updateThing(bridge.build());
236     }
237
238     private void startDiscoverJob() {
239         ScheduledFuture<?> job = discoverJob;
240         if (job == null || job.isCancelled()) {
241             int discoverInterval = thing.getConfiguration().as(FineOffsetGatewayConfiguration.class).discoverInterval;
242             discoverJob = scheduler.scheduleWithFixedDelay(this::fetchAndUpdateSensors, 0, discoverInterval,
243                     TimeUnit.SECONDS);
244         }
245     }
246
247     private void stopDiscoverJob() {
248         ScheduledFuture<?> job = this.discoverJob;
249         if (job != null) {
250             job.cancel(true);
251         }
252         this.discoverJob = null;
253     }
254
255     private void startPollingJob() {
256         ScheduledFuture<?> job = pollingJob;
257         if (job == null || job.isCancelled()) {
258             int pollingInterval = thing.getConfiguration().as(FineOffsetGatewayConfiguration.class).pollingInterval;
259             pollingJob = scheduler.scheduleWithFixedDelay(this::updateLiveData, 5, pollingInterval, TimeUnit.SECONDS);
260         }
261     }
262
263     private void stopPollingJob() {
264         ScheduledFuture<?> job = this.pollingJob;
265         if (job != null) {
266             job.cancel(true);
267         }
268         this.pollingJob = null;
269     }
270
271     private <T> @Nullable T query(Function<GatewayQueryService, T> delegate) {
272         @Nullable
273         GatewayQueryService queryService = this.gatewayQueryService;
274         if (queryService == null) {
275             return null;
276         }
277         return delegate.apply(queryService);
278     }
279
280     @Override
281     public void dispose() {
282         disposed = true;
283         @Nullable
284         GatewayQueryService queryService = this.gatewayQueryService;
285         if (queryService != null) {
286             try {
287                 queryService.close();
288             } catch (IOException e) {
289                 logger.debug("failed to close queryService", e);
290             }
291         }
292         this.gatewayQueryService = null;
293         this.sensorDeviceMap = null;
294         stopPollingJob();
295         stopDiscoverJob();
296     }
297 }