]> git.basschouten.com Git - openhab-addons.git/blob
09fe7e0baa78811c0bc838b188898d47a654b449
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.wundergroundupdatereceiver.internal;
14
15 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.*;
16
17 import java.time.ZonedDateTime;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Objects;
21 import java.util.Optional;
22
23 import javax.measure.Quantity;
24 import javax.measure.Unit;
25
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;
48
49 /**
50  * The {@link WundergroundUpdateReceiverHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author Daniel Demus - Initial contribution
54  */
55 @NonNullByDefault
56 public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
57
58     public String getStationId() {
59         return config.stationId;
60     }
61
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;
68
69     private final ChannelUID dateutcDatetimeChannel;
70     private final ChannelUID lastReceivedChannel;
71     private final ChannelUID queryStateChannel;
72     private final ChannelUID queryTriggerChannel;
73
74     private WundergroundUpdateReceiverConfiguration config = new WundergroundUpdateReceiverConfiguration();
75
76     public WundergroundUpdateReceiverHandler(Thing thing,
77             WundergroundUpdateReceiverServlet wunderGroundUpdateReceiverServlet,
78             WundergroundUpdateReceiverDiscoveryService discoveryService,
79             WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider,
80             ChannelTypeRegistry channelTypeRegistry, ManagedThingProvider managedThingProvider) {
81         super(thing);
82         this.discoveryService = discoveryService;
83         this.channelTypeProvider = channelTypeProvider;
84         this.channelTypeRegistry = channelTypeRegistry;
85         this.managedThingProvider = managedThingProvider;
86
87         final ChannelGroupUID metadatGroupUID = new ChannelGroupUID(getThing().getUID(), METADATA_GROUP);
88
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);
93
94         this.wundergroundUpdateReceiverServlet = Objects.requireNonNull(wunderGroundUpdateReceiverServlet);
95     }
96
97     @Override
98     public void handleCommand(ChannelUID channelUID, Command command) {
99         logger.trace("Ignoring command {}", command);
100     }
101
102     @Override
103     public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
104         super.handleConfigurationUpdate(configurationParameters);
105         this.wundergroundUpdateReceiverServlet.handlerConfigUpdated(this);
106     }
107
108     @Override
109     public void initialize() {
110         this.config = getConfigAs(WundergroundUpdateReceiverConfiguration.class);
111         wundergroundUpdateReceiverServlet.addHandler(this);
112         @Nullable
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));
119             requestParameters
120                     .forEach((String parameter, String[] query) -> buildChannel(thingBuilder, parameter, query));
121             updateThing(thingBuilder.build());
122         }
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);
127             return;
128         }
129         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
130                 wundergroundUpdateReceiverServlet.getErrorDetail());
131     }
132
133     @Override
134     public void dispose() {
135         wundergroundUpdateReceiverServlet.removeHandler(this.getStationId());
136         super.dispose();
137         logger.debug("Wunderground update receiver stopped listening for updates to station id {}", config.stationId);
138     }
139
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()) {
146             return;
147         }
148         updateState(queryStateChannel, StringType.valueOf(lastQuery));
149         triggerChannel(queryTriggerChannel, lastQuery);
150     }
151
152     private void buildChannel(ThingBuilder thingBuilder, String parameter, String... query) {
153         @Nullable
154         WundergroundUpdateReceiverParameterMapping channelTypeMapping = WundergroundUpdateReceiverParameterMapping
155                 .getOrCreateMapping(parameter, String.join("", query), channelTypeProvider);
156         if (channelTypeMapping == null) {
157             return;
158         }
159         ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeMapping.channelTypeId);
160         if (channelType == null) {
161             return;
162         }
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());
167     }
168
169     private DateTimeType safeResolvUtcDateTime(String dateUtc) {
170         if (!dateUtc.isEmpty() && !NOW.equals(dateUtc)) {
171             try {
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);
176             }
177         }
178         return new DateTimeType();
179     }
180
181     public void updateChannelState(String channelId, String[] stateParts) {
182         updateChannelState(channelId, String.join("", stateParts));
183     }
184
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();
190             @Nullable
191             Float numberValue = null;
192             try {
193                 numberValue = Float.valueOf(state);
194             } catch (NumberFormatException ignored) {
195             }
196
197             if (numberValue == null) {
198                 updateState(channelUID, StringType.valueOf(state));
199                 return;
200             }
201             @Nullable
202             Unit<? extends Quantity<?>> unit = WundergroundUpdateReceiverParameterMapping.getUnit(parameterName);
203             if (unit != null) {
204                 updateState(channelUID, new QuantityType<>(numberValue, unit));
205             } else if (LOW_BATTERY.equals(parameterName)) {
206                 updateState(channelUID, OnOffType.from(state));
207             } else {
208                 updateState(channelUID, new DecimalType(numberValue));
209             }
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());
216         }
217     }
218 }