]> git.basschouten.com Git - openhab-addons.git/blob
4ff4f1fc5acf0252fe1051b269052a5c132f1ef2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.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;
49
50 /**
51  * The {@link WundergroundUpdateReceiverHandler} is responsible for handling commands, which are
52  * sent to one of the channels.
53  *
54  * @author Daniel Demus - Initial contribution
55  */
56 @NonNullByDefault
57 public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
58
59     public String getStationId() {
60         return config.stationId;
61     }
62
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;
69
70     private final ChannelUID dateutcDatetimeChannel;
71     private final ChannelUID lastReceivedChannel;
72     private final ChannelUID queryStateChannel;
73     private final ChannelUID queryTriggerChannel;
74
75     private WundergroundUpdateReceiverConfiguration config = new WundergroundUpdateReceiverConfiguration();
76
77     public WundergroundUpdateReceiverHandler(Thing thing,
78             WundergroundUpdateReceiverServlet wunderGroundUpdateReceiverServlet,
79             WundergroundUpdateReceiverDiscoveryService discoveryService,
80             WundergroundUpdateReceiverUnknownChannelTypeProvider channelTypeProvider,
81             ChannelTypeRegistry channelTypeRegistry, ManagedThingProvider managedThingProvider) {
82         super(thing);
83         this.discoveryService = discoveryService;
84         this.channelTypeProvider = channelTypeProvider;
85         this.channelTypeRegistry = channelTypeRegistry;
86         this.managedThingProvider = managedThingProvider;
87
88         final ChannelGroupUID metadatGroupUID = new ChannelGroupUID(getThing().getUID(), METADATA_GROUP);
89
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);
94
95         this.wundergroundUpdateReceiverServlet = Objects.requireNonNull(wunderGroundUpdateReceiverServlet);
96     }
97
98     @Override
99     public void handleCommand(ChannelUID channelUID, Command command) {
100         logger.trace("Ignoring command {}", command);
101     }
102
103     @Override
104     public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
105         super.handleConfigurationUpdate(configurationParameters);
106         this.wundergroundUpdateReceiverServlet.handlerConfigUpdated(this);
107     }
108
109     @Override
110     public void initialize() {
111         this.config = getConfigAs(WundergroundUpdateReceiverConfiguration.class);
112         wundergroundUpdateReceiverServlet.addHandler(this);
113         @Nullable
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());
121         }
122         migrateChannels();
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     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());
140             }
141         });
142     }
143
144     @Override
145     public void dispose() {
146         wundergroundUpdateReceiverServlet.removeHandler(this.getStationId());
147         super.dispose();
148         logger.debug("Wunderground update receiver stopped listening for updates to station id {}", config.stationId);
149     }
150
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()) {
157             return;
158         }
159         updateState(queryStateChannel, StringType.valueOf(lastQuery));
160         triggerChannel(queryTriggerChannel, lastQuery);
161     }
162
163     private void buildChannel(ThingBuilder thingBuilder, String parameter, String value) {
164         @Nullable
165         WundergroundUpdateReceiverParameterMapping channelTypeMapping = WundergroundUpdateReceiverParameterMapping
166                 .getOrCreateMapping(parameter, value, channelTypeProvider);
167         if (channelTypeMapping == null) {
168             return;
169         }
170         ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeMapping.channelTypeId);
171         if (channelType == null) {
172             return;
173         }
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());
181     }
182
183     private DateTimeType safeResolvUtcDateTime(String dateUtc) {
184         if (!dateUtc.isEmpty() && !NOW.equals(dateUtc)) {
185             try {
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);
190             }
191         }
192         return new DateTimeType();
193     }
194
195     public void updateChannelState(String channelId, String[] stateParts) {
196         updateChannelState(channelId, String.join("", stateParts));
197     }
198
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();
204             @Nullable
205             Float numberValue = null;
206             try {
207                 numberValue = Float.valueOf(state);
208             } catch (NumberFormatException ignored) {
209             }
210
211             if (numberValue == null) {
212                 updateState(channelUID, StringType.valueOf(state));
213                 return;
214             }
215             @Nullable
216             Unit<? extends Quantity<?>> unit = WundergroundUpdateReceiverParameterMapping.getUnit(parameterName);
217             if (unit != null) {
218                 updateState(channelUID, new QuantityType<>(numberValue, unit));
219             } else if (LOW_BATTERY.equals(parameterName)) {
220                 updateState(channelUID, OnOffType.from(state));
221             } else {
222                 updateState(channelUID, new DecimalType(numberValue));
223             }
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());
230         }
231     }
232 }