diff --git a/README.md b/README.md index db7281a..0395a0c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # Fordpass Home Assistant Integration ## Credit -https://github.com/clarkd - Initial Home Assistant automation idea and Python code (Lock/Unlock) +- https://github.com/clarkd - Initial Home Assistant automation idea and Python code (Lock/Unlock) +- https://github.com/pinballnewf - Figuring out the application ID issue ## Install Use HACS and add as a custom repo. Once the integration is installed go to your integrations and follow the configuration options to specify the below: - Username (Fordpass App) - Password (Fordpass App) - VIN Number +- Region (Where you are based, required for tokens to work correctly) ## Usage Your car must have the lastest onboard modem functionality and have registered/authorised the fordpass application @@ -37,6 +39,7 @@ Click on options and choose imperial or metric to display in km/miles. Takes eff - Window Status (Only if your car supports it!) - Last Car Refresh status - Car Tracker +- Supports Multiple Regions ## Coming Soon diff --git a/custom_components/fordpass/__init__.py b/custom_components/fordpass/__init__.py index 26a3aba..0232852 100644 --- a/custom_components/fordpass/__init__.py +++ b/custom_components/fordpass/__init__.py @@ -16,7 +16,7 @@ UpdateFailed, ) -from .const import CONF_UNIT, DEFAULT_UNIT, DOMAIN, MANUFACTURER, VEHICLE, VIN +from .const import CONF_UNIT, DEFAULT_UNIT, DOMAIN, MANUFACTURER, REGION, VEHICLE, VIN from .fordpass_new import Vehicle CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) @@ -39,8 +39,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): user = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] vin = entry.data[VIN] - - coordinator = FordPassDataUpdateCoordinator(hass, user, password, vin, 1) + for ar in entry.data: + _LOGGER.debug(ar) + if REGION in entry.data.keys(): + _LOGGER.debug(entry.data[REGION]) + region = entry.data[REGION] + else: + _LOGGER.debug("CANT GET REGION") + region = "North America & Canada" + coordinator = FordPassDataUpdateCoordinator(hass, user, password, vin, region, 1) await coordinator.async_refresh() # Get initial data @@ -100,11 +107,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): class FordPassDataUpdateCoordinator(DataUpdateCoordinator): """DataUpdateCoordinator to handle fetching new data about the vehicle.""" - def __init__(self, hass, user, password, vin, saveToken=False): + def __init__(self, hass, user, password, vin, region, saveToken=False): """Initialize the coordinator and set up the Vehicle object.""" self._hass = hass self.vin = vin - self.vehicle = Vehicle(user, password, vin, saveToken) + self.vehicle = Vehicle(user, password, vin, region, saveToken) self._available = True super().__init__( diff --git a/custom_components/fordpass/config_flow.py b/custom_components/fordpass/config_flow.py index 2828c14..3a3b788 100644 --- a/custom_components/fordpass/config_flow.py +++ b/custom_components/fordpass/config_flow.py @@ -11,6 +11,8 @@ CONF_UNITS, DEFAULT_UNIT, DOMAIN, + REGION, + REGION_OPTIONS, VIN, ) from .fordpass_new import Vehicle @@ -22,6 +24,7 @@ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Required(VIN): vol.All(str, vol.Length(min=17, max=17)), + vol.Required(REGION): vol.In(REGION_OPTIONS), } ) @@ -31,7 +34,8 @@ async def validate_input(hass: core.HomeAssistant, data): Data has the keys from DATA_SCHEMA with values provided by the user. """ - vehicle = Vehicle(data[CONF_USERNAME], data[CONF_PASSWORD], data[VIN]) + _LOGGER.debug(data[REGION]) + vehicle = Vehicle(data[CONF_USERNAME], data[CONF_PASSWORD], data[VIN], data[REGION]) try: result = await hass.async_add_executor_job(vehicle.auth) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index 2fcc0cd..63c0fd6 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -12,3 +12,8 @@ CONF_UNIT = "units" CONF_UNITS = ["imperial", "metric"] + + +REGION = "region" + +REGION_OPTIONS = ["UK&Europe", "Australia", "North America & Canada"] diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 2998b5f..9ebb59a 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -14,20 +14,26 @@ apiHeaders = { **defaultHeaders, - "Application-Id": "5C80A6BB-CF0D-4A30-BDBF-FC804B5C1A98", "Content-Type": "application/json", } +region_lookup = { + "UK&Europe": "1E8C7794-FF5F-49BC-9596-A1E0C86C5B19", + "Australia": "5C80A6BB-CF0D-4A30-BDBF-FC804B5C1A98", + "North America & Canada": "71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592", +} + baseUrl = "https://usapi.cv.ford.com/api" class Vehicle(object): # Represents a Ford vehicle, with methods for status and issuing commands - def __init__(self, username, password, vin, saveToken=False): + def __init__(self, username, password, vin, region, saveToken=False): self.username = username self.password = password self.saveToken = saveToken + self.region = region_lookup[region] self.vin = vin self.token = None self.expires = None @@ -59,7 +65,7 @@ def auth(self): logging.info("Succesfully fetched token Stage1") result = r.json() data = {"code": result["access_token"]} - headers = {**apiHeaders} + headers = {**apiHeaders, "Application-Id": self.region} # Fetch OAUTH token stage 2 and refresh token r = requests.put( "https://api.mps.ford.com/api/oauth2/v1/token", @@ -81,7 +87,7 @@ def auth(self): def refreshToken(self, token): # Token is invalid so let's try refreshing it data = {"refresh_token": token["refresh_token"]} - headers = {**apiHeaders} + headers = {**apiHeaders, "Application-Id": self.region} r = requests.put( "https://api.mps.ford.com/api/oauth2/v1/refresh", @@ -101,7 +107,7 @@ def __acquireToken(self): # Fetch and refresh token as needed # If file exists read in token file and check it's valid if self.saveToken: - if os.path.isfile("/tmp/token.txt"): + if os.path.isfile("/tmp/fordpass_token.txt"): data = self.readToken() else: data = dict() @@ -129,13 +135,13 @@ def __acquireToken(self): def writeToken(self, token): # Save token to file to be reused - with open("/tmp/token.txt", "w") as outfile: + with open("/tmp/fordpass_token.txt", "w") as outfile: token["expiry_date"] = time.time() + token["expires_in"] json.dump(token, outfile) def readToken(self): # Get saved token from file - with open("/tmp/token.txt") as token_file: + with open("/tmp/fordpass_token.txt") as token_file: return json.load(token_file) def status(self): @@ -145,7 +151,11 @@ def status(self): params = {"lrdt": "01-01-1970 00:00:00"} - headers = {**apiHeaders, "auth-token": self.token} + headers = { + **apiHeaders, + "auth-token": self.token, + "Application-Id": self.region, + } r = requests.get( f"{baseUrl}/vehicles/v4/{self.vin}/status", params=params, headers=headers @@ -203,7 +213,11 @@ def __makeRequest(self, method, url, data, params): Make a request to the given URL, passing data/params as needed """ - headers = {**apiHeaders, "auth-token": self.token} + headers = { + **apiHeaders, + "auth-token": self.token, + "Application-Id": self.region + } return getattr(requests, method.lower())( url, headers=headers, data=data, params=params diff --git a/custom_components/fordpass/strings.json b/custom_components/fordpass/strings.json index a235be8..4488895 100644 --- a/custom_components/fordpass/strings.json +++ b/custom_components/fordpass/strings.json @@ -6,7 +6,8 @@ "data": { "vin": "VIN", "username": "FordPass Username (Email)", - "password": "FordPass Password" + "password": "FordPass Password", + "region": "FordPass Region" } } }, diff --git a/custom_components/fordpass/translations/en.json b/custom_components/fordpass/translations/en.json index c65d0f7..52862b6 100644 --- a/custom_components/fordpass/translations/en.json +++ b/custom_components/fordpass/translations/en.json @@ -13,10 +13,21 @@ "data": { "password": "FordPass Password", "username": "FordPass Username (Email)", - "vin": "VIN" + "vin": "VIN", + "region" : "FordPass Region" } } } }, + "options": { + "step": { + "init": { + "data": { + "units": "Unit of Measurement" + }, + "description": "Configure fordpass options" + } + } + }, "title": "FordPass" } \ No newline at end of file diff --git a/info.md b/info.md index 06b5b30..a02a687 100644 --- a/info.md +++ b/info.md @@ -1,5 +1,13 @@ # **Changelog** +### Version 1.07 +- Support for multiple regions (Fixes unavaliable bug) +- Token renamed to fordpass_token + +**In order to support regions you will need to reinstall the integration to change region** (Existing installs will default to North America) + +### Version 1.06 +- Minor bug fix ### Version 1.05 - Added device_tracker type (fordpass_tracker) - Added imperial or metric selection @@ -19,9 +27,6 @@ - Added car poll refresh - - - Fordpass can be configured via Integrations UI ## Integration page