2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.fineoffsetweatherstation.internal.handler;
15 import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_GATEWAY;
16 import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_SENSOR;
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
23 import java.util.Optional;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26 import java.util.function.Function;
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;
65 * The {@link FineOffsetGatewayHandler} is responsible for handling commands, which are
66 * sent to one of the channels.
68 * @author Andreas Berger - Initial contribution
71 public class FineOffsetGatewayHandler extends BaseBridgeHandler {
73 private static final String PROPERTY_FREQUENCY = "frequency";
75 private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayHandler.class);
76 private final Bundle bundle;
77 private final ConversionContext conversionContext;
79 private @Nullable FineOffsetGatewayQueryService gatewayQueryService;
81 private final FineOffsetGatewayDiscoveryService gatewayDiscoveryService;
82 private final ChannelTypeRegistry channelTypeRegistry;
83 private final TranslationProvider translationProvider;
84 private final LocaleProvider localeProvider;
86 private final ThingUID bridgeUID;
88 private @Nullable Map<SensorGatewayBinding, SensorDevice> sensorDeviceMap;
89 private @Nullable ScheduledFuture<?> pollingJob;
90 private @Nullable ScheduledFuture<?> discoverJob;
91 private boolean disposed;
93 public FineOffsetGatewayHandler(Bridge bridge, FineOffsetGatewayDiscoveryService gatewayDiscoveryService,
94 ChannelTypeRegistry channelTypeRegistry, TranslationProvider translationProvider,
95 LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider) {
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());
107 public void handleCommand(ChannelUID channelUID, Command command) {
111 public void initialize() {
112 FineOffsetGatewayConfiguration config = getConfigAs(FineOffsetGatewayConfiguration.class);
113 gatewayQueryService = new FineOffsetGatewayQueryService(config, this::updateStatus, conversionContext);
115 updateStatus(ThingStatus.UNKNOWN);
116 fetchAndUpdateSensors();
123 private void fetchAndUpdateSensors() {
125 Map<SensorGatewayBinding, SensorDevice> deviceMap = query(FineOffsetGatewayQueryService::getRegisteredSensors);
126 sensorDeviceMap = deviceMap;
128 if (deviceMap != null) {
129 gatewayDiscoveryService.addSensors(bridgeUID, deviceMap.values());
133 private void updateSensors() {
134 ((Bridge) thing).getThings().forEach(this::updateSensorThing);
137 private void updateSensorThing(Thing thing) {
138 Map<SensorGatewayBinding, SensorDevice> sensorMap = sensorDeviceMap;
139 if (!THING_TYPE_SENSOR.equals(thing.getThingTypeUID()) || sensorMap == null) {
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)));
149 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
150 updateSensorThing(childThing);
153 private void updateLiveData() {
157 List<MeasuredValue> data = query(FineOffsetGatewayQueryService::getLiveData);
162 List<Channel> channels = new ArrayList<>();
163 for (MeasuredValue measuredValue : data) {
165 Channel channel = thing.getChannel(measuredValue.getChannelId());
166 if (channel == null) {
167 channel = createChannel(measuredValue);
168 if (channel != null) {
169 channels.add(channel);
172 State state = measuredValue.getState();
173 updateState(channel.getUID(), state);
176 if (!channels.isEmpty()) {
177 updateThing(editThing().withChannels(channels).build());
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());
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());
194 builder.withLabel(label);
196 String description = translationProvider.getText(bundle, channelKey + ".description", null,
197 localeProvider.getLocale());
198 if (description != null) {
199 builder.withDescription(description);
202 ChannelType type = channelTypeRegistry.getChannelType(channelTypeId);
204 builder.withAcceptedItemType(type.getItemType());
206 return builder.build();
209 private void updateBridgeInfo() {
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]);
221 SystemInfo systemInfo = query(FineOffsetGatewayQueryService::fetchSystemInfo);
222 if (systemInfo != null && systemInfo.getFrequency() != null) {
223 properties.put(PROPERTY_FREQUENCY, systemInfo.getFrequency() + " MHz");
225 if (!thing.getProperties().equals(properties)) {
226 BridgeBuilder bridge = editThing();
227 bridge.withProperties(properties);
228 updateThing(bridge.build());
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,
241 private void stopDiscoverJob() {
242 ScheduledFuture<?> job = this.discoverJob;
246 this.discoverJob = null;
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);
257 private void stopPollingJob() {
258 ScheduledFuture<?> job = this.pollingJob;
262 this.pollingJob = null;
265 private <T> @Nullable T query(Function<FineOffsetGatewayQueryService, T> delegate) {
267 FineOffsetGatewayQueryService queryService = this.gatewayQueryService;
268 if (queryService == null) {
271 return delegate.apply(queryService);
275 public void dispose() {
278 FineOffsetGatewayQueryService queryService = this.gatewayQueryService;
279 if (queryService != null) {
281 queryService.close();
282 } catch (IOException e) {
283 logger.debug("failed to close queryService", e);
286 this.gatewayQueryService = null;
287 this.sensorDeviceMap = null;