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.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.ChannelType;
44 import org.openhab.core.thing.type.ChannelTypeRegistry;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link WundergroundUpdateReceiverHandler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author Daniel Demus - Initial contribution
56 public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
58 public String getStationId() {
59 return config.stationId;
62 private final Logger logger = LoggerFactory.getLogger(WundergroundUpdateReceiverHandler.class);
63 private final WundergroundUpdateReceiverServlet wundergroundUpdateReceiverServlet;
64 private final WundergroundUpdateReceiverDiscoveryService discoveryService;
65 private final WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider;
66 private final ChannelTypeRegistry channelTypeRegistry;
67 private final ManagedThingProvider managedThingProvider;
69 private final ChannelUID dateutcDatetimeChannel;
70 private final ChannelUID lastReceivedChannel;
71 private final ChannelUID queryStateChannel;
72 private final ChannelUID queryTriggerChannel;
74 private WundergroundUpdateReceiverConfiguration config = new WundergroundUpdateReceiverConfiguration();
76 public WundergroundUpdateReceiverHandler(Thing thing,
77 WundergroundUpdateReceiverServlet wunderGroundUpdateReceiverServlet,
78 WundergroundUpdateReceiverDiscoveryService discoveryService,
79 WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider,
80 ChannelTypeRegistry channelTypeRegistry, ManagedThingProvider managedThingProvider) {
82 this.discoveryService = discoveryService;
83 this.channelTypeProvider = channelTypeProvider;
84 this.channelTypeRegistry = channelTypeRegistry;
85 this.managedThingProvider = managedThingProvider;
87 final ChannelGroupUID metadatGroupUID = new ChannelGroupUID(getThing().getUID(), METADATA_GROUP);
89 this.dateutcDatetimeChannel = new ChannelUID(metadatGroupUID, DATEUTC_DATETIME);
90 this.lastReceivedChannel = new ChannelUID(metadatGroupUID, LAST_RECEIVED);
91 this.queryTriggerChannel = new ChannelUID(metadatGroupUID, LAST_QUERY_TRIGGER);
92 this.queryStateChannel = new ChannelUID(metadatGroupUID, LAST_QUERY_STATE);
94 this.wundergroundUpdateReceiverServlet = Objects.requireNonNull(wunderGroundUpdateReceiverServlet);
98 public void handleCommand(ChannelUID channelUID, Command command) {
99 logger.trace("Ignoring command {}", command);
103 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
104 super.handleConfigurationUpdate(configurationParameters);
105 this.wundergroundUpdateReceiverServlet.handlerConfigUpdated(this);
109 public void initialize() {
110 this.config = getConfigAs(WundergroundUpdateReceiverConfiguration.class);
111 wundergroundUpdateReceiverServlet.addHandler(this);
113 Map<String, String[]> requestParameters = discoveryService.getUnhandledStationRequest(config.stationId);
114 if (requestParameters != null && thing.getChannels().isEmpty()) {
115 final String[] noValues = new String[0];
116 ThingBuilder thingBuilder = editThing();
117 List.of(LAST_RECEIVED, LAST_QUERY_TRIGGER, DATEUTC_DATETIME, LAST_QUERY_STATE)
118 .forEach((String channelId) -> buildChannel(thingBuilder, channelId, noValues));
120 .forEach((String parameter, String[] query) -> buildChannel(thingBuilder, parameter, query));
121 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());
134 public void dispose() {
135 wundergroundUpdateReceiverServlet.removeHandler(this.getStationId());
137 logger.debug("Wunderground update receiver stopped listening for updates to station id {}", config.stationId);
140 public void updateChannelStates(Map<String, String> requestParameters) {
141 requestParameters.forEach(this::updateChannelState);
142 updateState(lastReceivedChannel, new DateTimeType());
143 updateState(dateutcDatetimeChannel, safeResolvUtcDateTime(requestParameters.getOrDefault(DATEUTC, NOW)));
144 String lastQuery = requestParameters.getOrDefault(LAST_QUERY, "");
145 if (lastQuery.isEmpty()) {
148 updateState(queryStateChannel, StringType.valueOf(lastQuery));
149 triggerChannel(queryTriggerChannel, lastQuery);
152 private void buildChannel(ThingBuilder thingBuilder, String parameter, String... query) {
154 WundergroundUpdateReceiverParameterMapping channelTypeMapping = WundergroundUpdateReceiverParameterMapping
155 .getOrCreateMapping(parameter, String.join("", query), channelTypeProvider);
156 if (channelTypeMapping == null) {
159 ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeMapping.channelTypeId);
160 if (channelType == null) {
163 ChannelBuilder channelBuilder = ChannelBuilder
164 .create(new ChannelUID(thing.getUID(), channelTypeMapping.channelGroup, parameter))
165 .withType(channelTypeMapping.channelTypeId).withAcceptedItemType(channelType.getItemType());
166 thingBuilder.withChannel(channelBuilder.build());
169 private DateTimeType safeResolvUtcDateTime(String dateUtc) {
170 if (!dateUtc.isEmpty() && !NOW.equals(dateUtc)) {
172 // Supposedly the format is "yyyy-MM-dd hh:mm:ss" from the device
173 return new DateTimeType(ZonedDateTime.parse(dateUtc.replace(" ", "T") + "Z"));
174 } catch (Exception ex) {
175 logger.warn("The device is submitting unparsable datetime values: {}", dateUtc);
178 return new DateTimeType();
181 public void updateChannelState(String channelId, String[] stateParts) {
182 updateChannelState(channelId, String.join("", stateParts));
185 public void updateChannelState(String parameterName, String state) {
186 Optional<Channel> channel = getThing().getChannels().stream()
187 .filter(ch -> parameterName.equals(ch.getUID().getIdWithoutGroup())).findFirst();
188 if (channel.isPresent()) {
189 ChannelUID channelUID = channel.get().getUID();
191 Float numberValue = null;
193 numberValue = Float.valueOf(state);
194 } catch (NumberFormatException ignored) {
197 if (numberValue == null) {
198 updateState(channelUID, StringType.valueOf(state));
202 Unit<? extends Quantity<?>> unit = WundergroundUpdateReceiverParameterMapping.getUnit(parameterName);
204 updateState(channelUID, new QuantityType<>(numberValue, unit));
205 } else if (LOW_BATTERY.equals(parameterName)) {
206 updateState(channelUID, OnOffType.from(state));
208 updateState(channelUID, new DecimalType(numberValue));
210 } else if (this.discoveryService.isDiscovering()
211 && !WundergroundUpdateReceiverParameterMapping.isExcluded(parameterName)
212 && this.managedThingProvider.get(this.thing.getUID()) != null) {
213 ThingBuilder thingBuilder = editThing();
214 buildChannel(thingBuilder, parameterName, state);
215 updateThing(thingBuilder.build());