2 * Copyright (c) 2010-2023 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.Collection;
21 import java.util.HashMap;
22 import java.util.List;
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;
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;
67 * The {@link FineOffsetGatewayHandler} is responsible for handling commands, which are
68 * sent to one of the channels.
70 * @author Andreas Berger - Initial contribution
73 public class FineOffsetGatewayHandler extends BaseBridgeHandler {
75 private static final String PROPERTY_FREQUENCY = "frequency";
77 private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayHandler.class);
78 private final Bundle bundle;
79 private final ConversionContext conversionContext;
81 private @Nullable GatewayQueryService gatewayQueryService;
83 private final FineOffsetGatewayDiscoveryService gatewayDiscoveryService;
84 private final ChannelTypeRegistry channelTypeRegistry;
85 private final TranslationProvider translationProvider;
86 private final LocaleProvider localeProvider;
88 private final ThingUID bridgeUID;
90 private @Nullable Map<SensorGatewayBinding, SensorDevice> sensorDeviceMap;
91 private @Nullable ScheduledFuture<?> pollingJob;
92 private @Nullable ScheduledFuture<?> discoverJob;
93 private boolean disposed;
95 public FineOffsetGatewayHandler(Bridge bridge, FineOffsetGatewayDiscoveryService gatewayDiscoveryService,
96 ChannelTypeRegistry channelTypeRegistry, TranslationProvider translationProvider,
97 LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider) {
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());
109 public void handleCommand(ChannelUID channelUID, Command command) {
113 public void initialize() {
114 FineOffsetGatewayConfiguration config = getConfigAs(FineOffsetGatewayConfiguration.class);
115 gatewayQueryService = config.protocol.getGatewayQueryService(config, this::updateStatus, conversionContext);
117 updateStatus(ThingStatus.UNKNOWN);
118 fetchAndUpdateSensors();
125 private void fetchAndUpdateSensors() {
127 Map<SensorGatewayBinding, SensorDevice> deviceMap = query(GatewayQueryService::getRegisteredSensors);
128 sensorDeviceMap = deviceMap;
130 if (deviceMap != null) {
131 gatewayDiscoveryService.addSensors(bridgeUID, deviceMap.values());
135 private void updateSensors() {
136 ((Bridge) thing).getThings().forEach(this::updateSensorThing);
139 private void updateSensorThing(Thing thing) {
140 Map<SensorGatewayBinding, SensorDevice> sensorMap = sensorDeviceMap;
141 if (!THING_TYPE_SENSOR.equals(thing.getThingTypeUID()) || sensorMap == null) {
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)));
151 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
152 updateSensorThing(childThing);
155 private void updateLiveData() {
159 Collection<MeasuredValue> data = query(GatewayQueryService::getMeasuredValues);
161 getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.UNDEF));
165 List<Channel> channels = new ArrayList<>();
166 for (MeasuredValue measuredValue : data) {
168 Channel channel = thing.getChannel(measuredValue.getChannelId());
169 if (channel == null) {
170 channel = createChannel(measuredValue);
171 if (channel != null) {
172 channels.add(channel);
175 State state = measuredValue.getState();
176 updateState(channel.getUID(), state);
179 if (!channels.isEmpty()) {
180 updateBridgeThing(bridgeBuilder -> bridgeBuilder.withChannels(channels));
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());
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());
196 builder.withLabel(label);
198 String description = translationProvider.getText(bundle, channelKey + ".description", null,
199 localeProvider.getLocale(), measuredValue.getChannelNumber());
200 if (description != null) {
201 builder.withDescription(description);
204 ChannelType type = channelTypeRegistry.getChannelType(channelTypeId);
206 builder.withAcceptedItemType(type.getItemType());
208 return builder.build();
211 private void updateBridgeInfo() {
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]);
223 SystemInfo systemInfo = query(GatewayQueryService::fetchSystemInfo);
224 if (systemInfo != null && systemInfo.getFrequency() != null) {
225 properties.put(PROPERTY_FREQUENCY, systemInfo.getFrequency() + " MHz");
227 if (!thing.getProperties().equals(properties)) {
228 updateBridgeThing(bridgeBuilder -> bridgeBuilder.withProperties(properties));
232 private void updateBridgeThing(Consumer<BridgeBuilder> customizer) {
233 BridgeBuilder bridge = editThing();
234 customizer.accept(bridge);
235 updateThing(bridge.build());
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,
247 private void stopDiscoverJob() {
248 ScheduledFuture<?> job = this.discoverJob;
252 this.discoverJob = null;
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);
263 private void stopPollingJob() {
264 ScheduledFuture<?> job = this.pollingJob;
268 this.pollingJob = null;
271 private <T> @Nullable T query(Function<GatewayQueryService, T> delegate) {
273 GatewayQueryService queryService = this.gatewayQueryService;
274 if (queryService == null) {
277 return delegate.apply(queryService);
281 public void dispose() {
284 GatewayQueryService queryService = this.gatewayQueryService;
285 if (queryService != null) {
287 queryService.close();
288 } catch (IOException e) {
289 logger.debug("failed to close queryService", e);
292 this.gatewayQueryService = null;
293 this.sensorDeviceMap = null;