]> git.basschouten.com Git - openhab-addons.git/blob
66a16830606e5e729bc528faafb09aed583a854f
[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.mybmw.internal.discovery;
14
15 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUPPORTED_THING_SET;
16
17 import java.lang.reflect.Field;
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Optional;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.mybmw.internal.MyBMWConstants;
27 import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
28 import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
29 import org.openhab.binding.mybmw.internal.handler.RemoteServiceHandler;
30 import org.openhab.binding.mybmw.internal.utils.Constants;
31 import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
32 import org.openhab.core.config.core.Configuration;
33 import org.openhab.core.config.discovery.AbstractDiscoveryService;
34 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
35 import org.openhab.core.config.discovery.DiscoveryService;
36 import org.openhab.core.thing.ThingUID;
37 import org.openhab.core.thing.binding.ThingHandler;
38 import org.openhab.core.thing.binding.ThingHandlerService;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The {@link VehicleDiscovery} requests data from BMW API and is identifying the Vehicles after response
44  *
45  * @author Bernd Weymann - Initial contribution
46  */
47 @NonNullByDefault
48 public class VehicleDiscovery extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
49     private static final Logger LOGGER = LoggerFactory.getLogger(VehicleDiscovery.class);
50     public static final String SUPPORTED_SUFFIX = "Supported";
51     public static final String ENABLE_SUFFIX = "Enable";
52     public static final String ENABLED_SUFFIX = "Enabled";
53     private static final int DISCOVERY_TIMEOUT = 10;
54     private Optional<MyBMWBridgeHandler> bridgeHandler = Optional.empty();
55
56     public VehicleDiscovery() {
57         super(SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
58     }
59
60     public void onResponse(List<Vehicle> vehicleList) {
61         bridgeHandler.ifPresent(bridge -> {
62             final ThingUID bridgeUID = bridge.getThing().getUID();
63             vehicleList.forEach(vehicle -> {
64                 // the DriveTrain field in the delivered json is defining the Vehicle Type
65                 String vehicleType = VehicleStatusUtils.vehicleType(vehicle.driveTrain, vehicle.model).toString();
66                 SUPPORTED_THING_SET.forEach(entry -> {
67                     if (entry.getId().equals(vehicleType)) {
68                         ThingUID uid = new ThingUID(entry, vehicle.vin, bridgeUID.getId());
69                         Map<String, String> properties = new HashMap<>();
70                         // Vehicle Properties
71                         properties.put("vehicleModel", vehicle.model);
72                         properties.put("vehicleDriveTrain", vehicle.driveTrain);
73                         properties.put("vehicleConstructionYear", Integer.toString(vehicle.year));
74                         properties.put("vehicleBodytype", vehicle.bodyType);
75
76                         properties.put("servicesSupported", getServices(vehicle, SUPPORTED_SUFFIX, true));
77                         properties.put("servicesUnsupported", getServices(vehicle, SUPPORTED_SUFFIX, false));
78                         String servicesEnabled = getServices(vehicle, ENABLED_SUFFIX, true) + Constants.SEMICOLON
79                                 + getServices(vehicle, ENABLE_SUFFIX, true);
80                         properties.put("servicesEnabled", servicesEnabled.trim());
81                         String servicesDisabled = getServices(vehicle, ENABLED_SUFFIX, false) + Constants.SEMICOLON
82                                 + getServices(vehicle, ENABLE_SUFFIX, false);
83                         properties.put("servicesDisabled", servicesDisabled.trim());
84
85                         // For RemoteServices we need to do it step-by-step
86                         StringBuffer remoteServicesEnabled = new StringBuffer();
87                         StringBuffer remoteServicesDisabled = new StringBuffer();
88                         if (vehicle.capabilities.lock.isEnabled) {
89                             remoteServicesEnabled.append(
90                                     RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
91                         } else {
92                             remoteServicesDisabled.append(
93                                     RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
94                         }
95                         if (vehicle.capabilities.unlock.isEnabled) {
96                             remoteServicesEnabled.append(
97                                     RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
98                         } else {
99                             remoteServicesDisabled.append(
100                                     RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
101                         }
102                         if (vehicle.capabilities.lights.isEnabled) {
103                             remoteServicesEnabled.append(
104                                     RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
105                         } else {
106                             remoteServicesDisabled.append(
107                                     RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
108                         }
109                         if (vehicle.capabilities.horn.isEnabled) {
110                             remoteServicesEnabled.append(
111                                     RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
112                         } else {
113                             remoteServicesDisabled.append(
114                                     RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
115                         }
116                         if (vehicle.capabilities.vehicleFinder.isEnabled) {
117                             remoteServicesEnabled.append(
118                                     RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
119                         } else {
120                             remoteServicesDisabled.append(
121                                     RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
122                         }
123                         if (vehicle.capabilities.climateNow.isEnabled) {
124                             remoteServicesEnabled.append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
125                                     + Constants.SEMICOLON);
126                         } else {
127                             remoteServicesDisabled
128                                     .append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
129                                             + Constants.SEMICOLON);
130                         }
131                         properties.put("remoteServicesEnabled", remoteServicesEnabled.toString().trim());
132                         properties.put("remoteServicesDisabled", remoteServicesDisabled.toString().trim());
133
134                         // Update Properties for already created Things
135                         bridge.getThing().getThings().forEach(vehicleThing -> {
136                             Configuration c = vehicleThing.getConfiguration();
137                             if (c.containsKey(MyBMWConstants.VIN)) {
138                                 String thingVIN = c.get(MyBMWConstants.VIN).toString();
139                                 if (vehicle.vin.equals(thingVIN)) {
140                                     vehicleThing.setProperties(properties);
141                                 }
142                             }
143                         });
144
145                         // Properties needed for functional Thing
146                         properties.put(MyBMWConstants.VIN, vehicle.vin);
147                         properties.put("vehicleBrand", vehicle.brand);
148                         properties.put("refreshInterval",
149                                 Integer.toString(MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
150
151                         String vehicleLabel = vehicle.brand + " " + vehicle.model;
152                         Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
153                         thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
154                                 .withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
155                                 .withProperties(convertedProperties).build());
156                     }
157                 });
158             });
159         });
160     }
161
162     @Override
163     public void setThingHandler(ThingHandler handler) {
164         if (handler instanceof MyBMWBridgeHandler bmwBridgeHandler) {
165             bridgeHandler = Optional.of(bmwBridgeHandler);
166             bridgeHandler.get().setDiscoveryService(this);
167         }
168     }
169
170     @Override
171     public @Nullable ThingHandler getThingHandler() {
172         return bridgeHandler.orElse(null);
173     }
174
175     @Override
176     protected void startScan() {
177         bridgeHandler.ifPresent(MyBMWBridgeHandler::requestVehicles);
178     }
179
180     @Override
181     public void deactivate() {
182         super.deactivate();
183     }
184
185     public static String getServices(Vehicle vehicle, String suffix, boolean enabled) {
186         StringBuffer sb = new StringBuffer();
187         List<String> l = getObject(vehicle.capabilities, enabled);
188         for (String capEntry : l) {
189             // remove "is" prefix
190             String cut = capEntry.substring(2);
191             if (cut.endsWith(suffix)) {
192                 if (sb.length() > 0) {
193                     sb.append(Constants.SEMICOLON);
194                 }
195                 sb.append(cut.substring(0, cut.length() - suffix.length()));
196             }
197         }
198         return sb.toString();
199     }
200
201     /**
202      * Get all field names from a DTO with a specific value
203      * Used to get e.g. all services which are "ACTIVATED"
204      *
205      * @param dto Object
206      * @param compare String which needs to map with the value
207      * @return String with all field names matching this value separated with Spaces
208      */
209     public static List<String> getObject(Object dto, Object compare) {
210         List<String> l = new ArrayList<String>();
211         for (Field field : dto.getClass().getDeclaredFields()) {
212             try {
213                 Object value = field.get(dto);
214                 if (compare.equals(value)) {
215                     l.add(field.getName());
216                 }
217             } catch (IllegalArgumentException | IllegalAccessException e) {
218                 LOGGER.debug("Field {} not found {}", compare, e.getMessage());
219             }
220         }
221         return l;
222     }
223 }