diff --git a/README.md b/README.md index f584134..db7281a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ Your car must have the lastest onboard modem functionality and have registered/a I have added a service to poll the car for updates, due to the battery drain I have left this up to you to set the interval. The service to be called is "refresh_status" and can be accessed in home assistant using "fordpas.refresh_status" with no parameters. **This will take up to 5 mins to update from the car once the service has been run** +### +Click on options and choose imperial or metric to display in km/miles. Takes effect on next restart of home assistant. Default is Metric + ## Currently Working @@ -33,6 +36,7 @@ I have added a service to poll the car for updates, due to the battery drain I h - Remote Start - Window Status (Only if your car supports it!) - Last Car Refresh status +- Car Tracker ## Coming Soon diff --git a/custom_components/fordpass/__init__.py b/custom_components/fordpass/__init__.py index a01e4c2..9c20156 100644 --- a/custom_components/fordpass/__init__.py +++ b/custom_components/fordpass/__init__.py @@ -16,12 +16,12 @@ UpdateFailed, ) -from .const import DOMAIN, MANUFACTURER, VEHICLE, VIN +from .const import DOMAIN, MANUFACTURER, VEHICLE, VIN, DEFAULT_UNIT, CONF_UNIT from .fordpass_new import Vehicle CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) -PLATFORMS = ["lock", "sensor", "switch"] +PLATFORMS = ["lock", "sensor", "switch", "device_tracker"] _LOGGER = logging.getLogger(__name__) @@ -44,6 +44,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator.async_refresh() # Get initial data + if not entry.options: + await async_update_options(hass, entry) + if not coordinator.last_update_success: raise ConfigEntryNotReady @@ -68,6 +71,17 @@ async def async_refresh_status_service(service_call): return True +async def async_update_options(hass, config_entry): + options = { + CONF_UNIT: entry.data.get( + CONF_UNIT, DEFAULT_UNIT + ) + } + hass.config_entries.async_update_entry( + config_entry, options=options + ) + + def refresh_status(service, hass, coordinator): _LOGGER.debug("Running Service") coordinator.vehicle.requestUpdate() diff --git a/custom_components/fordpass/config_flow.py b/custom_components/fordpass/config_flow.py index 9048d87..f5206e8 100644 --- a/custom_components/fordpass/config_flow.py +++ b/custom_components/fordpass/config_flow.py @@ -4,8 +4,11 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback + + +from .const import DOMAIN, VIN, CONF_UNITS, CONF_UNIT, DEFAULT_UNIT # pylint:disable=unused-import -from .const import DOMAIN, VIN # pylint:disable=unused-import from .fordpass_new import Vehicle _LOGGER = logging.getLogger(__name__) @@ -66,6 +69,36 @@ async def async_step_user(self, user_input=None): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlow(config_entry) + +class OptionsFlow(config_entries.OptionsFlow): + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Initialize options flow.""" + self.config_entry = config_entry + + + async def async_step_init(self, user_input=None): + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + options = { + vol.Optional( + CONF_UNIT, + default=self.config_entry.options.get( + CONF_UNIT, DEFAULT_UNIT + ), + ): vol.In(CONF_UNITS) + } + + return self.async_show_form( + step_id="init", data_schema=vol.Schema(options) + ) + + class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index b235fb6..cecaa8e 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -7,3 +7,11 @@ MANUFACTURER = "Ford Motor Company" VEHICLE = "Ford Vehicle" + +DEFAULT_UNIT = "metric" +CONF_UNIT = "units" + +CONF_UNITS = [ + "imperial", + "metric" +] \ No newline at end of file diff --git a/custom_components/fordpass/device_tracker.py b/custom_components/fordpass/device_tracker.py new file mode 100644 index 0000000..f45fd5a --- /dev/null +++ b/custom_components/fordpass/device_tracker.py @@ -0,0 +1,48 @@ +import logging +from datetime import timedelta + +from homeassistant.components.device_tracker import SOURCE_TYPE_GPS +from homeassistant.components.device_tracker.config_entry import TrackerEntity + +from . import FordPassEntity +from .const import DOMAIN + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add the Entities from the config.""" + entry = hass.data[DOMAIN][config_entry.entry_id] + + async_add_entities([CarTracker(entry, "gps")], True) + + +class CarTracker(FordPassEntity, TrackerEntity): + def __init__(self, coordinator, sensor): + + self._attr = {} + self.sensor = sensor + self.coordinator = coordinator + self._device_id = "fordpass_tracker" + + @property + def latitude(self): + return float(self.coordinator.data[self.sensor]["latitude"]) + + @property + def longitude(self): + return float(self.coordinator.data[self.sensor]["longitude"]) + + @property + def source_type(self): + return SOURCE_TYPE_GPS + + @property + def name(self): + return "fordpass_tracker" + + @property + def device_id(self): + return self.device_id + + @property + def device_state_attributes(self): + return self.coordinator.data[self.sensor].items() diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index e4e2036..9f039cd 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -22,7 +22,7 @@ class Lock(FordPassEntity, LockEntity): def __init__(self, coordinator): """Initialize.""" - super().__init__(device_id="lock", name="Lock", coordinator=coordinator) + super().__init__(device_id="fordpass_doorlock", name="fordpass_doorlock", coordinator=coordinator) async def async_lock(self, **kwargs): """Locks the vehicle.""" diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 86e4d72..1cdf8fa 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -5,7 +5,7 @@ from homeassistant.util import Throttle from . import FordPassEntity -from .const import DOMAIN +from .const import DOMAIN, CONF_UNIT _LOGGER = logging.getLogger(__name__) @@ -28,13 +28,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ] sensors = [] for snr in snrarray: - async_add_entities([CarSensor(entry, snr)], True) + async_add_entities([CarSensor(entry, snr, config_entry.options)], True) -class CarSensor(FordPassEntity, Entity): - def __init__(self, coordinator, sensor): +class CarSensor(FordPassEntity, Entity,): + def __init__(self, coordinator, sensor, options): self.sensor = sensor + self.options = options self._attr = {} self.coordinator = coordinator self._device_id = "fordpass_" + sensor @@ -42,9 +43,12 @@ def __init__(self, coordinator, sensor): def get_value(self, ftype): if ftype == "state": if self.sensor == "odometer": - return self.coordinator.data[self.sensor]["value"] + if self.options[CONF_UNIT] == "imperial": + return round(float(self.coordinator.data[self.sensor]["value"]) / 1.60934) + else: + return self.coordinator.data[self.sensor]["value"] elif self.sensor == "fuel": - return self.coordinator.data[self.sensor]["fuelLevel"] + return round(self.coordinator.data[self.sensor]["fuelLevel"]) elif self.sensor == "battery": return self.coordinator.data[self.sensor]["batteryHealth"]["value"] elif self.sensor == "oil": @@ -73,9 +77,12 @@ def get_value(self, ftype): return self.coordinator.data[self.sensor] elif ftype == "measurement": if self.sensor == "odometer": - return "km" + if self.options[CONF_UNIT] == "imperial": + return "mi" + else: + return "km" elif self.sensor == "fuel": - return "L" + return "%" elif self.sensor == "battery": return None elif self.sensor == "oil": diff --git a/custom_components/fordpass/strings.json b/custom_components/fordpass/strings.json index c766401..a235be8 100644 --- a/custom_components/fordpass/strings.json +++ b/custom_components/fordpass/strings.json @@ -1,23 +1,33 @@ { - "title": "FordPass", - "config": { - "step": { - "user": { - "data": { - "vin": "VIN", - "username": "FordPass Username (Email)", - "password": "FordPass Password" - } + "title": "FordPass", + "config": { + "step": { + "user": { + "data": { + "vin": "VIN", + "username": "FordPass Username (Email)", + "password": "FordPass Password" } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "units": "Unit of Measurement" + }, + "description": "Configure fordpass options" } } } - \ No newline at end of file +} + \ No newline at end of file diff --git a/info.md b/info.md index 4d3f95d..8a14f01 100644 --- a/info.md +++ b/info.md @@ -1,5 +1,12 @@ # **Changelog** +### Version 1.05 +- Added device_tracker type (fordpass_tracker) +- Added imperial or metric selection +- Change fuel reading to % +- Renamed lock entity from "lock.lock" to "lock.fordpass_lock" + + ### Version 1.04 - Added window position status - Added service "fresh_status" to allow for polling the car at a set interval or event