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.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.binding.BaseThingHandler;
40 import org.openhab.core.thing.binding.builder.ChannelBuilder;
41 import org.openhab.core.thing.binding.builder.ThingBuilder;
42 import org.openhab.core.thing.type.ChannelType;
43 import org.openhab.core.thing.type.ChannelTypeRegistry;
44 import org.openhab.core.types.Command;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * The {@link WundergroundUpdateReceiverHandler} is responsible for handling commands, which are
50 * sent to one of the channels.
52 * @author Daniel Demus - Initial contribution
55 public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
57 public String getStationId() {
58 return config.stationId;
61 private final Logger logger = LoggerFactory.getLogger(WundergroundUpdateReceiverHandler.class);
62 private final WundergroundUpdateReceiverServlet wundergroundUpdateReceiverServlet;
63 private final WundergroundUpdateReceiverDiscoveryService discoveryService;
64 private final WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider;
65 private final ChannelTypeRegistry channelTypeRegistry;
67 private final ChannelUID dateutcDatetimeChannel;
68 private final ChannelUID lastReceivedChannel;
69 private final ChannelUID queryStateChannel;
70 private final ChannelUID queryTriggerChannel;
72 private WundergroundUpdateReceiverConfiguration config = new WundergroundUpdateReceiverConfiguration();
74 public WundergroundUpdateReceiverHandler(Thing thing,
75 WundergroundUpdateReceiverServlet wunderGroundUpdateReceiverServlet,
76 WundergroundUpdateReceiverDiscoveryService discoveryService,
77 WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider,
78 ChannelTypeRegistry channelTypeRegistry) {
80 this.discoveryService = discoveryService;
81 this.channelTypeProvider = channelTypeProvider;
82 this.channelTypeRegistry = channelTypeRegistry;
84 final ChannelGroupUID metadatGroupUID = new ChannelGroupUID(getThing().getUID(), METADATA_GROUP);
86 this.dateutcDatetimeChannel = new ChannelUID(metadatGroupUID, DATEUTC_DATETIME);
87 this.lastReceivedChannel = new ChannelUID(metadatGroupUID, LAST_RECEIVED);
88 this.queryTriggerChannel = new ChannelUID(metadatGroupUID, LAST_QUERY_TRIGGER);
89 this.queryStateChannel = new ChannelUID(metadatGroupUID, LAST_QUERY_STATE);
91 this.wundergroundUpdateReceiverServlet = Objects.requireNonNull(wunderGroundUpdateReceiverServlet);
95 public void handleCommand(ChannelUID channelUID, Command command) {
96 logger.trace("Ignoring command {}", command);
100 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
101 super.handleConfigurationUpdate(configurationParameters);
102 this.wundergroundUpdateReceiverServlet.handlerConfigUpdated(this);
106 public void initialize() {
107 this.config = getConfigAs(WundergroundUpdateReceiverConfiguration.class);
108 wundergroundUpdateReceiverServlet.addHandler(this);
110 Map<String, String[]> requestParameters = discoveryService.getUnhandledStationRequest(config.stationId);
111 if (requestParameters != null && thing.getChannels().isEmpty()) {
112 final String[] noValues = new String[0];
113 ThingBuilder thingBuilder = editThing();
114 List.of(LAST_RECEIVED, LAST_QUERY_TRIGGER, DATEUTC_DATETIME, LAST_QUERY_STATE)
115 .forEach((String channelId) -> buildChannel(thingBuilder, channelId, noValues));
117 .forEach((String parameter, String[] query) -> buildChannel(thingBuilder, parameter, query));
118 updateThing(thingBuilder.build());
120 discoveryService.removeUnhandledStationId(config.stationId);
121 if (wundergroundUpdateReceiverServlet.isActive()) {
122 updateStatus(ThingStatus.ONLINE);
123 logger.debug("Wunderground update receiver listening for updates to station id {}", config.stationId);
126 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
127 wundergroundUpdateReceiverServlet.getErrorDetail());
131 public void dispose() {
132 wundergroundUpdateReceiverServlet.removeHandler(this.getStationId());
134 logger.debug("Wunderground update receiver stopped listening for updates to station id {}", config.stationId);
137 public void updateChannelStates(Map<String, String> requestParameters) {
138 requestParameters.forEach(this::updateChannelState);
139 updateState(lastReceivedChannel, new DateTimeType());
140 updateState(dateutcDatetimeChannel, safeResolvUtcDateTime(requestParameters.getOrDefault(DATEUTC, NOW)));
141 String lastQuery = requestParameters.getOrDefault(LAST_QUERY, "");
142 if (lastQuery.isEmpty()) {
145 updateState(queryStateChannel, StringType.valueOf(lastQuery));
146 triggerChannel(queryTriggerChannel, lastQuery);
149 private void buildChannel(ThingBuilder thingBuilder, String parameter, String... query) {
151 WundergroundUpdateReceiverParameterMapping channelTypeMapping = WundergroundUpdateReceiverParameterMapping
152 .getOrCreateMapping(parameter, String.join("", query), channelTypeProvider);
153 if (channelTypeMapping == null) {
156 ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeMapping.channelTypeId);
157 if (channelType == null) {
160 ChannelBuilder channelBuilder = ChannelBuilder
161 .create(new ChannelUID(thing.getUID(), channelTypeMapping.channelGroup, parameter))
162 .withType(channelTypeMapping.channelTypeId).withAcceptedItemType(channelType.getItemType());
163 thingBuilder.withChannel(channelBuilder.build());
166 private DateTimeType safeResolvUtcDateTime(String dateUtc) {
167 if (!dateUtc.isEmpty() && !NOW.equals(dateUtc)) {
169 // Supposedly the format is "yyyy-MM-dd hh:mm:ss" from the device
170 return new DateTimeType(ZonedDateTime.parse(dateUtc.replace(" ", "T") + "Z"));
171 } catch (Exception ex) {
172 logger.warn("The device is submitting unparsable datetime values: {}", dateUtc);
175 return new DateTimeType();
178 public void updateChannelState(String channelId, String[] stateParts) {
179 updateChannelState(channelId, String.join("", stateParts));
182 public void updateChannelState(String parameterName, String state) {
183 Optional<Channel> channel = getThing().getChannels().stream()
184 .filter(ch -> parameterName.equals(ch.getUID().getIdWithoutGroup())).findFirst();
185 if (channel.isPresent()) {
186 ChannelUID channelUID = channel.get().getUID();
188 Float numberValue = null;
190 numberValue = Float.valueOf(state);
191 } catch (NumberFormatException ignored) {
194 if (numberValue == null) {
195 updateState(channelUID, StringType.valueOf(state));
199 Unit<? extends Quantity<?>> unit = WundergroundUpdateReceiverParameterMapping.getUnit(parameterName);
201 updateState(channelUID, new QuantityType<>(numberValue, unit));
202 } else if (LOW_BATTERY.equals(parameterName)) {
203 updateState(channelUID, OnOffType.from(state));
205 updateState(channelUID, new DecimalType(numberValue));
207 } else if (this.discoveryService.isDiscovering()
208 && !WundergroundUpdateReceiverParameterMapping.isExcluded(parameterName)) {
209 ThingBuilder thingBuilder = editThing();
210 buildChannel(thingBuilder, parameterName, state);
211 updateThing(thingBuilder.build());