* Available for all vehicles with built-in GPS sensor. Function can be enabled/disabled in the head unit
* Read-only values
-| Channel Label | Channel ID | Type |
-|-----------------|---------------------|--------------|
-| GPS Coordinates | gps | Location |
-| Heading | heading | Number:Angle |
-| Address | address | String |
-
+| Channel Label | Channel ID | Type |
+|---------------------|---------------------|---------------|
+| GPS Coordinates | gps | Location |
+| Heading | heading | Number:Angle |
+| Address | address | String |
+| Distance from Home | home-distance | Number:Length |
#### Remote Services
public static final String GPS = "gps";
public static final String HEADING = "heading";
public static final String ADDRESS = "address";
+ public static final String HOME_DISTANCE = "home-distance";
// Status
public static final String DOORS = "doors";
import org.openhab.binding.mybmw.internal.handler.MyBMWCommandOptionProvider;
import org.openhab.binding.mybmw.internal.handler.VehicleHandler;
import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
private final HttpClientFactory httpClientFactory;
private final MyBMWCommandOptionProvider commandOptionProvider;
+ private final LocationProvider locationProvider;
private String localeLanguage;
@Activate
public MyBMWHandlerFactory(final @Reference HttpClientFactory hcf, final @Reference MyBMWCommandOptionProvider cop,
- final @Reference LocaleProvider lp) {
+ final @Reference LocaleProvider localeP, final @Reference LocationProvider locationP) {
httpClientFactory = hcf;
commandOptionProvider = cop;
- localeLanguage = lp.getLocale().getLanguage().toLowerCase();
+ locationProvider = locationP;
+ localeLanguage = localeP.getLocale().getLanguage().toLowerCase();
}
@Override
if (THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(thingTypeUID)) {
return new MyBMWBridgeHandler((Bridge) thing, httpClientFactory, localeLanguage);
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
- VehicleHandler vh = new VehicleHandler(thing, commandOptionProvider, thingTypeUID.getId());
+ VehicleHandler vh = new VehicleHandler(thing, commandOptionProvider, locationProvider,
+ thingTypeUID.getId());
return vh;
}
return null;
* @author Bernd Weymann - Initial contribution
*/
public class Coordinates {
- public double latitude;// ": 50.556049,
- public double longitude;// ": 8.495669
+ public double latitude;
+ public double longitude;
}
public class Location {
public Coordinates coordinates;
public Address address;
- public int heading;// ": 222
+ public int heading;
}
import org.openhab.binding.mybmw.internal.utils.Converter;
import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
+import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
+import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
protected String selectedSession = Constants.UNDEF;
protected MyBMWCommandOptionProvider commandOptionProvider;
+ private LocationProvider locationProvider;
// Data Caches
protected Optional<String> vehicleStatusCache = Optional.empty();
protected Optional<byte[]> imageCache = Optional.empty();
- public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, String type) {
+ public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String type) {
super(thing);
commandOptionProvider = cop;
+ locationProvider = lp;
+ if (lp.getLocation() == null) {
+ logger.debug("Home location not available");
+ }
hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
- || type.equals(VehicleType.ELECTRIC_REX.toString());
+ || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
isHybrid = hasFuel && isElectric;
}
protected void updatePosition(Location pos) {
- updateChannel(CHANNEL_GROUP_LOCATION, GPS, PointType
- .valueOf(Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude)));
- updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
- updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(pos.address.formatted));
+ if (pos.coordinates.latitude < 0) {
+ updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF);
+ updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF);
+ updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF);
+ updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
+ } else {
+ PointType vehicleLocation = PointType.valueOf(
+ Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude));
+ updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation);
+ updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
+ updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(pos.address.formatted));
+ PointType homeLocation = locationProvider.getLocation();
+ if (homeLocation != null) {
+ updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
+ QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE));
+ } else {
+ updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
+ }
+ }
}
}
import org.openhab.binding.mybmw.internal.utils.Converter;
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
+import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
ChargeSessionsCallback chargeSessionCallback = new ChargeSessionsCallback();
ByteResponseCallback imageCallback = new ImageCallback();
- public VehicleHandler(Thing thing, MyBMWCommandOptionProvider cop, String driveTrain) {
- super(thing, cop, driveTrain);
+ public VehicleHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String driveTrain) {
+ super(thing, cop, lp, driveTrain);
}
@Override
}
if (v.properties.vehicleLocation == null) {
v.properties.vehicleLocation = new Location();
- v.properties.vehicleLocation.heading = -1;
+ v.properties.vehicleLocation.heading = Constants.INT_UNDEF;
v.properties.vehicleLocation.coordinates = new Coordinates();
- v.properties.vehicleLocation.coordinates.latitude = -1.234;
- v.properties.vehicleLocation.coordinates.longitude = -9.876;
+ v.properties.vehicleLocation.coordinates.latitude = Constants.INT_UNDEF;
+ v.properties.vehicleLocation.coordinates.longitude = Constants.INT_UNDEF;
v.properties.vehicleLocation.address = new Address();
v.properties.vehicleLocation.address.formatted = Constants.UNDEF;
}
channel-type.mybmw.front-right-target-channel.label = Tire Pressure Front Right Target
channel-type.mybmw.gps-channel.label = GPS Coordinates
channel-type.mybmw.heading-channel.label = Heading Angle
+channel-type.mybmw.home-distance-channel.label = Distance from Home
+channel-type.mybmw.home-distance-channel.description = Computed distance between vehicle and home location
channel-type.mybmw.hood-channel.label = Hood
channel-type.mybmw.image-view-channel.label = Image Viewport
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Front Side View
<channel id="gps" typeId="gps-channel"/>
<channel id="heading" typeId="heading-channel"/>
<channel id="address" typeId="address-channel"/>
+ <channel id="home-distance" typeId="home-distance-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>
<item-type>String</item-type>
<label>Address</label>
</channel-type>
+ <channel-type id="home-distance-channel">
+ <item-type>Number:Length</item-type>
+ <label>Distance from Home</label>
+ <description>Computed distance between vehicle and home location</description>
+ <state pattern="%d %unit%" readOnly="true"/>
+ </channel-type>
</thing:thing-descriptions>
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
import org.openhab.binding.mybmw.internal.dto.properties.CBS;
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.binding.mybmw.internal.handler.VehicleTests;
import org.openhab.binding.mybmw.internal.utils.Constants;
import org.openhab.binding.mybmw.internal.utils.Converter;
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
+import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.State;
public StatusWrapper(String type, String statusJson) {
hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
- || type.equals(VehicleType.ELECTRIC_REX.toString());
+ || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
isHybrid = hasFuel && isElectric;
assertEquals(expected.toString(), dtt.toString(), "Last Update");
break;
case GPS:
- assertTrue(state instanceof PointType);
- pt = (PointType) state;
- assertNotNull(vehicle.properties.vehicleLocation);
- assertEquals(
- PointType.valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude) + ","
- + Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude)),
- pt, "Coordinates");
+ if (state instanceof PointType) {
+ pt = (PointType) state;
+ assertNotNull(vehicle.properties.vehicleLocation);
+ assertEquals(
+ PointType.valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude)
+ + "," + Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude)),
+ pt, "Coordinates");
+ } // else no check needed
break;
case HEADING:
- assertTrue(state instanceof QuantityType);
- qt = ((QuantityType) state);
- assertEquals(Units.DEGREE_ANGLE, qt.getUnit(), "Angle Unit");
- assertNotNull(vehicle.properties.vehicleLocation);
- assertEquals(vehicle.properties.vehicleLocation.heading, qt.intValue(), 0.01, "Heading");
+ if (state instanceof QuantityType) {
+ qt = ((QuantityType) state);
+ assertEquals(Units.DEGREE_ANGLE, qt.getUnit(), "Angle Unit");
+ assertNotNull(vehicle.properties.vehicleLocation);
+ assertEquals(vehicle.properties.vehicleLocation.heading, qt.intValue(), 0.01, "Heading");
+ } // else no check needed
break;
case RANGE_RADIUS_ELECTRIC:
assertTrue(state instanceof QuantityType);
}
break;
case ADDRESS:
- assertTrue(state instanceof StringType);
- st = (StringType) state;
- assertEquals(st.toFullString(), vehicle.properties.vehicleLocation.address.formatted,
- "Location Address");
+ if (state instanceof StringType) {
+ st = (StringType) state;
+ assertEquals(st.toFullString(), vehicle.properties.vehicleLocation.address.formatted,
+ "Location Address");
+ } // else no check needed
+ break;
+ case HOME_DISTANCE:
+ if (state instanceof QuantityType) {
+ qt = (QuantityType) state;
+ PointType vehicleLocation = PointType
+ .valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude) + ","
+ + Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude));
+ int distance = vehicleLocation.distanceFrom(VehicleTests.HOME_LOCATION).intValue();
+ assertEquals(qt.intValue(), distance, "Distance from Home");
+ assertEquals(qt.getUnit(), SIUnits.METRE, "Distance from Home Unit");
+ } // else no check needed
break;
case RAW:
// don't assert raw channel
assertEquals("BMW", v.brand, "Car brand");
assertEquals(true, v.properties.areDoorsClosed, "Doors Closed");
assertEquals(76, v.properties.electricRange.distance.value, "Electric Range");
- assertEquals(6.789, v.properties.vehicleLocation.coordinates.longitude, 0.1, "Location lon");
+ assertEquals(9.876, v.properties.vehicleLocation.coordinates.longitude, 0.1, "Location lon");
assertEquals("immediateCharging", v.status.chargingProfile.chargingMode, "Charging Mode");
assertEquals(2, v.status.chargingProfile.getTimerId(2).id, "Timer ID");
assertEquals("[sunday]", v.status.chargingProfile.getTimerId(2).timerWeekDays.toString(), "Timer Weekdays");
import org.openhab.binding.mybmw.internal.dto.ChargeStatisticWrapper;
import org.openhab.binding.mybmw.internal.util.FileReader;
import org.openhab.binding.mybmw.internal.utils.Constants;
+import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
Thing thing = mock(Thing.class);
when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
- cch = new VehicleHandler(thing, cop, type);
+ LocationProvider locationProvider = mock(LocationProvider.class);
+ cch = new VehicleHandler(thing, cop, locationProvider, type);
VehicleConfiguration vc = new VehicleConfiguration();
vc.vin = Constants.ANONYMOUS;
Optional<VehicleConfiguration> ovc = Optional.of(vc);
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
+import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
Thing thing = mock(Thing.class);
when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
- cch = new VehicleHandler(thing, cop, type);
+ LocationProvider locationProvider = mock(LocationProvider.class);
+ cch = new VehicleHandler(thing, cop, locationProvider, type);
tc = mock(ThingHandlerCallback.class);
cch.setCallback(tc);
channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
import org.openhab.binding.mybmw.internal.dto.StatusWrapper;
import org.openhab.binding.mybmw.internal.util.FileReader;
import org.openhab.binding.mybmw.internal.utils.Constants;
+import org.openhab.core.i18n.LocationProvider;
+import org.openhab.core.library.types.PointType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
private static final int CHECK_AVAILABLE = 3;
private static final int SERVICE_AVAILABLE = 3;
private static final int SERVICE_EMPTY = 3;
- private static final int LOCATION = 3;
+ private static final int LOCATION = 4;
private static final int CHARGE_PROFILE = 44;
private static final int TIRES = 8;
-
+ public static final PointType HOME_LOCATION = new PointType("54.321,9.876");
@Nullable
ArgumentCaptor<ChannelUID> channelCaptor;
@Nullable
Thing thing = mock(Thing.class);
when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
- cch = new VehicleHandler(thing, cop, type);
+ LocationProvider locationProvider = mock(LocationProvider.class);
+ when(locationProvider.getLocation()).thenReturn(HOME_LOCATION);
+ cch = new VehicleHandler(thing, cop, locationProvider, type);
VehicleConfiguration vc = new VehicleConfiguration();
vc.vin = vin;
Optional<VehicleConfiguration> ovc = Optional.of(vc);
logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
setup(VehicleType.MILD_HYBRID.toString(), "anonymous");
String content = FileReader.readFileInString("src/test/resources/responses/G21/340i.json");
- assertTrue(testVehicle(content, 38, Optional.empty()));
- // assertTrue(testVehicle(content,
- // STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + TIRES,
- // Optional.empty()));
+ assertTrue(testVehicle(content,
+ STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty()));
}
}
],
"vehicleLocation": {
"coordinates": {
- "latitude": 1.2345,
- "longitude": 6.789
+ "latitude": 54.321,
+ "longitude": 9.876
},
"address": {
"formatted": "anonymous"