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.wundergroundupdatereceiver.internal;
15 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.*;
17 import java.time.ZonedDateTime;
18 import java.util.List;
20 import java.util.Objects;
21 import java.util.Optional;
23 import javax.measure.Quantity;
24 import javax.measure.Unit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.library.types.DateTimeType;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.thing.Channel;
34 import org.openhab.core.thing.ChannelGroupUID;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.ManagedThingProvider;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.thing.binding.builder.ChannelBuilder;
42 import org.openhab.core.thing.binding.builder.ThingBuilder;
43 import org.openhab.core.thing.type.ChannelKind;
44 import org.openhab.core.thing.type.ChannelType;
45 import org.openhab.core.thing.type.ChannelTypeRegistry;
46 import org.openhab.core.types.Command;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link WundergroundUpdateReceiverHandler} is responsible for handling commands, which are
52 * sent to one of the channels.
54 * @author Daniel Demus - Initial contribution
57 public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
59 public String getStationId() {
60 return config.stationId;
63 private final Logger logger = LoggerFactory.getLogger(WundergroundUpdateReceiverHandler.class);
64 private final WundergroundUpdateReceiverServlet wundergroundUpdateReceiverServlet;
65 private final WundergroundUpdateReceiverDiscoveryService discoveryService;
66 private final WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider;
67 private final ChannelTypeRegistry channelTypeRegistry;
68 private final ManagedThingProvider managedThingProvider;
70 private final ChannelUID dateutcDatetimeChannel;
71 private final ChannelUID lastReceivedChannel;
72 private final ChannelUID queryStateChannel;
73 private final ChannelUID queryTriggerChannel;
75 private WundergroundUpdateReceiverConfiguration config = new WundergroundUpdateReceiverConfiguration();
77 public WundergroundUpdateReceiverHandler(Thing thing,
78 WundergroundUpdateReceiverServlet wunderGroundUpdateReceiverServlet,
79 WundergroundUpdateReceiverDiscoveryService discoveryService,
80 WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider,
81 ChannelTypeRegistry channelTypeRegistry, ManagedThingProvider managedThingProvider) {
83 this.discoveryService = discoveryService;
84 this.channelTypeProvider = channelTypeProvider;
85 this.channelTypeRegistry = channelTypeRegistry;
86 this.managedThingProvider = managedThingProvider;
88 final ChannelGroupUID metadatGroupUID = new ChannelGroupUID(getThing().getUID(), METADATA_GROUP);
90 this.dateutcDatetimeChannel = new ChannelUID(metadatGroupUID, DATEUTC_DATETIME);
91 this.lastReceivedChannel = new ChannelUID(metadatGroupUID, LAST_RECEIVED);
92 this.queryTriggerChannel = new ChannelUID(metadatGroupUID, LAST_QUERY_TRIGGER);
93 this.queryStateChannel = new ChannelUID(metadatGroupUID, LAST_QUERY_STATE);
95 this.wundergroundUpdateReceiverServlet = Objects.requireNonNull(wunderGroundUpdateReceiverServlet);
99 public void handleCommand(ChannelUID channelUID, Command command) {
100 logger.trace("Ignoring command {}", command);
104 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
105 super.handleConfigurationUpdate(configurationParameters);
106 this.wundergroundUpdateReceiverServlet.handlerConfigUpdated(this);
110 public void initialize() {
111 this.config = getConfigAs(WundergroundUpdateReceiverConfiguration.class);
112 wundergroundUpdateReceiverServlet.addHandler(this);
114 Map<String, String> requestParameters = discoveryService.getUnhandledStationRequest(config.stationId);
115 if (requestParameters != null && thing.getChannels().isEmpty()) {
116 ThingBuilder thingBuilder = editThing();
117 List.of(LAST_RECEIVED, LAST_QUERY_TRIGGER, DATEUTC_DATETIME, LAST_QUERY_STATE)
118 .forEach((String channelId) -> buildChannel(thingBuilder, channelId, ""));
119 requestParameters.forEach((String parameter, String query) -> buildChannel(thingBuilder, parameter, query));
120 updateThing(thingBuilder.build());
123 discoveryService.removeUnhandledStationId(config.stationId);
124 if (wundergroundUpdateReceiverServlet.isActive()) {
125 updateStatus(ThingStatus.ONLINE);
126 logger.debug("Wunderground update receiver listening for updates to station id {}", config.stationId);
129 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
130 wundergroundUpdateReceiverServlet.getErrorDetail());
133 private void migrateChannels() {
134 Optional.ofNullable(getThing().getChannel(queryTriggerChannel)).ifPresent(c -> {
135 if (c.getKind() != ChannelKind.TRIGGER) {
136 ThingBuilder builder = editThing();
137 builder.withoutChannel(c.getUID());
138 buildChannel(builder, LAST_QUERY_TRIGGER, "");
139 updateThing(builder.build());
145 public void dispose() {
146 wundergroundUpdateReceiverServlet.removeHandler(this.getStationId());
148 logger.debug("Wunderground update receiver stopped listening for updates to station id {}", config.stationId);
151 public void updateChannelStates(Map<String, String> requestParameters) {
152 requestParameters.forEach(this::updateChannelState);
153 updateState(lastReceivedChannel, new DateTimeType());
154 updateState(dateutcDatetimeChannel, safeResolvUtcDateTime(requestParameters.getOrDefault(DATEUTC, NOW)));
155 String lastQuery = requestParameters.getOrDefault(LAST_QUERY, "");
156 if (lastQuery.isEmpty()) {
159 updateState(queryStateChannel, StringType.valueOf(lastQuery));
160 triggerChannel(queryTriggerChannel, lastQuery);
163 private void buildChannel(ThingBuilder thingBuilder, String parameter, String value) {
165 WundergroundUpdateReceiverParameterMapping channelTypeMapping = WundergroundUpdateReceiverParameterMapping
166 .getOrCreateMapping(parameter, value, channelTypeProvider);
167 if (channelTypeMapping == null) {
170 ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeMapping.channelTypeId);
171 if (channelType == null) {
174 ChannelBuilder channelBuilder = ChannelBuilder
175 .create(new ChannelUID(thing.getUID(), channelTypeMapping.channelGroup, parameter))
176 .withKind(channelType.getKind()).withAutoUpdatePolicy(channelType.getAutoUpdatePolicy())
177 .withDefaultTags(channelType.getTags()).withType(channelTypeMapping.channelTypeId)
178 .withAcceptedItemType(channelType.getItemType()).withLabel(channelType.getLabel());
179 Optional.ofNullable(channelType.getDescription()).ifPresent(channelBuilder::withDescription);
180 thingBuilder.withChannel(channelBuilder.build());
183 private DateTimeType safeResolvUtcDateTime(String dateUtc) {
184 if (!dateUtc.isEmpty() && !NOW.equals(dateUtc)) {
186 // Supposedly the format is "yyyy-MM-dd hh:mm:ss" from the device
187 return new DateTimeType(ZonedDateTime.parse(dateUtc.replace(" ", "T") + "Z"));
188 } catch (Exception ex) {
189 logger.warn("The device is submitting unparsable datetime values: {}", dateUtc);
192 return new DateTimeType();
195 public void updateChannelState(String channelId, String[] stateParts) {
196 updateChannelState(channelId, String.join("", stateParts));
199 public void updateChannelState(String parameterName, String state) {
200 Optional<Channel> channel = getThing().getChannels().stream()
201 .filter(ch -> parameterName.equals(ch.getUID().getIdWithoutGroup())).findFirst();
202 if (channel.isPresent()) {
203 ChannelUID channelUID = channel.get().getUID();
205 Float numberValue = null;
207 numberValue = Float.valueOf(state);
208 } catch (NumberFormatException ignored) {
211 if (numberValue == null) {
212 updateState(channelUID, StringType.valueOf(state));
216 Unit<? extends Quantity<?>> unit = WundergroundUpdateReceiverParameterMapping.getUnit(parameterName);
218 updateState(channelUID, new QuantityType<>(numberValue, unit));
219 } else if (LOW_BATTERY.equals(parameterName)) {
220 updateState(channelUID, OnOffType.from(state));
222 updateState(channelUID, new DecimalType(numberValue));
224 } else if (this.discoveryService.isDiscovering()
225 && !WundergroundUpdateReceiverParameterMapping.isExcluded(parameterName)
226 && this.managedThingProvider.get(this.thing.getUID()) != null) {
227 ThingBuilder thingBuilder = editThing();
228 buildChannel(thingBuilder, parameterName, state);
229 updateThing(thingBuilder.build());