]> git.basschouten.com Git - openhab-addons.git/blob
c7b2da83dfd9d80d27a63ce7e49c302b4e24af20
[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.mybmw.internal.handler;
14
15 import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
16
17 import java.util.Optional;
18 import java.util.concurrent.ScheduledExecutorService;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.mybmw.internal.VehicleConfiguration;
25 import org.openhab.binding.mybmw.internal.dto.charge.ChargeSessionsContainer;
26 import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
27 import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
28 import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
29 import org.openhab.binding.mybmw.internal.utils.Constants;
30 import org.openhab.binding.mybmw.internal.utils.Converter;
31 import org.openhab.binding.mybmw.internal.utils.ImageProperties;
32 import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
33 import org.openhab.core.io.net.http.HttpUtil;
34 import org.openhab.core.library.types.RawType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.binding.BridgeHandler;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44
45 import com.google.gson.JsonSyntaxException;
46
47 /**
48  * The {@link VehicleHandler} handles responses from BMW API
49  *
50  * @author Bernd Weymann - Initial contribution
51  * @author Norbert Truchsess - edit & send charge profile
52  */
53 @NonNullByDefault
54 public class VehicleHandler extends VehicleChannelHandler {
55     private Optional<MyBMWProxy> proxy = Optional.empty();
56     private Optional<RemoteServiceHandler> remote = Optional.empty();
57     public Optional<VehicleConfiguration> configuration = Optional.empty();
58     private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
59     private Optional<ScheduledFuture<?>> editTimeout = Optional.empty();
60
61     private ImageProperties imageProperties = new ImageProperties();
62     VehicleStatusCallback vehicleStatusCallback = new VehicleStatusCallback();
63     ChargeStatisticsCallback chargeStatisticsCallback = new ChargeStatisticsCallback();
64     ChargeSessionsCallback chargeSessionCallback = new ChargeSessionsCallback();
65     ByteResponseCallback imageCallback = new ImageCallback();
66
67     public VehicleHandler(Thing thing, MyBMWCommandOptionProvider cop, String driveTrain) {
68         super(thing, cop, driveTrain);
69     }
70
71     @Override
72     public void handleCommand(ChannelUID channelUID, Command command) {
73         String group = channelUID.getGroupId();
74
75         // Refresh of Channels with cached values
76         if (command instanceof RefreshType) {
77             if (CHANNEL_GROUP_STATUS.equals(group)) {
78                 vehicleStatusCache.ifPresent(vehicleStatus -> vehicleStatusCallback.onResponse(vehicleStatus));
79             } else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
80                 imageCache.ifPresent(image -> imageCallback.onResponse(image));
81             }
82             // Check for Channel Group and corresponding Actions
83         } else if (CHANNEL_GROUP_REMOTE.equals(group)) {
84             // Executing Remote Services
85             if (command instanceof StringType) {
86                 String serviceCommand = ((StringType) command).toFullString();
87                 remote.ifPresent(remot -> {
88                     switch (serviceCommand) {
89                         case REMOTE_SERVICE_LIGHT_FLASH:
90                         case REMOTE_SERVICE_DOOR_LOCK:
91                         case REMOTE_SERVICE_DOOR_UNLOCK:
92                         case REMOTE_SERVICE_HORN:
93                         case REMOTE_SERVICE_VEHICLE_FINDER:
94                             RemoteServiceUtils.getRemoteService(serviceCommand)
95                                     .ifPresentOrElse(service -> remot.execute(service), () -> {
96                                         logger.debug("Remote service execution {} unknown", serviceCommand);
97                                     });
98                             break;
99                         case REMOTE_SERVICE_AIR_CONDITIONING_START:
100                             RemoteServiceUtils.getRemoteService(serviceCommand)
101                                     .ifPresentOrElse(service -> remot.execute(service), () -> {
102                                         logger.debug("Remote service execution {} unknown", serviceCommand);
103                                     });
104                             break;
105                         case REMOTE_SERVICE_AIR_CONDITIONING_STOP:
106                             RemoteServiceUtils.getRemoteService(serviceCommand)
107                                     .ifPresentOrElse(service -> remot.execute(service), () -> {
108                                         logger.debug("Remote service execution {} unknown", serviceCommand);
109                                     });
110                             break;
111                         default:
112                             logger.debug("Remote service execution {} unknown", serviceCommand);
113                             break;
114                     }
115                 });
116             }
117         } else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
118             // Image Change
119             configuration.ifPresent(config -> {
120                 if (command instanceof StringType) {
121                     if (channelUID.getIdWithoutGroup().equals(IMAGE_VIEWPORT)) {
122                         String newViewport = command.toString();
123                         synchronized (imageProperties) {
124                             if (!imageProperties.viewport.equals(newViewport)) {
125                                 imageProperties = new ImageProperties(newViewport);
126                                 imageCache = Optional.empty();
127                                 proxy.ifPresent(prox -> prox.requestImage(config, imageProperties, imageCallback));
128                             }
129                         }
130                         updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(newViewport));
131                     }
132                 }
133             });
134         } else if (CHANNEL_GROUP_SERVICE.equals(group)) {
135             if (command instanceof StringType) {
136                 int index = Converter.getIndex(command.toFullString());
137                 if (index != -1) {
138                     selectService(index);
139                 } else {
140                     logger.debug("Cannot select Service index {}", command.toFullString());
141                 }
142             }
143         } else if (CHANNEL_GROUP_CHECK_CONTROL.equals(group)) {
144             if (command instanceof StringType) {
145                 int index = Converter.getIndex(command.toFullString());
146                 if (index != -1) {
147                     selectCheckControl(index);
148                 } else {
149                     logger.debug("Cannot select CheckControl index {}", command.toFullString());
150                 }
151             }
152         } else if (CHANNEL_GROUP_CHARGE_SESSION.equals(group)) {
153             if (command instanceof StringType) {
154                 int index = Converter.getIndex(command.toFullString());
155                 if (index != -1) {
156                     selectSession(index);
157                 } else {
158                     logger.debug("Cannot select Session index {}", command.toFullString());
159                 }
160             }
161         }
162     }
163
164     @Override
165     public void initialize() {
166         updateStatus(ThingStatus.UNKNOWN);
167         final VehicleConfiguration config = getConfigAs(VehicleConfiguration.class);
168         configuration = Optional.of(config);
169         Bridge bridge = getBridge();
170         if (bridge != null) {
171             BridgeHandler handler = bridge.getHandler();
172             if (handler != null) {
173                 proxy = ((MyBMWBridgeHandler) handler).getProxy();
174                 remote = proxy.map(prox -> prox.getRemoteServiceHandler(this));
175             } else {
176                 logger.debug("Bridge Handler null");
177             }
178         } else {
179             logger.debug("Bridge null");
180         }
181
182         imageProperties = new ImageProperties();
183         updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(imageProperties.viewport));
184
185         // start update schedule
186         startSchedule(config.refreshInterval);
187     }
188
189     private void startSchedule(int interval) {
190         refreshJob.ifPresentOrElse(job -> {
191             if (job.isCancelled()) {
192                 refreshJob = Optional
193                         .of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
194             } // else - scheduler is already running!
195         }, () -> {
196             refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
197         });
198     }
199
200     @Override
201     public void dispose() {
202         refreshJob.ifPresent(job -> job.cancel(true));
203         editTimeout.ifPresent(job -> job.cancel(true));
204         remote.ifPresent(RemoteServiceHandler::cancel);
205     }
206
207     public void getData() {
208         proxy.ifPresentOrElse(prox -> {
209             configuration.ifPresentOrElse(config -> {
210                 prox.requestVehicles(config.vehicleBrand, vehicleStatusCallback);
211                 if (isElectric) {
212                     prox.requestChargeStatistics(config, chargeStatisticsCallback);
213                     prox.requestChargeSessions(config, chargeSessionCallback);
214                 }
215                 if (!imageCache.isPresent() && !imageProperties.failLimitReached()) {
216                     prox.requestImage(config, imageProperties, imageCallback);
217                 }
218             }, () -> {
219                 logger.warn("MyBMW Vehicle Configuration isn't present");
220             });
221         }, () -> {
222             logger.warn("MyBMWProxy isn't present");
223         });
224     }
225
226     public void updateRemoteExecutionStatus(@Nullable String service, String status) {
227         updateChannel(CHANNEL_GROUP_REMOTE, REMOTE_STATE,
228                 StringType.valueOf((service == null ? "-" : service) + Constants.SPACE + status.toLowerCase()));
229     }
230
231     public Optional<VehicleConfiguration> getConfiguration() {
232         return configuration;
233     }
234
235     public ScheduledExecutorService getScheduler() {
236         return scheduler;
237     }
238
239     public class ImageCallback implements ByteResponseCallback {
240         @Override
241         public void onResponse(byte[] content) {
242             if (content.length > 0) {
243                 imageCache = Optional.of(content);
244                 String contentType = HttpUtil.guessContentTypeFromData(content);
245                 updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_FORMAT, new RawType(content, contentType));
246             } else {
247                 synchronized (imageProperties) {
248                     imageProperties.failed();
249                 }
250             }
251         }
252
253         /**
254          * Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
255          */
256         @Override
257         public void onError(NetworkError error) {
258             logger.debug("{}", error.toString());
259             synchronized (imageProperties) {
260                 imageProperties.failed();
261             }
262         }
263     }
264
265     /**
266      * The VehicleStatus is supported by all Vehicle Types so it's used to reflect the Thing Status
267      */
268     public class VehicleStatusCallback implements StringResponseCallback {
269         @Override
270         public void onResponse(@Nullable String content) {
271             if (content != null) {
272                 if (getConfiguration().isPresent()) {
273                     Vehicle v = Converter.getVehicle(configuration.get().vin, content);
274                     if (v.valid) {
275                         vehicleStatusCache = Optional.of(content);
276                         updateStatus(ThingStatus.ONLINE);
277                         updateChannel(CHANNEL_GROUP_STATUS, RAW,
278                                 StringType.valueOf(Converter.getRawVehicleContent(configuration.get().vin, content)));
279                         updateVehicle(v);
280                         if (isElectric) {
281                             updateChargeProfile(v.status.chargingProfile);
282                         }
283                     } else {
284                         logger.debug("Vehicle {} not valid", configuration.get().vin);
285                     }
286                 } else {
287                     logger.debug("configuration not present");
288                 }
289             } else {
290                 updateChannel(CHANNEL_GROUP_STATUS, RAW, StringType.valueOf(Constants.EMPTY_JSON));
291                 logger.debug("Content not valid");
292             }
293         }
294
295         @Override
296         public void onError(NetworkError error) {
297             logger.debug("{}", error.toString());
298             vehicleStatusCache = Optional.of(Converter.getGson().toJson(error));
299             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
300         }
301     }
302
303     public class ChargeStatisticsCallback implements StringResponseCallback {
304         @Override
305         public void onResponse(@Nullable String content) {
306             if (content != null) {
307                 try {
308                     ChargeStatisticsContainer csc = Converter.getGson().fromJson(content,
309                             ChargeStatisticsContainer.class);
310                     if (csc != null) {
311                         updateChargeStatistics(csc);
312                     }
313                 } catch (JsonSyntaxException jse) {
314                     logger.warn("{}", jse.getLocalizedMessage());
315                 }
316             } else {
317                 logger.debug("Content not valid");
318             }
319         }
320
321         @Override
322         public void onError(NetworkError error) {
323             logger.debug("{}", error.toString());
324         }
325     }
326
327     public class ChargeSessionsCallback implements StringResponseCallback {
328         @Override
329         public void onResponse(@Nullable String content) {
330             if (content != null) {
331                 try {
332                     ChargeSessionsContainer csc = Converter.getGson().fromJson(content, ChargeSessionsContainer.class);
333                     if (csc != null) {
334                         if (csc.chargingSessions != null) {
335                             updateSessions(csc.chargingSessions.sessions);
336                         }
337                     }
338                 } catch (JsonSyntaxException jse) {
339                     logger.warn("{}", jse.getLocalizedMessage());
340                 }
341             } else {
342                 logger.debug("Content not valid");
343             }
344         }
345
346         @Override
347         public void onError(NetworkError error) {
348             logger.debug("{}", error.toString());
349         }
350     }
351 }