From 7a07bce87dc5d9666c6fbc24f7df62c8d7a85202 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Sat, 23 Sep 2023 18:28:19 -0400 Subject: [PATCH 01/63] Added charge_logs function to return the charge logs --- custom_components/fordpass/fordpass_new.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 22c3031..0e94a7e 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -456,6 +456,36 @@ def request_update(self, vin=""): ) return status.json()["status"] + def charge_log(self): + """Get Charge logs from account""" + self.__acquire_token() + + if self.region2 == "Australia": + countryheader = "AUS" + elif self.region2 == "North America & Canada": + countryheader = "USA" + elif self.region2 == "UK&Europe": + countryheader = "GBR" + else: + countryheader = "USA" + headers = { + **apiHeaders, + "Auth-Token": self.token, + "Application-Id": self.region, + "Countrycode": countryheader, + "Locale": "EN-US" + } + + response = session.get( + f"{GUARD_URL}/electrification/experiences/v1/devices/{self.vin}/energy-transfer-logs/", + headers=headers) + if response.status_code == 200: + result = response.json() + return result["energyTransferLogs"] + + response.raise_for_status() + return None + def __make_request(self, method, url, data, params): """ Make a request to the given URL, passing data/params as needed From 4c772f7c85cdc62d07f02696c4c9e10a5c5e106b Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Sat, 23 Sep 2023 18:41:16 -0400 Subject: [PATCH 02/63] GET request did not require country code --- custom_components/fordpass/fordpass_new.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 0e94a7e..6367519 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -460,25 +460,16 @@ def charge_log(self): """Get Charge logs from account""" self.__acquire_token() - if self.region2 == "Australia": - countryheader = "AUS" - elif self.region2 == "North America & Canada": - countryheader = "USA" - elif self.region2 == "UK&Europe": - countryheader = "GBR" - else: - countryheader = "USA" headers = { **apiHeaders, "Auth-Token": self.token, - "Application-Id": self.region, - "Countrycode": countryheader, - "Locale": "EN-US" + "Application-Id": self.region } response = session.get( f"{GUARD_URL}/electrification/experiences/v1/devices/{self.vin}/energy-transfer-logs/", headers=headers) + if response.status_code == 200: result = response.json() return result["energyTransferLogs"] From d85119ad3455c0c0482f45ed2604f4e9e9d3f66a Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Thu, 5 Oct 2023 00:03:33 -0400 Subject: [PATCH 03/63] Added chargeStatus function and sensor. --- custom_components/fordpass/__init__.py | 3 ++ custom_components/fordpass/const.py | 1 + custom_components/fordpass/fordpass_new.py | 21 +++++++++++++ custom_components/fordpass/sensor.py | 34 ++++++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/custom_components/fordpass/__init__.py b/custom_components/fordpass/__init__.py index 41eaa72..20e0ba8 100644 --- a/custom_components/fordpass/__init__.py +++ b/custom_components/fordpass/__init__.py @@ -215,6 +215,9 @@ async def _async_update_data(self): data["vehicles"] = await self._hass.async_add_executor_job( self.vehicle.vehicles ) + data["chargeStatus"] = await self._hass.async_add_executor_job( + self.vehicle.charge_status + ) _LOGGER.debug(data) # If data has now been fetched but was previously unavailable, log and reset if not self._available: diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index b8ae300..bdfa34d 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,6 +42,7 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, + "chargeStatus": {"icon": "mdi:ev-station"}, "deepSleepInProgress": { "icon": "mdi:power-sleep", "name": "Deep Sleep Mode Active", diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 6367519..4d76278 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -477,6 +477,27 @@ def charge_log(self): response.raise_for_status() return None + def charge_status(self): + """Get Charge status from account""" + self.__acquire_token() + + headers = { + **apiHeaders, + "Auth-Token": self.token, + "Application-Id": self.region + } + + response = session.get( + f"{GUARD_URL}/electrification/experiences/v1/devices/{self.vin}/energy-transfer-status/", + headers=headers) + + if response.status_code == 200: + result = response.json() + return result + + response.raise_for_status() + return None + def __make_request(self, method, url, data, params): """ Make a request to the given URL, passing data/params as needed diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 3a08c94..94df35e 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -149,6 +149,12 @@ def get_value(self, ftype): if self.coordinator.data["messages"] is None: return None return len(self.coordinator.data["messages"]) + if self.sensor == "chargeStatus": + if self.coordinator.data["chargeStatus"] is not None and self.coordinator.data["chargeStatus"]["power"] is not None: + power_wh = self.coordinator.data["chargeStatus"]["power"] + power_kwh = power_wh / 1000 + return round(power_kwh, 1) + return "Not Supported" if self.sensor == "dieselSystemStatus": if self.coordinator.data["dieselSystemStatus"]["filterRegenerationStatus"] is not None: return self.coordinator.data["dieselSystemStatus"]["filterRegenerationStatus"] @@ -197,6 +203,8 @@ def get_value(self, ftype): if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": return "mi" return "km" + if self.sensor == "chargeStatus": + return "kWh" if self.sensor == "exhaustFluidLevel": return "%" return None @@ -344,6 +352,32 @@ def get_value(self, ftype): ]["value"] return elecs + if self.sensor == "chargeStatus": + if self.coordinator.data["chargeStatus"] is None: + return None + cs = {} + if self.coordinator.data[self.sensor]["chargerType"] is not None: + cs["chargerType"] = self.coordinator.data[self.sensor]["chargerType"] + if self.coordinator.data[self.sensor]["currentTargetSoc"] is not None: + cs["currentTargetSoc"] = self.coordinator.data[self.sensor]["currentTargetSoc"] + if ( + self.coordinator.data[self.sensor]["plugDetails"] is not None and + self.coordinator.data[self.sensor]["plugDetails"]["plugInTime"] is not None + ): + cs["Plug In Time"] = self.coordinator.data[self.sensor]["plugDetails"]["plugInTime"] + if ( + self.coordinator.data[self.sensor]["plugDetails"] is not None and + self.coordinator.data[self.sensor]["plugDetails"]["totalPluggedInTime"] is not None + ): + cs["Total Plugged In Time"] = self.coordinator.data[self.sensor]["plugDetails"]["totalPluggedInTime"] + + if self.coordinator.data[self.sensor]["power"] is not None: + power_wh = self.coordinator.data["chargeStatus"]["power"] + power_kwh = power_wh / 1000 + cs["Power Level kWh"] = round(power_kwh, 1) + if self.coordinator.data[self.sensor]["energyConsumed"] is not None: + cs["Energy Consumed"] = self.coordinator.data[self.sensor]["energyConsumed"] + return cs if self.sensor == "zoneLighting": if "zoneLighting" not in self.coordinator.data: return None From c126eebe1b44bf1f0bec265c28ec680dda7bdf42 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Mon, 9 Oct 2023 23:35:28 -0400 Subject: [PATCH 04/63] gross script to grab json from autonomic API --- custom_components/fordpass/autonomicData.py | 80 +++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 custom_components/fordpass/autonomicData.py diff --git a/custom_components/fordpass/autonomicData.py b/custom_components/fordpass/autonomicData.py new file mode 100644 index 0000000..3eb26d5 --- /dev/null +++ b/custom_components/fordpass/autonomicData.py @@ -0,0 +1,80 @@ +import json +import requests + +fp_username = "" #FordPass Username +fp_password = "" #FordPass password +fp_vin = "" #FordPass VIN for vehicle to get data from +fp_token = "Hass_fordpass_token.txt" #Name of the file for the user_fordpass_token.txt from the fordpass-ha integration + + +def get_autonomic_token(ford_access_token): + url = "https://accounts.autonomic.ai/v1/auth/oidc/token" + headers = { + "accept": "*/*", + "content-type": "application/x-www-form-urlencoded" + } + data = { + "subject_token": ford_access_token, + "subject_issuer": "fordpass", + "client_id": "fordpass-prod", + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt" + } + + try: + response = requests.post(url, headers=headers, data=data) + response.raise_for_status() + autonomic_token_data = response.json() + return autonomic_token_data + + except requests.exceptions.HTTPError as errh: + print(f"HTTP Error: {errh}") + except requests.exceptions.ConnectionError as errc: + print(f"Error Connecting: {errc}") + except requests.exceptions.Timeout as errt: + print(f"Timeout Error: {errt}") + except requests.exceptions.RequestException as err: + print(f"Something went wrong: {err}") + +def get_vehicle_status(vin, access_token): + BASE_URL = "https://api.autonomic.ai/" + endpoint = f"v1beta/telemetry/sources/fordpass/vehicles/{vin}:query" + url = f"{BASE_URL}{endpoint}" + headers = { + "Authorization": f"Bearer {access_token}", # Replace 'your_autonom_token' with the actual Autonomic API token + "Content-Type": "application/json", + "accept": "*/*" + } + + try: + response = requests.post(url, headers=headers, json={}) + response.raise_for_status() # Raise HTTPError for bad requests (4xx and 5xx status codes) + + # Parse the JSON response + vehicle_status_data = response.json() + return vehicle_status_data + + except requests.exceptions.HTTPError as errh: + print(f"HTTP Error: {errh}") + except requests.exceptions.ConnectionError as errc: + print(f"Error Connecting: {errc}") + except requests.exceptions.Timeout as errt: + print(f"Timeout Error: {errt}") + except requests.exceptions.RequestException as err: + print(f"Something went wrong: {err}") + +# Get FordPass token +with open(fp_token, 'r') as file: + fp_token_data = json.load(file) + +ford_access_token = fp_token_data['access_token'] + +# Exchange Fordpass token for Autonomic Token +autonomic_token = get_autonomic_token(ford_access_token) +vehicle_status = get_vehicle_status(fp_vin, autonomic_token["access_token"]) + +# Write the updated JSON data to the file +with open('new_data.json', 'w') as file: + json.dump(vehicle_status, file, indent=4) +print("done") + From 38e61eac349e9d93d44622d03e12466572d66c2b Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Mon, 9 Oct 2023 23:37:46 -0400 Subject: [PATCH 05/63] comments --- custom_components/fordpass/autonomicData.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/custom_components/fordpass/autonomicData.py b/custom_components/fordpass/autonomicData.py index 3eb26d5..1c3dd93 100644 --- a/custom_components/fordpass/autonomicData.py +++ b/custom_components/fordpass/autonomicData.py @@ -1,10 +1,19 @@ import json import requests -fp_username = "" #FordPass Username -fp_password = "" #FordPass password -fp_vin = "" #FordPass VIN for vehicle to get data from -fp_token = "Hass_fordpass_token.txt" #Name of the file for the user_fordpass_token.txt from the fordpass-ha integration +# Place this script in the /config/custom_components/fordpass folder on your HomeAssistant +# Add the details below +# run from a terminal: python3 autonomicData.py +# It will create autonomicData.json in the same folder + +#FordPass Username +fp_username = "" +#FordPass password +fp_password = "" +#FordPass VIN for vehicle to get data from +fp_vin = "" +#Name of the file for the user_fordpass_token.txt from the fordpass-ha integration +fp_token = "Hass_fordpass_token.txt" def get_autonomic_token(ford_access_token): @@ -74,7 +83,7 @@ def get_vehicle_status(vin, access_token): vehicle_status = get_vehicle_status(fp_vin, autonomic_token["access_token"]) # Write the updated JSON data to the file -with open('new_data.json', 'w') as file: +with open('autonomicData.json', 'w') as file: json.dump(vehicle_status, file, indent=4) print("done") From b5869b9f96e4f417091b5726bd905b26703f1fc2 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Mon, 9 Oct 2023 23:40:00 -0400 Subject: [PATCH 06/63] not using un/pw --- custom_components/fordpass/autonomicData.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/custom_components/fordpass/autonomicData.py b/custom_components/fordpass/autonomicData.py index 1c3dd93..6dc2b03 100644 --- a/custom_components/fordpass/autonomicData.py +++ b/custom_components/fordpass/autonomicData.py @@ -6,10 +6,6 @@ # run from a terminal: python3 autonomicData.py # It will create autonomicData.json in the same folder -#FordPass Username -fp_username = "" -#FordPass password -fp_password = "" #FordPass VIN for vehicle to get data from fp_vin = "" #Name of the file for the user_fordpass_token.txt from the fordpass-ha integration From dfc7d7446cf51377a4db24f519ea9dac88f58246 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 19:33:21 +1000 Subject: [PATCH 07/63] Start of code change --- custom_components/fordpass/const.py | 29 +++++++++++--------- custom_components/fordpass/device_tracker.py | 15 +++------- custom_components/fordpass/manifest.json | 2 +- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index b8ae300..0a10722 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -41,20 +41,20 @@ "doorStatus": {"icon": "mdi:car-door"}, "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, - "elVeh": {"icon": "mdi:ev-station"}, - "deepSleepInProgress": { - "icon": "mdi:power-sleep", - "name": "Deep Sleep Mode Active", - }, - "firmwareUpgInProgress": { + #"elVeh": {"icon": "mdi:ev-station"}, + #"deepSleepInProgress": { + # "icon": "mdi:power-sleep", + # "name": "Deep Sleep Mode Active", + #}, + #"firmwareUpgInProgress": { "icon": "mdi:one-up", - "name": "Firmware Update In Progress", - }, - "remoteStartStatus": {"icon": "mdi:remote"}, - "zoneLighting": {"icon": "mdi:spotlight-beam"}, + ## "name": "Firmware Update In Progress", + #}, + #"remoteStartStatus": {"icon": "mdi:remote"}, + #"zoneLighting": {"icon": "mdi:spotlight-beam"}, "messages": {"icon": "mdi:message-text"}, - "dieselSystemStatus": {"icon": "mdi:smoking-pipe"}, - "exhaustFluidLevel": {"icon": "mdi:barrel"} + #"dieselSystemStatus": {"icon": "mdi:smoking-pipe"}, + #"exhaustFluidLevel": {"icon": "mdi:barrel"} } SWITCHES = {"ignition": {"icon": "hass:power"}, "guardmode": {"icon": "mdi:shield-key"}} @@ -72,4 +72,7 @@ }, } -SWITCHES = {"ignition": {"icon": "hass:power"}, "guardmode": {"icon": "mdi:shield-key"}} +SWITCHES = { + "ignition": {"icon": "hass:power"}, + #"guardmode": {"icon": "mdi:shield-key"} + } diff --git a/custom_components/fordpass/device_tracker.py b/custom_components/fordpass/device_tracker.py index 5202358..7c08346 100644 --- a/custom_components/fordpass/device_tracker.py +++ b/custom_components/fordpass/device_tracker.py @@ -15,7 +15,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entry = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] # Added a check to see if the car supports GPS - if entry.data["gps"] is not None: + if entry.data["metrics"]["position"] != None: async_add_entities([CarTracker(entry, "gps")], True) else: _LOGGER.debug("Vehicle does not support GPS") @@ -24,28 +24,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class CarTracker(FordPassEntity, TrackerEntity): def __init__(self, coordinator, sensor): - super().__init__( - device_id="fordpass_" + sensor, - name="fordpass_" + sensor, - coordinator=coordinator - ) - self._attr = {} self.sensor = sensor self.coordinator = coordinator + self.data = coordinator.data["metrics"] self._device_id = "fordpass_tracker" # Required for HA 2022.7 self.coordinator_context = object() @property def latitude(self): - """Return latitude from Vehicle GPS""" - return float(self.coordinator.data[self.sensor]["latitude"]) + return float(self.data["position"]["value"]["location"]["lat"]) @property def longitude(self): - """Return longitude from Vehicle GPS""" - return float(self.coordinator.data[self.sensor]["longitude"]) + return float(self.data["position"]["value"]["location"]["lon"]) @property def source_type(self): diff --git a/custom_components/fordpass/manifest.json b/custom_components/fordpass/manifest.json index 05f9bd8..a02b2cd 100644 --- a/custom_components/fordpass/manifest.json +++ b/custom_components/fordpass/manifest.json @@ -12,6 +12,6 @@ "loggers": ["custom_components.fordpass"], "requirements": [], "ssdp": [], - "version": "0.1.52", + "version": "0.1.53", "zeroconf": [] } \ No newline at end of file From ccc4360fac2fc881f221d087b8def24bcdb03aea Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 19:44:59 +1000 Subject: [PATCH 08/63] updated locks and switch --- custom_components/fordpass/lock.py | 28 +++++++++++++++++----------- custom_components/fordpass/switch.py | 24 ++++++++++-------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index ff0f55c..6eb7223 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -14,43 +14,49 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entry = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] lock = Lock(entry) - if lock.coordinator.data.get("lockStatus", {}) and lock.coordinator.data["lockStatus"]["value"] != "ERROR": + if lock.coordinator.data["metrics"]["doorLockStatus"] and lock.coordinator.data["metrics"]["doorLockStatus"][0]["value"] != "ERROR": async_add_entities([lock], False) else: _LOGGER.debug("Ford model doesn't support remote locking") + class Lock(FordPassEntity, LockEntity): """Defines the vehicle's lock.""" def __init__(self, coordinator): """Initialize.""" - super().__init__( - device_id="fordpass_doorlock", - name="fordpass_doorlock", - coordinator=coordinator, - ) + self._device_id="fordpass_doorlock" + self.coordinator = coordinator + + self.data = coordinator.data["metrics"] + + # Required for HA 2022.7 + self.coordinator_context = object() async def async_lock(self, **kwargs): """Locks the vehicle.""" self._attr_is_locking = True self.async_write_ha_state() _LOGGER.debug("Locking %s", self.coordinator.vin) - await self.coordinator.hass.async_add_executor_job( + status = await self.coordinator.hass.async_add_executor_job( self.coordinator.vehicle.lock ) + _LOGGER.debug(status) await self.coordinator.async_request_refresh() + _LOGGER.debug("Locking here") self._attr_is_locking = False self.async_write_ha_state() async def async_unlock(self, **kwargs): """Unlocks the vehicle.""" + _LOGGER.debug("Unlocking %s", self.coordinator.vin) self._attr_is_unlocking = True self.async_write_ha_state() - _LOGGER.debug("Unlocking %s", self.coordinator.vin) - await self.coordinator.hass.async_add_executor_job( + status = await self.coordinator.hass.async_add_executor_job( self.coordinator.vehicle.unlock ) + _LOGGER.debug(status) await self.coordinator.async_request_refresh() self._attr_is_unlocking = False self.async_write_ha_state() @@ -58,9 +64,9 @@ async def async_unlock(self, **kwargs): @property def is_locked(self): """Determine if the lock is locked.""" - if self.coordinator.data is None or self.coordinator.data["lockStatus"] is None: + if self.data is None or self.data["doorLockStatus"] is None: return None - return self.coordinator.data["lockStatus"]["value"] == "LOCKED" + return self.data["doorLockStatus"][0]["value"] == "LOCKED" @property def icon(self): diff --git a/custom_components/fordpass/switch.py b/custom_components/fordpass/switch.py index f8ded13..4fa2e2b 100644 --- a/custom_components/fordpass/switch.py +++ b/custom_components/fordpass/switch.py @@ -15,17 +15,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # switches = [Switch(entry)] # async_add_entities(switches, False) - for key in SWITCHES: - switch = Switch(entry, key, config_entry.options) + for key, value in SWITCHES.items(): + sw = Switch(entry, key, config_entry.options) # Only add guard entity if supported by the car if key == "guardmode": - if "guardstatus" in switch.coordinator.data: - if switch.coordinator.data["guardstatus"]["returnCode"] == 200: - async_add_entities([switch], False) + if "guardstatus" in sw.coordinator.data: + if sw.coordinator.data["guardstatus"]["returnCode"] == 200: + async_add_entities([sw], False) else: _LOGGER.debug("Guard mode not supported on this vehicle") else: - async_add_entities([switch], False) + async_add_entities([sw], False) class Switch(FordPassEntity, SwitchEntity): @@ -33,15 +33,10 @@ class Switch(FordPassEntity, SwitchEntity): def __init__(self, coordinator, switch, options): """Initialize""" - super().__init__( - device_id="fordpass_doorlock", - name="fordpass_doorlock", - coordinator=coordinator, - ) - self._device_id = "fordpass_" + switch self.switch = switch self.coordinator = coordinator + self.data = coordinator.data["metrics"] # Required for HA 2022.7 self.coordinator_context = object() @@ -88,10 +83,11 @@ def is_on(self): """Check status of switch""" if self.switch == "ignition": if ( - self.coordinator.data is None or self.coordinator.data["remoteStartStatus"] is None + self.data is None or self.data["ignitionStatus"] is None ): return None - return self.coordinator.data["remoteStartStatus"]["value"] + if self.data["ignitionStatus"]["value"] == "OFF": + return False if self.switch == "guardmode": # Need to find the correct response for enabled vs disabled so this may be spotty at the moment guardstatus = self.coordinator.data["guardstatus"] From 90aeee3c497cc3f5249d7c4b673bcd807ebf51ea Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 19:47:05 +1000 Subject: [PATCH 09/63] code check 1 --- custom_components/fordpass/const.py | 28 ++++++++++---------- custom_components/fordpass/device_tracker.py | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index 0a10722..1499772 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -41,20 +41,20 @@ "doorStatus": {"icon": "mdi:car-door"}, "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, - #"elVeh": {"icon": "mdi:ev-station"}, - #"deepSleepInProgress": { - # "icon": "mdi:power-sleep", + # "elVeh": {"icon": "mdi:ev-station"}, + # "deepSleepInProgress": { + # "icon": "mdi:power-sleep", # "name": "Deep Sleep Mode Active", - #}, - #"firmwareUpgInProgress": { - "icon": "mdi:one-up", - ## "name": "Firmware Update In Progress", - #}, - #"remoteStartStatus": {"icon": "mdi:remote"}, - #"zoneLighting": {"icon": "mdi:spotlight-beam"}, + # }, + # "firmwareUpgInProgress": { + # "icon": "mdi:one-up", + # "name": "Firmware Update In Progress", + # }, + # "remoteStartStatus": {"icon": "mdi:remote"}, + # "zoneLighting": {"icon": "mdi:spotlight-beam"}, "messages": {"icon": "mdi:message-text"}, - #"dieselSystemStatus": {"icon": "mdi:smoking-pipe"}, - #"exhaustFluidLevel": {"icon": "mdi:barrel"} + # "dieselSystemStatus": {"icon": "mdi:smoking-pipe"}, + # "exhaustFluidLevel": {"icon": "mdi:barrel"} } SWITCHES = {"ignition": {"icon": "hass:power"}, "guardmode": {"icon": "mdi:shield-key"}} @@ -73,6 +73,6 @@ } SWITCHES = { - "ignition": {"icon": "hass:power"}, - #"guardmode": {"icon": "mdi:shield-key"} + "ignition": {"icon": "hass:power"}, + # "guardmode": {"icon": "mdi:shield-key"} } diff --git a/custom_components/fordpass/device_tracker.py b/custom_components/fordpass/device_tracker.py index 7c08346..7240f6d 100644 --- a/custom_components/fordpass/device_tracker.py +++ b/custom_components/fordpass/device_tracker.py @@ -15,7 +15,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entry = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] # Added a check to see if the car supports GPS - if entry.data["metrics"]["position"] != None: + if entry.data["metrics"]["position"] is not None: async_add_entities([CarTracker(entry, "gps")], True) else: _LOGGER.debug("Vehicle does not support GPS") From 9d3ca2d3a59bd5e9efad0d668b08138f6978deb1 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 19:52:23 +1000 Subject: [PATCH 10/63] updated fordpass_new code --- custom_components/fordpass/fordpass_new.py | 151 ++++++++++++++++----- custom_components/fordpass/lock.py | 3 - 2 files changed, 114 insertions(+), 40 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 22c3031..e1af469 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -32,9 +32,13 @@ "North America & Canada": "71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592", } +newApi = True + BASE_URL = "https://usapi.cv.ford.com/api" GUARD_URL = "https://api.mps.ford.com/api" SSO_URL = "https://sso.ci.ford.com" +autonomicUrl = "https://api.autonomic.ai/v1" +autonomicAccountUrl = "https://accounts.autonomic.ai/v1" session = requests.Session() @@ -55,6 +59,7 @@ def __init__( self.expires = None self.expires_at = None self.refresh_token = None + self.auto_token = None retry = Retry(connect=3, backoff_factor=0.5) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) @@ -189,7 +194,12 @@ def auth(self): session.cookies.clear() return True response.raise_for_status() - return False + # Code to get Auto token + if self.getAutoToken(): + return True + else: + return False + def refresh_token_func(self, token): """Refresh token if still valid""" @@ -271,6 +281,38 @@ def clear_token(self): if os.path.isfile(self.token_location): os.remove(self.token_location) + def getAutoToken(self): + """Get token from new autonomic API""" + _LOGGER.debug("Getting Auto Token") + headers={ + "accept": "*/*", + "content-type": "application/x-www-form-urlencoded" + } + + data = { + "subject_token": self.token, + "subject_issuer": "fordpass", + "client_id": "fordpass-prod", + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + + } + + + r = session.post( + f"{autonomicAccountUrl}/auth/oidc/token", + data=data, + headers=headers + ) + + if r.status_code == 200: + result = r.json() + _LOGGER.debug(r.status_code) + _LOGGER.debug(r.text) + self.auto_token = result["access_token"] + return True + + def status(self): """Get Vehicle status from API""" @@ -284,37 +326,50 @@ def status(self): "Application-Id": self.region, } - response = session.get( - f"{BASE_URL}/vehicles/v5/{self.vin}/status", params=params, headers=headers - ) - if response.status_code == 200: - result = response.json() - if result["status"] == 402: - response.raise_for_status() - return result["vehiclestatus"] - if response.status_code == 401: - _LOGGER.debug("401 with status request: start token refresh") - data = {} - data["access_token"] = self.token - data["refresh_token"] = self.refresh_token - data["expiry_date"] = self.expires_at - self.refresh_token_func(data) - self.__acquire_token() + if newApi: headers = { **apiHeaders, - "auth-token": self.token, + "authorization": f"Bearer {self.auto_token2}", "Application-Id": self.region, - } + } + r = session.get( + f"{autonomicUrl}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers + ) + if r.status_code == 200: + #_LOGGER.debug(r.text) + result = r.json() + return result + else: response = session.get( - f"{BASE_URL}/vehicles/v5/{self.vin}/status", - params=params, - headers=headers, + f"{BASE_URL}/vehicles/v5/{self.vin}/status", params=params, headers=headers ) if response.status_code == 200: result = response.json() - return result["vehiclestatus"] - response.raise_for_status() - return None + if result["status"] == 402: + response.raise_for_status() + return result["vehiclestatus"] + if response.status_code == 401: + _LOGGER.debug("401 with status request: start token refresh") + data = {} + data["access_token"] = self.token + data["refresh_token"] = self.refresh_token + data["expiry_date"] = self.expires_at + self.refresh_token_func(data) + self.__acquire_token() + headers = { + **apiHeaders, + "auth-token": self.token, + "Application-Id": self.region, + } + response = session.get( + f"{BASE_URL}/vehicles/v5/{self.vin}/status", + params=params, + headers=headers, + ) + if response.status_code == 200: + result = response.json() + return result["vehiclestatus"] + response.raise_for_status() def messages(self): """Get Vehicle messages from API""" @@ -393,33 +448,25 @@ def start(self): """ Issue a start command to the engine """ - return self.__request_and_poll( - "PUT", f"{BASE_URL}/vehicles/v5/{self.vin}/engine/start" - ) + return self.__requestAndPollCommand("remoteStart") def stop(self): """ Issue a stop command to the engine """ - return self.__request_and_poll( - "DELETE", f"{BASE_URL}/vehicles/v5/{self.vin}/engine/start" - ) + return self.__requestAndPollCommand("cancelRemoteStart") def lock(self): """ Issue a lock command to the doors """ - return self.__request_and_poll( - "PUT", f"{BASE_URL}/vehicles/v5/{self.vin}/doors/lock" - ) + return self.__requestAndPollCommand("unlock") def unlock(self): """ Issue an unlock command to the doors """ - return self.__request_and_poll( - "DELETE", f"{BASE_URL}/vehicles/v5/{self.vin}/doors/lock" - ) + return self.__requestAndPollCommand("unlock") def enable_guard(self): """ @@ -486,7 +533,37 @@ def __poll_status(self, url, command_id): return True _LOGGER.debug("Command failed") return False + + def __requestAndPollCommand(self, command, vin=None): + """Send command to the new Command endpoint""" + headers = { + **apiHeaders, + "Application-Id": self.region, + "authorization": f"Bearer {self.auto_token2}" + } + + data = { + "properties": {}, + "tags": {}, + "type": command, + "wakeUp": True + } + if vin is None: + r = session.post( + f"{autonomicUrl}command/vehicles/{self.vin}/commands", + data=json.dumps(data), + headers = headers + ) + else: + r = session.post( + f"{autonomicUrl}command/vehicles/{vin}/commands", + data=json.dumps(data), + headers = headers + ) + _LOGGER.debug("Testing command") + _LOGGER.debug(r.status_code) + _LOGGER.debug(r.text) def __request_and_poll(self, method, url): """Poll API until status code is reached, locking + remote start""" self.__acquire_token() diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index 6eb7223..3a1b67b 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -20,15 +20,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): _LOGGER.debug("Ford model doesn't support remote locking") - class Lock(FordPassEntity, LockEntity): """Defines the vehicle's lock.""" - def __init__(self, coordinator): """Initialize.""" self._device_id="fordpass_doorlock" self.coordinator = coordinator - self.data = coordinator.data["metrics"] # Required for HA 2022.7 From 1f17c1f615c48bb9f984c0ff8ac4e1b749298f88 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:03:10 +1000 Subject: [PATCH 11/63] code fixes --- custom_components/fordpass/fordpass_new.py | 14 +++++++------- custom_components/fordpass/lock.py | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index e1af469..521daa3 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -37,8 +37,8 @@ BASE_URL = "https://usapi.cv.ford.com/api" GUARD_URL = "https://api.mps.ford.com/api" SSO_URL = "https://sso.ci.ford.com" -autonomicUrl = "https://api.autonomic.ai/v1" -autonomicAccountUrl = "https://accounts.autonomic.ai/v1" +AUTONOMIC_URL = "https://api.autonomic.ai/v1" +AUTONOMIC_ACCOUNT_URL = "https://accounts.autonomic.ai/v1" session = requests.Session() @@ -300,7 +300,7 @@ def getAutoToken(self): r = session.post( - f"{autonomicAccountUrl}/auth/oidc/token", + f"{AUTONOMIC_ACCOUNT_URL}/auth/oidc/token", data=data, headers=headers ) @@ -333,7 +333,7 @@ def status(self): "Application-Id": self.region, } r = session.get( - f"{autonomicUrl}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers + f"{AUTONOMIC_URL}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers ) if r.status_code == 200: #_LOGGER.debug(r.text) @@ -539,7 +539,7 @@ def __requestAndPollCommand(self, command, vin=None): headers = { **apiHeaders, "Application-Id": self.region, - "authorization": f"Bearer {self.auto_token2}" + "authorization": f"Bearer {self.auto_token}" } data = { @@ -550,13 +550,13 @@ def __requestAndPollCommand(self, command, vin=None): } if vin is None: r = session.post( - f"{autonomicUrl}command/vehicles/{self.vin}/commands", + f"{AUTONOMIC_URL}command/vehicles/{self.vin}/commands", data=json.dumps(data), headers = headers ) else: r = session.post( - f"{autonomicUrl}command/vehicles/{vin}/commands", + f"{AUTONOMIC_URL}command/vehicles/{vin}/commands", data=json.dumps(data), headers = headers ) diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index 3a1b67b..6746b9a 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -69,3 +69,8 @@ def is_locked(self): def icon(self): """Return MDI Icon""" return "mdi:car-door-lock" + + @property + def name(self): + """Return Name""" + return "fordpass_doorlock" From 652952d77d0c2aefb61aa550e989c76de6304f9a Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:05:23 +1000 Subject: [PATCH 12/63] code refactor 1 --- custom_components/fordpass/fordpass_new.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 521daa3..3cb9dba 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -200,7 +200,6 @@ def auth(self): else: return False - def refresh_token_func(self, token): """Refresh token if still valid""" data = {"refresh_token": token["refresh_token"]} @@ -284,7 +283,7 @@ def clear_token(self): def getAutoToken(self): """Get token from new autonomic API""" _LOGGER.debug("Getting Auto Token") - headers={ + headers = { "accept": "*/*", "content-type": "application/x-www-form-urlencoded" } @@ -298,7 +297,6 @@ def getAutoToken(self): } - r = session.post( f"{AUTONOMIC_ACCOUNT_URL}/auth/oidc/token", data=data, @@ -311,7 +309,7 @@ def getAutoToken(self): _LOGGER.debug(r.text) self.auto_token = result["access_token"] return True - + def status(self): """Get Vehicle status from API""" @@ -334,7 +332,7 @@ def status(self): } r = session.get( f"{AUTONOMIC_URL}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers - ) + ) if r.status_code == 200: #_LOGGER.debug(r.text) result = r.json() From 8ddfce1f0856bf38e64373b0ab14eededeed096a Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:08:34 +1000 Subject: [PATCH 13/63] code refactor 2 --- custom_components/fordpass/fordpass_new.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 3cb9dba..8dbbd16 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -310,7 +310,6 @@ def getAutoToken(self): self.auto_token = result["access_token"] return True - def status(self): """Get Vehicle status from API""" @@ -329,12 +328,12 @@ def status(self): **apiHeaders, "authorization": f"Bearer {self.auto_token2}", "Application-Id": self.region, - } + } r = session.get( f"{AUTONOMIC_URL}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers - ) + ) if r.status_code == 200: - #_LOGGER.debug(r.text) + # _LOGGER.debug(r.text) result = r.json() return result else: @@ -531,15 +530,15 @@ def __poll_status(self, url, command_id): return True _LOGGER.debug("Command failed") return False - + def __requestAndPollCommand(self, command, vin=None): """Send command to the new Command endpoint""" headers = { **apiHeaders, "Application-Id": self.region, "authorization": f"Bearer {self.auto_token}" - } - + } + data = { "properties": {}, "tags": {}, @@ -550,18 +549,19 @@ def __requestAndPollCommand(self, command, vin=None): r = session.post( f"{AUTONOMIC_URL}command/vehicles/{self.vin}/commands", data=json.dumps(data), - headers = headers + headers=headers ) else: r = session.post( f"{AUTONOMIC_URL}command/vehicles/{vin}/commands", data=json.dumps(data), - headers = headers + headers=headers ) _LOGGER.debug("Testing command") _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) + def __request_and_poll(self, method, url): """Poll API until status code is reached, locking + remote start""" self.__acquire_token() From c3edafc299d71300c35327d3b1eb56f4b25565d7 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:10:13 +1000 Subject: [PATCH 14/63] refactor 3 --- custom_components/fordpass/const.py | 6 +++--- custom_components/fordpass/fordpass_new.py | 2 +- custom_components/fordpass/lock.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index 1499772..f03b1a6 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -73,6 +73,6 @@ } SWITCHES = { - "ignition": {"icon": "hass:power"}, - # "guardmode": {"icon": "mdi:shield-key"} - } + "ignition": {"icon": "hass:power"}, + # "guardmode": {"icon": "mdi:shield-key"} +} diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 8dbbd16..7dd9d5f 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -561,7 +561,7 @@ def __requestAndPollCommand(self, command, vin=None): _LOGGER.debug("Testing command") _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) - + def __request_and_poll(self, method, url): """Poll API until status code is reached, locking + remote start""" self.__acquire_token() diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index 6746b9a..c6f9d1d 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -24,7 +24,7 @@ class Lock(FordPassEntity, LockEntity): """Defines the vehicle's lock.""" def __init__(self, coordinator): """Initialize.""" - self._device_id="fordpass_doorlock" + self._device_id = "fordpass_doorlock" self.coordinator = coordinator self.data = coordinator.data["metrics"] @@ -69,7 +69,7 @@ def is_locked(self): def icon(self): """Return MDI Icon""" return "mdi:car-door-lock" - + @property def name(self): """Return Name""" From f67f8b6cc5b813b05d0a4152b3b8320f6401efeb Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:13:46 +1000 Subject: [PATCH 15/63] refactor 4 --- custom_components/fordpass/device_tracker.py | 2 ++ custom_components/fordpass/fordpass_new.py | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/custom_components/fordpass/device_tracker.py b/custom_components/fordpass/device_tracker.py index 7240f6d..c675c7d 100644 --- a/custom_components/fordpass/device_tracker.py +++ b/custom_components/fordpass/device_tracker.py @@ -34,10 +34,12 @@ def __init__(self, coordinator, sensor): @property def latitude(self): + """Return latitude""" return float(self.data["position"]["value"]["location"]["lat"]) @property def longitude(self): + """Return longtitude""" return float(self.data["position"]["value"]["location"]["lon"]) @property diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 7dd9d5f..219d1ba 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -309,6 +309,7 @@ def getAutoToken(self): _LOGGER.debug(r.text) self.auto_token = result["access_token"] return True + return False def status(self): """Get Vehicle status from API""" @@ -326,7 +327,7 @@ def status(self): if newApi: headers = { **apiHeaders, - "authorization": f"Bearer {self.auto_token2}", + "authorization": f"Bearer {self.auto_token}", "Application-Id": self.region, } r = session.get( @@ -445,25 +446,25 @@ def start(self): """ Issue a start command to the engine """ - return self.__requestAndPollCommand("remoteStart") + return self.__request_and_poll_command("remoteStart") def stop(self): """ Issue a stop command to the engine """ - return self.__requestAndPollCommand("cancelRemoteStart") + return self.__request_and_poll_command("cancelRemoteStart") def lock(self): """ Issue a lock command to the doors """ - return self.__requestAndPollCommand("unlock") + return self.__request_and_poll_command("unlock") def unlock(self): """ Issue an unlock command to the doors """ - return self.__requestAndPollCommand("unlock") + return self.__request_and_poll_command("unlock") def enable_guard(self): """ @@ -531,7 +532,7 @@ def __poll_status(self, url, command_id): _LOGGER.debug("Command failed") return False - def __requestAndPollCommand(self, command, vin=None): + def __request_and_poll_command(self, command, vin=None): """Send command to the new Command endpoint""" headers = { **apiHeaders, From dd05ea05047bceba281bf2d824323f04dd42d1a3 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:18:31 +1000 Subject: [PATCH 16/63] api snake case --- custom_components/fordpass/fordpass_new.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 219d1ba..3e23411 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -32,7 +32,7 @@ "North America & Canada": "71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592", } -newApi = True +NEW_API = True BASE_URL = "https://usapi.cv.ford.com/api" GUARD_URL = "https://api.mps.ford.com/api" @@ -197,8 +197,7 @@ def auth(self): # Code to get Auto token if self.getAutoToken(): return True - else: - return False + return False def refresh_token_func(self, token): """Refresh token if still valid""" @@ -324,7 +323,7 @@ def status(self): "Application-Id": self.region, } - if newApi: + if NEW_API: headers = { **apiHeaders, "authorization": f"Bearer {self.auto_token}", @@ -419,7 +418,7 @@ def vehicles(self): _LOGGER.debug(result) return result - _LOGGER.debug(response.text) + # _LOGGER.debug(response.text) response.raise_for_status() return None From 2447315f958fc1bc8ec5ab40d7adad849b9f6b67 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:23:36 +1000 Subject: [PATCH 17/63] Updated readme --- custom_components/fordpass/fordpass_new.py | 4 ++-- info.md | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 3e23411..4f3a247 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -195,7 +195,7 @@ def auth(self): return True response.raise_for_status() # Code to get Auto token - if self.getAutoToken(): + if self.get_auto_token(): return True return False @@ -279,7 +279,7 @@ def clear_token(self): if os.path.isfile(self.token_location): os.remove(self.token_location) - def getAutoToken(self): + def get_auto_token(self): """Get token from new autonomic API""" _LOGGER.debug("Getting Auto Token") headers = { diff --git a/info.md b/info.md index adc5f13..ae0048a 100644 --- a/info.md +++ b/info.md @@ -1,4 +1,12 @@ ## **Changelog** +### Version 1.53 +*Warning this version is only a BETA and has a lot broken due to the massive API changes from Ford use at your own risk or wait a week!* +- Updated vehicle endpoint to use new Autonomics API +- Added secondary Autonomic token +- Remapped commands to use new "command" API endpoint +- Remapped existing sensors to new json variables (Some are missinge) + +There is a LOT more coming soon as the new API exposes an excessive amount of information including speed, pedal position, crash sensors and way more. ### Version 1.52 - Update for discontinued API endpoints (Update, lock, remote start) ### Version 1.51 From 81f2f5b8caa2dfc8397a07a92fbb8481b622bf14 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:34:28 +1000 Subject: [PATCH 18/63] Fix missing sensor data from dev env --- custom_components/fordpass/fordpass_new.py | 1 + custom_components/fordpass/sensor.py | 211 +++++++++++---------- 2 files changed, 107 insertions(+), 105 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 4f3a247..78bb251 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -249,6 +249,7 @@ def __acquire_token(self): self.auth() else: _LOGGER.debug("Token is valid, continuing") + self.get_auto_token() def write_token(self, token): """Save token to file for reuse""" diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 3a08c94..18a42d9 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -70,79 +70,87 @@ def get_value(self, ftype): if self.fordoptions[CONF_DISTANCE_UNIT] is not None: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": if DISTANCE_CONVERSION_DISABLED in self.fordoptions and self.fordoptions[DISTANCE_CONVERSION_DISABLED] is True: - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] return round( - float(self.coordinator.data[self.sensor]["value"]) / 1.60934 + float(self.data[self.sensor]["value"]) / 1.60934 ) - return self.coordinator.data[self.sensor]["value"] - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "fuel": - if self.coordinator.data[self.sensor] is None: + if "fuelLevel" in self.data: + if self.data["fuelLevel"] is None: + return None + return round(self.data["fuelLevel"]["value"]) + elif "xevBatteryStateOfCharge": + return round(self.data["xevBatteryStateOfCharge"]["value"]) + else: return None - return round(self.coordinator.data[self.sensor]["fuelLevel"]) if self.sensor == "battery": - return self.coordinator.data[self.sensor]["batteryHealth"]["value"] + return self.data["batteryStateOfCharge"]["value"] if self.sensor == "oil": - return self.coordinator.data[self.sensor]["oilLife"] + return self.data["oilLifeRemaining"]["value"] if self.sensor == "tirePressure": - return self.coordinator.data[self.sensor]["value"] + return self.data["tirePressureSystemStatus"][0]["value"] if self.sensor == "gps": - if self.coordinator.data[self.sensor] is None: + if self.data["position"] is None: return "Unsupported" - return self.coordinator.data[self.sensor]["gpsState"] + return self.data["position"]["value"] if self.sensor == "alarm": - return self.coordinator.data[self.sensor]["value"] + return self.data["alarmStatus"]["value"] if self.sensor == "ignitionStatus": - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "firmwareUpgInProgress": - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "deepSleepInProgress": - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "doorStatus": - for key, value in self.coordinator.data[self.sensor].items(): + for value in self.data["doorStatus"]: if value["value"] == "Invalid": continue - if value["value"] != "Closed": + if value["value"] != "CLOSED": return "Open" return "Closed" if self.sensor == "windowPosition": - if self.coordinator.data[self.sensor] is None: - return "Unsupported" - status = "Closed" - for key, value in self.coordinator.data[self.sensor].items(): - if "open" in value["value"].lower(): - status = "Open" - return status + if "windowStatus" in self.data: + if self.data["windowStatus"] is None: + return "Unsupported" + status = "Closed" + for window in self.data["windowStatus"]: + windowrange = window["value"]["doubleRange"] + if windowrange["lowerBound"] != 0.0 and windowrange["upperBound"] != 0.0: + status = "Open" + return status + return "Unsupported" if self.sensor == "lastRefresh": return dt.as_local( datetime.strptime( - self.coordinator.data[self.sensor] + "+0000", "%m-%d-%Y %H:%M:%S%z" + self.coordinator.data["updateTime"] , "%Y-%m-%dT%H:%M:%S.%fz" ) ) if self.sensor == "elVeh": - if self.coordinator.data["elVehDTE"] is not None: + if self.data["elVehDTE"] is not None: if self.fordoptions[CONF_DISTANCE_UNIT] is not None: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": return round( - float(self.coordinator.data["elVehDTE"]["value"]) / 1.60934 + float(self.data["elVehDTE"]["value"]) / 1.60934 ) - return float(self.coordinator.data["elVehDTE"]["value"]) - return float(self.coordinator.data["elVehDTE"]["value"]) + return float(self.data["elVehDTE"]["value"]) + return float(self.data["elVehDTE"]["value"]) return "Unsupported" if self.sensor == "zoneLighting": - if "zoneLighting" not in self.coordinator.data: + if "zoneLighting" not in self.data: return "Unsupported" if ( - self.coordinator.data["zoneLighting"] is not None and self.coordinator.data["zoneLighting"]["activationData"] is not None + self.data["zoneLighting"] is not None and self.data["zoneLighting"]["activationData"] is not None ): - return self.coordinator.data["zoneLighting"]["activationData"][ + return self.data["zoneLighting"]["activationData"][ "value" ] return "Unsupported" if self.sensor == "remoteStartStatus": - if self.coordinator.data["remoteStartStatus"] is None: + if self.data["remoteStartStatus"] is None: return None - if self.coordinator.data["remoteStartStatus"]["value"] == 1: + if self.data["remoteStartStatus"]["value"] == 1: return "Active" return "Inactive" if self.sensor == "messages": @@ -150,12 +158,12 @@ def get_value(self, ftype): return None return len(self.coordinator.data["messages"]) if self.sensor == "dieselSystemStatus": - if self.coordinator.data["dieselSystemStatus"]["filterRegenerationStatus"] is not None: - return self.coordinator.data["dieselSystemStatus"]["filterRegenerationStatus"] + if self.data["dieselSystemStatus"]["filterRegenerationStatus"] is not None: + return self.data["dieselSystemStatus"]["filterRegenerationStatus"] return "Not Supported" if self.sensor == "exhaustFluidLevel": - if "value" in self.coordinator.data["dieselSystemStatus"]["exhaustFluidLevel"]: - return self.coordinator.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] + if "value" in self.data["dieselSystemStatus"]["exhaustFluidLevel"]: + return self.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] return "Not Supported" return None if ftype == "measurement": @@ -202,26 +210,26 @@ def get_value(self, ftype): return None if ftype == "attribute": if self.sensor == "odometer": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "fuel": - if self.coordinator.data[self.sensor] is None: + if self.data["fuelRange"] is None: return None if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - self.coordinator.data["fuel"]["distanceToEmpty"] = round( - float(self.coordinator.data["fuel"]["distanceToEmpty"]) / 1.60934 + self.data["fuelRange"]["value"] = round( + float(self.data["fuelRange"]["value"]) / 1.60934 ) - return self.coordinator.data[self.sensor].items() + return {"fuelRange": self.data["fuelRange"]["value"]} if self.sensor == "battery": return { - "Battery Voltage": self.coordinator.data[self.sensor][ - "batteryStatusActual" - ]["value"] + "Battery Voltage": self.data["batteryVoltage"]["value"] } if self.sensor == "oil": - return self.coordinator.data[self.sensor].items() + return self.data["oilLifeRemaining"].items() if self.sensor == "tirePressure": - if self.coordinator.data["TPMS"] is not None: + if self.data["tirePressure"] is not None: + _LOGGER.debug(self.fordoptions[CONF_PRESSURE_UNIT]) if self.fordoptions[CONF_PRESSURE_UNIT] == "PSI": + _LOGGER.debug("PSIIIII") sval = 0.1450377377 rval = 1 decimal = 0 @@ -234,161 +242,154 @@ def get_value(self, ftype): rval = 6.8947572932 decimal = 0 else: + _LOGGER.debug("HITT") sval = 1 rval = 1 decimal = 0 tirepress = {} - for key, value in self.coordinator.data["TPMS"].items(): - if "TirePressure" in key and value is not None and value != '': - if "recommended" in key: - tirepress[key] = round(float(value["value"]) * rval, decimal) - else: - tirepress[key] = round(float(value["value"]) * sval, decimal) + for value in self.data["tirePressure"]: + #if "recommended" in key: + # tirepress[key] = round(float(value["value"]) * rval, decimal) + #else: + tirepress[value["vehicleWheel"]] = round(float(value["value"]) * sval, decimal) return tirepress return None if self.sensor == "gps": - if self.coordinator.data[self.sensor] is None: + if self.data["position"] is None: return None - return self.coordinator.data[self.sensor].items() + return self.data["position"].items() if self.sensor == "alarm": - return self.coordinator.data[self.sensor].items() + return self.data["alarmStatus"].items() if self.sensor == "ignitionStatus": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "firmwareUpgInProgress": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "deepSleepInProgress": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "doorStatus": doors = {} - for key, value in self.coordinator.data[self.sensor].items(): - doors[key] = value["value"] + for value in self.data[self.sensor]: + doors[value["vehicleDoor"]] = value["value"] return doors if self.sensor == "windowPosition": - if self.coordinator.data[self.sensor] is None: + if "windowStatus" not in self.data: return None windows = {} - for key, value in self.coordinator.data[self.sensor].items(): - windows[key] = value["value"] - if "open" in value["value"].lower(): - if "btwn" in value["value"].lower(): - windows[key] = "Open-Partial" - else: - windows[key] = "Open" - elif "closed" in value["value"].lower(): - windows[key] = "Closed" + for window in self.data["windowStatus"]: + windows[window["vehicleWindow"]] = window return windows if self.sensor == "lastRefresh": return None if self.sensor == "elVeh": - if self.coordinator.data["elVehDTE"] is None: + if self.data["elVehDTE"] is None: return None elecs = {} if ( - self.coordinator.data["elVehDTE"] is not None and self.coordinator.data["elVehDTE"]["value"] is not None + self.data["elVehDTE"] is not None and self.data["elVehDTE"]["value"] is not None ): - elecs["elVehDTE"] = self.coordinator.data["elVehDTE"]["value"] + elecs["elVehDTE"] = self.data["elVehDTE"]["value"] if ( - self.coordinator.data["plugStatus"] is not None and self.coordinator.data["plugStatus"]["value"] is not None + self.data["plugStatus"] is not None and self.data["plugStatus"]["value"] is not None ): - elecs["Plug Status"] = self.coordinator.data["plugStatus"][ + elecs["Plug Status"] = self.data["plugStatus"][ "value" ] if ( - self.coordinator.data["chargingStatus"] is not None and self.coordinator.data["chargingStatus"]["value"] is not None + self.data["chargingStatus"] is not None and self.data["chargingStatus"]["value"] is not None ): - elecs["Charging Status"] = self.coordinator.data[ + elecs["Charging Status"] = self.data[ "chargingStatus" ]["value"] if ( - self.coordinator.data["chargeStartTime"] is not None and self.coordinator.data["chargeStartTime"]["value"] is not None + self.data["chargeStartTime"] is not None and self.data["chargeStartTime"]["value"] is not None ): - elecs["Charge Start Time"] = self.coordinator.data[ + elecs["Charge Start Time"] = self.data[ "chargeStartTime" ]["value"] if ( - self.coordinator.data["chargeEndTime"] is not None and self.coordinator.data["chargeEndTime"]["value"] is not None + self.data["chargeEndTime"] is not None and self.data["chargeEndTime"]["value"] is not None ): - elecs["Charge End Time"] = self.coordinator.data[ + elecs["Charge End Time"] = self.data[ "chargeEndTime" ]["value"] if ( - self.coordinator.data["batteryFillLevel"] is not None and self.coordinator.data["batteryFillLevel"]["value"] is not None + self.data["batteryFillLevel"] is not None and self.data["batteryFillLevel"]["value"] is not None ): - elecs["Battery Fill Level"] = int(self.coordinator.data[ + elecs["Battery Fill Level"] = int(self.data[ "batteryFillLevel" ]["value"]) if ( - self.coordinator.data["chargerPowertype"] is not None and self.coordinator.data["chargerPowertype"]["value"] is not None + self.data["chargerPowertype"] is not None and self.data["chargerPowertype"]["value"] is not None ): - elecs["Charger Power Type"] = self.coordinator.data[ + elecs["Charger Power Type"] = self.data[ "chargerPowertype" ]["value"] if ( - self.coordinator.data["batteryChargeStatus"] is not None and self.coordinator.data["batteryChargeStatus"]["value"] is not None + self.data["batteryChargeStatus"] is not None and self.data["batteryChargeStatus"]["value"] is not None ): - elecs["Battery Charge Status"] = self.coordinator.data[ + elecs["Battery Charge Status"] = self.data[ "batteryChargeStatus" ]["value"] if ( - self.coordinator.data["batteryPerfStatus"] is not None and self.coordinator.data["batteryPerfStatus"]["value"] is not None + self.data["batteryPerfStatus"] is not None and self.data["batteryPerfStatus"]["value"] is not None ): - elecs["Battery Performance Status"] = self.coordinator.data[ + elecs["Battery Performance Status"] = self.data[ "batteryPerfStatus" ]["value"] return elecs if self.sensor == "zoneLighting": - if "zoneLighting" not in self.coordinator.data: + if "zoneLighting" not in self.data: return None if ( - self.coordinator.data[self.sensor] is not None and self.coordinator.data[self.sensor]["zoneStatusData"] is not None + self.data[self.sensor] is not None and self.data[self.sensor]["zoneStatusData"] is not None ): zone = {} - if self.coordinator.data[self.sensor]["zoneStatusData"] is not None: - for key, value in self.coordinator.data[self.sensor][ + if self.data[self.sensor]["zoneStatusData"] is not None: + for key, value in self.data[self.sensor][ "zoneStatusData" ].items(): zone["zone_" + key] = value["value"] if ( - self.coordinator.data[self.sensor]["lightSwitchStatusData"] + self.data[self.sensor]["lightSwitchStatusData"] is not None ): - for key, value in self.coordinator.data[self.sensor][ + for key, value in self.data[self.sensor][ "lightSwitchStatusData" ].items(): if value is not None: zone[key] = value["value"] if ( - self.coordinator.data[self.sensor]["zoneLightingFaultStatus"] + self.data[self.sensor]["zoneLightingFaultStatus"] is not None ): - zone["zoneLightingFaultStatus"] = self.coordinator.data[ + zone["zoneLightingFaultStatus"] = self.data[ self.sensor ]["zoneLightingFaultStatus"]["value"] if ( - self.coordinator.data[self.sensor][ + self.data[self.sensor][ "zoneLightingShutDownWarning" ] is not None ): - zone["zoneLightingShutDownWarning"] = self.coordinator.data[ + zone["zoneLightingShutDownWarning"] = self.data[ self.sensor ]["zoneLightingShutDownWarning"]["value"] return zone return None if self.sensor == "remoteStartStatus": - if self.coordinator.data["remoteStart"] is None: + if self.data["remoteStart"] is None: return None - return self.coordinator.data["remoteStart"].items() + return self.data["remoteStart"].items() if self.sensor == "messages": if self.coordinator.data["messages"] is None: return None @@ -398,9 +399,9 @@ def get_value(self, ftype): messages[value["messageSubject"]] = value["createdDate"] return messages if self.sensor == "dieselSystemStatus": - return self.coordinator.data["dieselSystemStatus"] + return self.data["dieselSystemStatus"] if self.sensor == "exhaustFluidLevel": - return self.coordinator.data["dieselSystemStatus"] + return self.data["dieselSystemStatus"] return None return None From 5ebf8ed27f3906239472dda6faac6e5b217f9667 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 20:38:31 +1000 Subject: [PATCH 19/63] Fix missing data attribute --- custom_components/fordpass/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 18a42d9..04cf393 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -59,6 +59,7 @@ def __init__(self, coordinator, sensor, options): self.fordoptions = options self._attr = {} self.coordinator = coordinator + self.data = coordinator.data["metrics"] self._device_id = "fordpass_" + sensor # Required for HA 2022.7 self.coordinator_context = object() From d1437566cb56fca733288687bbe7693f047778f6 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 21:09:41 +1000 Subject: [PATCH 20/63] add electric vehicle data --- custom_components/fordpass/const.py | 2 +- custom_components/fordpass/sensor.py | 65 ++++++++++------------------ 2 files changed, 23 insertions(+), 44 deletions(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index f03b1a6..d622fd4 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -41,7 +41,7 @@ "doorStatus": {"icon": "mdi:car-door"}, "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, - # "elVeh": {"icon": "mdi:ev-station"}, + "elVeh": {"icon": "mdi:ev-station"}, # "deepSleepInProgress": { # "icon": "mdi:power-sleep", # "name": "Deep Sleep Mode Active", diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 04cf393..9a6c742 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -28,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if "zoneLighting" in sensor.coordinator.data: sensors.append(sensor) elif key == "elVeh": - if sensor.coordinator.data["elVehDTE"] is not None: + if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): @@ -87,9 +87,9 @@ def get_value(self, ftype): else: return None if self.sensor == "battery": - return self.data["batteryStateOfCharge"]["value"] + return round(self.data["batteryStateOfCharge"]["value"]) if self.sensor == "oil": - return self.data["oilLifeRemaining"]["value"] + return round(self.data["oilLifeRemaining"]["value"]) if self.sensor == "tirePressure": return self.data["tirePressureSystemStatus"][0]["value"] if self.sensor == "gps": @@ -129,14 +129,14 @@ def get_value(self, ftype): ) ) if self.sensor == "elVeh": - if self.data["elVehDTE"] is not None: + if "xevBatteryRange" in self.data: if self.fordoptions[CONF_DISTANCE_UNIT] is not None: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": return round( - float(self.data["elVehDTE"]["value"]) / 1.60934 + float(self.data["xevBatteryRange"]["value"]) / 1.60934 ) - return float(self.data["elVehDTE"]["value"]) - return float(self.data["elVehDTE"]["value"]) + return float(self.data["xevBatteryRange"]["value"]) + return float(self.data["xevBatteryRange"]["value"]) return "Unsupported" if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: @@ -175,9 +175,9 @@ def get_value(self, ftype): if self.sensor == "fuel": return "%" if self.sensor == "battery": - return None + return "%" if self.sensor == "oil": - return None + return "%" if self.sensor == "tirePressure": return None if self.sensor == "gps": @@ -282,67 +282,46 @@ def get_value(self, ftype): if self.sensor == "lastRefresh": return None if self.sensor == "elVeh": - if self.data["elVehDTE"] is None: + if self.data["xevBatteryCapacity"] is None: return None elecs = {} if ( - self.data["elVehDTE"] is not None and self.data["elVehDTE"]["value"] is not None + self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None ): - elecs["elVehDTE"] = self.data["elVehDTE"]["value"] + elecs["xevBatteryCapacity"] = self.data["xevBatteryCapacity"]["value"] if ( - self.data["plugStatus"] is not None and self.data["plugStatus"]["value"] is not None + self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None ): - elecs["Plug Status"] = self.data["plugStatus"][ + elecs["Plug Status"] = self.data["xevPlugChargerStatus"][ "value" ] if ( - self.data["chargingStatus"] is not None and self.data["chargingStatus"]["value"] is not None + self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None ): elecs["Charging Status"] = self.data[ - "chargingStatus" - ]["value"] - - if ( - self.data["chargeStartTime"] is not None and self.data["chargeStartTime"]["value"] is not None - ): - elecs["Charge Start Time"] = self.data[ - "chargeStartTime" + "xevBatteryChargeDisplayStatus" ]["value"] if ( - self.data["chargeEndTime"] is not None and self.data["chargeEndTime"]["value"] is not None - ): - elecs["Charge End Time"] = self.data[ - "chargeEndTime" - ]["value"] - - if ( - self.data["batteryFillLevel"] is not None and self.data["batteryFillLevel"]["value"] is not None - ): - elecs["Battery Fill Level"] = int(self.data[ - "batteryFillLevel" - ]["value"]) - - if ( - self.data["chargerPowertype"] is not None and self.data["chargerPowertype"]["value"] is not None + self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None ): elecs["Charger Power Type"] = self.data[ - "chargerPowertype" + "xevChargeStationPowerType" ]["value"] if ( - self.data["batteryChargeStatus"] is not None and self.data["batteryChargeStatus"]["value"] is not None + self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None ): elecs["Battery Charge Status"] = self.data[ - "batteryChargeStatus" + "xevChargeStationCommunicationStatus" ]["value"] if ( - self.data["batteryPerfStatus"] is not None and self.data["batteryPerfStatus"]["value"] is not None + self.data["xevBatteryPerformanceStatus"] is not None and self.data["xevBatteryPerformanceStatus"]["value"] is not None ): elecs["Battery Performance Status"] = self.data[ - "batteryPerfStatus" + "xevBatteryPerformanceStatus" ]["value"] return elecs From 0e3c1bfc62e5678b5d6b68c45ce0e53b36949cb3 Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 10 Oct 2023 21:14:25 +1000 Subject: [PATCH 21/63] Fix command url missing slash --- custom_components/fordpass/fordpass_new.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 78bb251..15cb8c7 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -548,13 +548,13 @@ def __request_and_poll_command(self, command, vin=None): } if vin is None: r = session.post( - f"{AUTONOMIC_URL}command/vehicles/{self.vin}/commands", + f"{AUTONOMIC_URL}/command/vehicles/{self.vin}/commands", data=json.dumps(data), headers=headers ) else: r = session.post( - f"{AUTONOMIC_URL}command/vehicles/{vin}/commands", + f"{AUTONOMIC_URL}/command/vehicles/{vin}/commands", data=json.dumps(data), headers=headers ) From 3c8430947a3b76d3bb1084dfb912946131497680 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 14:29:48 -0400 Subject: [PATCH 22/63] Added auto redaction for VIN,LAT,LONG,VehicleID. Added github username to easily share the results --- custom_components/fordpass/autonomicData.py | 66 ++++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/custom_components/fordpass/autonomicData.py b/custom_components/fordpass/autonomicData.py index 6dc2b03..7fde1cf 100644 --- a/custom_components/fordpass/autonomicData.py +++ b/custom_components/fordpass/autonomicData.py @@ -1,16 +1,22 @@ import json import requests +import sys +import os +from datetime import datetime + # Place this script in the /config/custom_components/fordpass folder on your HomeAssistant # Add the details below -# run from a terminal: python3 autonomicData.py -# It will create autonomicData.json in the same folder +# run from a terminal in the /config/custom_components/fordpass folder: python3 autonomicData.py +# It will create username_status_timestamp.json in the same folder +# Script will automatically redact your VIN, VehicleID, and Geolocation details (lat, long) +#GitHub username to append to the filename +gitHub_username = "" #FordPass VIN for vehicle to get data from fp_vin = "" #Name of the file for the user_fordpass_token.txt from the fordpass-ha integration -fp_token = "Hass_fordpass_token.txt" - +fp_token = "_fordpass_token.txt" def get_autonomic_token(ford_access_token): url = "https://accounts.autonomic.ai/v1/auth/oidc/token" @@ -50,6 +56,7 @@ def get_vehicle_status(vin, access_token): "Content-Type": "application/json", "accept": "*/*" } + redactionItems = ["lat", "lon", "vehicleId", "vin"] try: response = requests.post(url, headers=headers, json={}) @@ -57,6 +64,9 @@ def get_vehicle_status(vin, access_token): # Parse the JSON response vehicle_status_data = response.json() + + # Redact sensitive information + redact_json(vehicle_status_data, redactionItems) return vehicle_status_data except requests.exceptions.HTTPError as errh: @@ -68,18 +78,44 @@ def get_vehicle_status(vin, access_token): except requests.exceptions.RequestException as err: print(f"Something went wrong: {err}") -# Get FordPass token -with open(fp_token, 'r') as file: - fp_token_data = json.load(file) +def redact_json(data, redaction): + if isinstance(data, dict): + for key in list(data.keys()): + if key in redaction: + data[key] = 'REDACTED' + else: + redact_json(data[key], redaction) + elif isinstance(data, list): + for item in data: + redact_json(item, redaction) + +if __name__ == "__main__": + if gitHub_username == "": + gitHub_username = 'my' + if fp_vin == "": + print("Please enter your VIN into the python script") + sys.exit() + if fp_token == "": + print("Please enter your FordPass token text file name into the python script") + sys.exit() + elif os.path.isfile(fp_token) == False: + print(f"Error finding FordPass token text file: {fp_token}") + sys.exit() + + # Get FordPass token + with open(fp_token, 'r') as file: + fp_token_data = json.load(file) -ford_access_token = fp_token_data['access_token'] + ford_access_token = fp_token_data['access_token'] -# Exchange Fordpass token for Autonomic Token -autonomic_token = get_autonomic_token(ford_access_token) -vehicle_status = get_vehicle_status(fp_vin, autonomic_token["access_token"]) + # Exchange Fordpass token for Autonomic Token + autonomic_token = get_autonomic_token(ford_access_token) + vehicle_status = get_vehicle_status(fp_vin, autonomic_token["access_token"]) -# Write the updated JSON data to the file -with open('autonomicData.json', 'w') as file: - json.dump(vehicle_status, file, indent=4) -print("done") + current_datetime = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + fileName = f'/root/config/custom_components/fordpass/{gitHub_username}_status_{current_datetime}.json' + # Write the updated JSON data to the file + with open(fileName, 'w') as file: + json.dump(vehicle_status, file, indent=4) + print("done") From e099a7cfed2f3680f30b6079c13e4d2a7f46941e Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 15:13:17 -0400 Subject: [PATCH 23/63] better dir handling, handle token expire --- custom_components/fordpass/autonomicData.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/custom_components/fordpass/autonomicData.py b/custom_components/fordpass/autonomicData.py index 7fde1cf..7f70d0a 100644 --- a/custom_components/fordpass/autonomicData.py +++ b/custom_components/fordpass/autonomicData.py @@ -40,12 +40,18 @@ def get_autonomic_token(ford_access_token): except requests.exceptions.HTTPError as errh: print(f"HTTP Error: {errh}") + print(f"Trying refresh token") + get_autonomic_token(ford_refresh_token) except requests.exceptions.ConnectionError as errc: print(f"Error Connecting: {errc}") + sys.exit() except requests.exceptions.Timeout as errt: print(f"Timeout Error: {errt}") + sys.exit() except requests.exceptions.RequestException as err: print(f"Something went wrong: {err}") + sys.exit() + def get_vehicle_status(vin, access_token): BASE_URL = "https://api.autonomic.ai/" @@ -90,6 +96,7 @@ def redact_json(data, redaction): redact_json(item, redaction) if __name__ == "__main__": + workingDir = "/config/custom_components/fordpass" if gitHub_username == "": gitHub_username = 'my' if fp_vin == "": @@ -98,24 +105,25 @@ def redact_json(data, redaction): if fp_token == "": print("Please enter your FordPass token text file name into the python script") sys.exit() - elif os.path.isfile(fp_token) == False: - print(f"Error finding FordPass token text file: {fp_token}") + elif os.path.isfile(os.path.join(workingDir, fp_token)) == False: + print(f"Error finding FordPass token text file: {os.path.join(workingDir, fp_token)}") sys.exit() + fp_token = os.path.join(workingDir, fp_token) # Get FordPass token with open(fp_token, 'r') as file: fp_token_data = json.load(file) ford_access_token = fp_token_data['access_token'] - + ford_refresh_token = fp_token_data['refresh_token'] # Exchange Fordpass token for Autonomic Token autonomic_token = get_autonomic_token(ford_access_token) vehicle_status = get_vehicle_status(fp_vin, autonomic_token["access_token"]) current_datetime = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") - fileName = f'/root/config/custom_components/fordpass/{gitHub_username}_status_{current_datetime}.json' + fileName = os.path.join(workingDir, f"{gitHub_username}_status_{current_datetime}.json") # Write the updated JSON data to the file with open(fileName, 'w') as file: json.dump(vehicle_status, file, indent=4) - print("done") + print("done") \ No newline at end of file From 835006891da1e3a1dee3cc346761a2dd34c3f4a2 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 08:10:40 +1000 Subject: [PATCH 24/63] Fix status_update function --- custom_components/fordpass/fordpass_new.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 15cb8c7..5e32fa3 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -496,10 +496,8 @@ def request_update(self, vin=""): vinnum = vin else: vinnum = self.vin - status = self.__make_request( - "PUT", f"{BASE_URL}/vehicles/v5/{vinnum}/status", None, None - ) - return status.json()["status"] + status = self.__requestAndPollCommand("statusRefresh", vinnum) + return status def __make_request(self, method, url, data, params): """ @@ -552,16 +550,20 @@ def __request_and_poll_command(self, command, vin=None): data=json.dumps(data), headers=headers ) + else: r = session.post( f"{AUTONOMIC_URL}/command/vehicles/{vin}/commands", data=json.dumps(data), headers=headers ) - _LOGGER.debug("Testing command") _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) + if r.status_code == 201: + return True + + return False def __request_and_poll(self, method, url): """Poll API until status code is reached, locking + remote start""" From b0c4b4f3702183729bb674a4825287eb5a915c71 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 08:14:15 +1000 Subject: [PATCH 25/63] code tidy --- custom_components/fordpass/fordpass_new.py | 3 +-- custom_components/fordpass/sensor.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 5e32fa3..f6e5829 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -550,7 +550,7 @@ def __request_and_poll_command(self, command, vin=None): data=json.dumps(data), headers=headers ) - + else: r = session.post( f"{AUTONOMIC_URL}/command/vehicles/{vin}/commands", @@ -562,7 +562,6 @@ def __request_and_poll_command(self, command, vin=None): _LOGGER.debug(r.text) if r.status_code == 201: return True - return False def __request_and_poll(self, method, url): diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 9a6c742..0b0e825 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -125,7 +125,7 @@ def get_value(self, ftype): if self.sensor == "lastRefresh": return dt.as_local( datetime.strptime( - self.coordinator.data["updateTime"] , "%Y-%m-%dT%H:%M:%S.%fz" + self.coordinator.data["updateTime"], "%Y-%m-%dT%H:%M:%S.%fz" ) ) if self.sensor == "elVeh": @@ -249,9 +249,9 @@ def get_value(self, ftype): decimal = 0 tirepress = {} for value in self.data["tirePressure"]: - #if "recommended" in key: - # tirepress[key] = round(float(value["value"]) * rval, decimal) - #else: + # if "recommended" in key: + # tirepress[key] = round(float(value["value"]) * rval, decimal) + # else: tirepress[value["vehicleWheel"]] = round(float(value["value"]) * sval, decimal) return tirepress return None From 0b7e8084209b221ba2653200e7d5874a4d9b8d95 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 08:35:29 +1000 Subject: [PATCH 26/63] Add remote start status and countdown to attribute --- custom_components/fordpass/sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 0b0e825..8c8c36f 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -149,9 +149,9 @@ def get_value(self, ftype): ] return "Unsupported" if self.sensor == "remoteStartStatus": - if self.data["remoteStartStatus"] is None: + if self.data["remoteStartCountdownTimer"] is None: return None - if self.data["remoteStartStatus"]["value"] == 1: + if self.data["remoteStartCountdownTimer"]["value"] > 0: return "Active" return "Inactive" if self.sensor == "messages": @@ -367,9 +367,9 @@ def get_value(self, ftype): return zone return None if self.sensor == "remoteStartStatus": - if self.data["remoteStart"] is None: + if self.data["remoteStartCountdownTimer"] is None: return None - return self.data["remoteStart"].items() + return { "Countdown": self.data["remoteStartCountdownTimer"]["value"] } if self.sensor == "messages": if self.coordinator.data["messages"] is None: return None From aa66319f23315dcb8447bb026ccfc3a2e42c7de1 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 08:40:14 +1000 Subject: [PATCH 27/63] enable remoteStartStatus sensor --- custom_components/fordpass/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index d622fd4..fc47d94 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -50,7 +50,7 @@ # "icon": "mdi:one-up", # "name": "Firmware Update In Progress", # }, - # "remoteStartStatus": {"icon": "mdi:remote"}, + "remoteStartStatus": {"icon": "mdi:remote"}, # "zoneLighting": {"icon": "mdi:spotlight-beam"}, "messages": {"icon": "mdi:message-text"}, # "dieselSystemStatus": {"icon": "mdi:smoking-pipe"}, From fff4513ad654947ae0cf22b822b14af39ba921b1 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 21:15:43 -0400 Subject: [PATCH 28/63] missed redaction for charging location. This now redacts all --- custom_components/fordpass/autonomicData.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/custom_components/fordpass/autonomicData.py b/custom_components/fordpass/autonomicData.py index 7f70d0a..ad83344 100644 --- a/custom_components/fordpass/autonomicData.py +++ b/custom_components/fordpass/autonomicData.py @@ -2,6 +2,7 @@ import requests import sys import os +import re from datetime import datetime @@ -62,7 +63,7 @@ def get_vehicle_status(vin, access_token): "Content-Type": "application/json", "accept": "*/*" } - redactionItems = ["lat", "lon", "vehicleId", "vin"] + redactionItems = ["lat", "lon", "vehicleId", "vin", "latitude", "longitude"] try: response = requests.post(url, headers=headers, json={}) @@ -85,16 +86,28 @@ def get_vehicle_status(vin, access_token): print(f"Something went wrong: {err}") def redact_json(data, redaction): + # Regular expression to match GPS coordinates + gps_pattern = r'"gpsDegree":\s*-?\d+\.\d+,\s*"gpsFraction":\s*-?\d+\.\d+,\s*"gpsSign":\s*-?\d+\.\d+' + if isinstance(data, dict): for key in list(data.keys()): if key in redaction: data[key] = 'REDACTED' else: - redact_json(data[key], redaction) + if isinstance(data[key], str): + # Redact GPS coordinates in string values + data[key] = re.sub(gps_pattern, '"gpsDegree": "REDACTED", "gpsFraction": "REDACTED", "gpsSign": "REDACTED"', data[key]) + else: + redact_json(data[key], redaction) + # Special handling for 'stringArrayValue' + if key == 'stringArrayValue': + for i in range(len(data[key])): + data[key][i] = re.sub(gps_pattern, '"gpsDegree": "REDACTED", "gpsFraction": "REDACTED", "gpsSign": "REDACTED"', data[key][i]) elif isinstance(data, list): for item in data: redact_json(item, redaction) - + + if __name__ == "__main__": workingDir = "/config/custom_components/fordpass" if gitHub_username == "": From c592729b82abddb9cb957da41cd1926baa033b54 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 22:01:50 -0400 Subject: [PATCH 29/63] updated all to 1.53Beta --- custom_components/fordpass/__init__.py | 5 +- custom_components/fordpass/config_flow.py | 2 +- custom_components/fordpass/const.py | 29 +- custom_components/fordpass/device_tracker.py | 19 +- custom_components/fordpass/fordpass_new.py | 201 ++++---- custom_components/fordpass/lock.py | 32 +- custom_components/fordpass/manifest.json | 32 +- custom_components/fordpass/sensor.py | 271 +++++----- custom_components/fordpass/sensorEV.py | 490 +++++++++++++++++++ custom_components/fordpass/switch.py | 26 +- 10 files changed, 789 insertions(+), 318 deletions(-) create mode 100644 custom_components/fordpass/sensorEV.py diff --git a/custom_components/fordpass/__init__.py b/custom_components/fordpass/__init__.py index 20e0ba8..edde988 100644 --- a/custom_components/fordpass/__init__.py +++ b/custom_components/fordpass/__init__.py @@ -215,9 +215,6 @@ async def _async_update_data(self): data["vehicles"] = await self._hass.async_add_executor_job( self.vehicle.vehicles ) - data["chargeStatus"] = await self._hass.async_add_executor_job( - self.vehicle.charge_status - ) _LOGGER.debug(data) # If data has now been fetched but was previously unavailable, log and reset if not self._available: @@ -272,4 +269,4 @@ def device_info(self): "name": f"{VEHICLE} ({self.coordinator.vin})", "model": f"{model}", "manufacturer": MANUFACTURER, - } + } \ No newline at end of file diff --git a/custom_components/fordpass/config_flow.py b/custom_components/fordpass/config_flow.py index bd7c2bb..699fb28 100644 --- a/custom_components/fordpass/config_flow.py +++ b/custom_components/fordpass/config_flow.py @@ -187,4 +187,4 @@ class InvalidAuth(exceptions.HomeAssistantError): class InvalidVin(exceptions.HomeAssistantError): - """Error to indicate the wrong vin""" + """Error to indicate the wrong vin""" \ No newline at end of file diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index bdfa34d..6c0da58 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,20 +42,20 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, - "chargeStatus": {"icon": "mdi:ev-station"}, - "deepSleepInProgress": { - "icon": "mdi:power-sleep", - "name": "Deep Sleep Mode Active", - }, - "firmwareUpgInProgress": { - "icon": "mdi:one-up", - "name": "Firmware Update In Progress", - }, + "elVehCharging": {"icon": "mdi:ev-station"}, + # "deepSleepInProgress": { + # "icon": "mdi:power-sleep", + # "name": "Deep Sleep Mode Active", + # }, + # "firmwareUpgInProgress": { + # "icon": "mdi:one-up", + # "name": "Firmware Update In Progress", + # }, "remoteStartStatus": {"icon": "mdi:remote"}, - "zoneLighting": {"icon": "mdi:spotlight-beam"}, + # "zoneLighting": {"icon": "mdi:spotlight-beam"}, "messages": {"icon": "mdi:message-text"}, - "dieselSystemStatus": {"icon": "mdi:smoking-pipe"}, - "exhaustFluidLevel": {"icon": "mdi:barrel"} + # "dieselSystemStatus": {"icon": "mdi:smoking-pipe"}, + # "exhaustFluidLevel": {"icon": "mdi:barrel"} } SWITCHES = {"ignition": {"icon": "hass:power"}, "guardmode": {"icon": "mdi:shield-key"}} @@ -73,4 +73,7 @@ }, } -SWITCHES = {"ignition": {"icon": "hass:power"}, "guardmode": {"icon": "mdi:shield-key"}} +SWITCHES = { + "ignition": {"icon": "hass:power"}, + # "guardmode": {"icon": "mdi:shield-key"} +} \ No newline at end of file diff --git a/custom_components/fordpass/device_tracker.py b/custom_components/fordpass/device_tracker.py index 5202358..c9d1366 100644 --- a/custom_components/fordpass/device_tracker.py +++ b/custom_components/fordpass/device_tracker.py @@ -15,7 +15,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entry = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] # Added a check to see if the car supports GPS - if entry.data["gps"] is not None: + if entry.data["metrics"]["position"] is not None: async_add_entities([CarTracker(entry, "gps")], True) else: _LOGGER.debug("Vehicle does not support GPS") @@ -24,28 +24,23 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class CarTracker(FordPassEntity, TrackerEntity): def __init__(self, coordinator, sensor): - super().__init__( - device_id="fordpass_" + sensor, - name="fordpass_" + sensor, - coordinator=coordinator - ) - self._attr = {} self.sensor = sensor self.coordinator = coordinator + self.data = coordinator.data["metrics"] self._device_id = "fordpass_tracker" # Required for HA 2022.7 self.coordinator_context = object() @property def latitude(self): - """Return latitude from Vehicle GPS""" - return float(self.coordinator.data[self.sensor]["latitude"]) + """Return latitude""" + return float(self.data["position"]["value"]["location"]["lat"]) @property def longitude(self): - """Return longitude from Vehicle GPS""" - return float(self.coordinator.data[self.sensor]["longitude"]) + """Return longtitude""" + return float(self.data["position"]["value"]["location"]["lon"]) @property def source_type(self): @@ -70,4 +65,4 @@ def extra_state_attributes(self): @property def icon(self): """Return device tracker icon""" - return "mdi:radar" + return "mdi:radar" \ No newline at end of file diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 4d76278..952ee28 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -32,9 +32,13 @@ "North America & Canada": "71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592", } +NEW_API = True + BASE_URL = "https://usapi.cv.ford.com/api" GUARD_URL = "https://api.mps.ford.com/api" SSO_URL = "https://sso.ci.ford.com" +AUTONOMIC_URL = "https://api.autonomic.ai/v1" +AUTONOMIC_ACCOUNT_URL = "https://accounts.autonomic.ai/v1" session = requests.Session() @@ -55,6 +59,7 @@ def __init__( self.expires = None self.expires_at = None self.refresh_token = None + self.auto_token = None retry = Retry(connect=3, backoff_factor=0.5) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) @@ -189,6 +194,9 @@ def auth(self): session.cookies.clear() return True response.raise_for_status() + # Code to get Auto token + if self.get_auto_token(): + return True return False def refresh_token_func(self, token): @@ -241,6 +249,7 @@ def __acquire_token(self): self.auth() else: _LOGGER.debug("Token is valid, continuing") + self.get_auto_token() def write_token(self, token): """Save token to file for reuse""" @@ -271,6 +280,37 @@ def clear_token(self): if os.path.isfile(self.token_location): os.remove(self.token_location) + def get_auto_token(self): + """Get token from new autonomic API""" + _LOGGER.debug("Getting Auto Token") + headers = { + "accept": "*/*", + "content-type": "application/x-www-form-urlencoded" + } + + data = { + "subject_token": self.token, + "subject_issuer": "fordpass", + "client_id": "fordpass-prod", + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + + } + + r = session.post( + f"{AUTONOMIC_ACCOUNT_URL}/auth/oidc/token", + data=data, + headers=headers + ) + + if r.status_code == 200: + result = r.json() + _LOGGER.debug(r.status_code) + _LOGGER.debug(r.text) + self.auto_token = result["access_token"] + return True + return False + def status(self): """Get Vehicle status from API""" @@ -284,37 +324,50 @@ def status(self): "Application-Id": self.region, } - response = session.get( - f"{BASE_URL}/vehicles/v5/{self.vin}/status", params=params, headers=headers - ) - if response.status_code == 200: - result = response.json() - if result["status"] == 402: - response.raise_for_status() - return result["vehiclestatus"] - if response.status_code == 401: - _LOGGER.debug("401 with status request: start token refresh") - data = {} - data["access_token"] = self.token - data["refresh_token"] = self.refresh_token - data["expiry_date"] = self.expires_at - self.refresh_token_func(data) - self.__acquire_token() + if NEW_API: headers = { **apiHeaders, - "auth-token": self.token, + "authorization": f"Bearer {self.auto_token}", "Application-Id": self.region, } + r = session.get( + f"{AUTONOMIC_URL}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers + ) + if r.status_code == 200: + # _LOGGER.debug(r.text) + result = r.json() + return result + else: response = session.get( - f"{BASE_URL}/vehicles/v5/{self.vin}/status", - params=params, - headers=headers, + f"{BASE_URL}/vehicles/v5/{self.vin}/status", params=params, headers=headers ) if response.status_code == 200: result = response.json() - return result["vehiclestatus"] - response.raise_for_status() - return None + if result["status"] == 402: + response.raise_for_status() + return result["vehiclestatus"] + if response.status_code == 401: + _LOGGER.debug("401 with status request: start token refresh") + data = {} + data["access_token"] = self.token + data["refresh_token"] = self.refresh_token + data["expiry_date"] = self.expires_at + self.refresh_token_func(data) + self.__acquire_token() + headers = { + **apiHeaders, + "auth-token": self.token, + "Application-Id": self.region, + } + response = session.get( + f"{BASE_URL}/vehicles/v5/{self.vin}/status", + params=params, + headers=headers, + ) + if response.status_code == 200: + result = response.json() + return result["vehiclestatus"] + response.raise_for_status() def messages(self): """Get Vehicle messages from API""" @@ -366,7 +419,7 @@ def vehicles(self): _LOGGER.debug(result) return result - _LOGGER.debug(response.text) + # _LOGGER.debug(response.text) response.raise_for_status() return None @@ -393,33 +446,25 @@ def start(self): """ Issue a start command to the engine """ - return self.__request_and_poll( - "PUT", f"{BASE_URL}/vehicles/v5/{self.vin}/engine/start" - ) + return self.__request_and_poll_command("remoteStart") def stop(self): """ Issue a stop command to the engine """ - return self.__request_and_poll( - "DELETE", f"{BASE_URL}/vehicles/v5/{self.vin}/engine/start" - ) + return self.__request_and_poll_command("cancelRemoteStart") def lock(self): """ Issue a lock command to the doors """ - return self.__request_and_poll( - "PUT", f"{BASE_URL}/vehicles/v5/{self.vin}/doors/lock" - ) + return self.__request_and_poll_command("unlock") def unlock(self): """ Issue an unlock command to the doors """ - return self.__request_and_poll( - "DELETE", f"{BASE_URL}/vehicles/v5/{self.vin}/doors/lock" - ) + return self.__request_and_poll_command("unlock") def enable_guard(self): """ @@ -451,53 +496,9 @@ def request_update(self, vin=""): vinnum = vin else: vinnum = self.vin - status = self.__make_request( - "PUT", f"{BASE_URL}/vehicles/v5/{vinnum}/status", None, None - ) - return status.json()["status"] - - def charge_log(self): - """Get Charge logs from account""" - self.__acquire_token() + status = self.__requestAndPollCommand("statusRefresh", vinnum) + return status - headers = { - **apiHeaders, - "Auth-Token": self.token, - "Application-Id": self.region - } - - response = session.get( - f"{GUARD_URL}/electrification/experiences/v1/devices/{self.vin}/energy-transfer-logs/", - headers=headers) - - if response.status_code == 200: - result = response.json() - return result["energyTransferLogs"] - - response.raise_for_status() - return None - - def charge_status(self): - """Get Charge status from account""" - self.__acquire_token() - - headers = { - **apiHeaders, - "Auth-Token": self.token, - "Application-Id": self.region - } - - response = session.get( - f"{GUARD_URL}/electrification/experiences/v1/devices/{self.vin}/energy-transfer-status/", - headers=headers) - - if response.status_code == 200: - result = response.json() - return result - - response.raise_for_status() - return None - def __make_request(self, method, url, data, params): """ Make a request to the given URL, passing data/params as needed @@ -529,6 +530,40 @@ def __poll_status(self, url, command_id): _LOGGER.debug("Command failed") return False + def __request_and_poll_command(self, command, vin=None): + """Send command to the new Command endpoint""" + headers = { + **apiHeaders, + "Application-Id": self.region, + "authorization": f"Bearer {self.auto_token}" + } + + data = { + "properties": {}, + "tags": {}, + "type": command, + "wakeUp": True + } + if vin is None: + r = session.post( + f"{AUTONOMIC_URL}/command/vehicles/{self.vin}/commands", + data=json.dumps(data), + headers=headers + ) + + else: + r = session.post( + f"{AUTONOMIC_URL}/command/vehicles/{vin}/commands", + data=json.dumps(data), + headers=headers + ) + _LOGGER.debug("Testing command") + _LOGGER.debug(r.status_code) + _LOGGER.debug(r.text) + if r.status_code == 201: + return True + return False + def __request_and_poll(self, method, url): """Poll API until status code is reached, locking + remote start""" self.__acquire_token() @@ -539,4 +574,4 @@ def __request_and_poll(self, method, url): if "commandId" in result: return self.__poll_status(url, result["commandId"]) return False - return False + return False \ No newline at end of file diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index ff0f55c..deed51d 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -14,7 +14,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entry = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] lock = Lock(entry) - if lock.coordinator.data.get("lockStatus", {}) and lock.coordinator.data["lockStatus"]["value"] != "ERROR": + if lock.coordinator.data["metrics"]["doorLockStatus"] and lock.coordinator.data["metrics"]["doorLockStatus"][0]["value"] != "ERROR": async_add_entities([lock], False) else: _LOGGER.debug("Ford model doesn't support remote locking") @@ -22,35 +22,38 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class Lock(FordPassEntity, LockEntity): """Defines the vehicle's lock.""" - def __init__(self, coordinator): """Initialize.""" - super().__init__( - device_id="fordpass_doorlock", - name="fordpass_doorlock", - coordinator=coordinator, - ) + self._device_id = "fordpass_doorlock" + self.coordinator = coordinator + self.data = coordinator.data["metrics"] + + # Required for HA 2022.7 + self.coordinator_context = object() async def async_lock(self, **kwargs): """Locks the vehicle.""" self._attr_is_locking = True self.async_write_ha_state() _LOGGER.debug("Locking %s", self.coordinator.vin) - await self.coordinator.hass.async_add_executor_job( + status = await self.coordinator.hass.async_add_executor_job( self.coordinator.vehicle.lock ) + _LOGGER.debug(status) await self.coordinator.async_request_refresh() + _LOGGER.debug("Locking here") self._attr_is_locking = False self.async_write_ha_state() async def async_unlock(self, **kwargs): """Unlocks the vehicle.""" + _LOGGER.debug("Unlocking %s", self.coordinator.vin) self._attr_is_unlocking = True self.async_write_ha_state() - _LOGGER.debug("Unlocking %s", self.coordinator.vin) - await self.coordinator.hass.async_add_executor_job( + status = await self.coordinator.hass.async_add_executor_job( self.coordinator.vehicle.unlock ) + _LOGGER.debug(status) await self.coordinator.async_request_refresh() self._attr_is_unlocking = False self.async_write_ha_state() @@ -58,11 +61,16 @@ async def async_unlock(self, **kwargs): @property def is_locked(self): """Determine if the lock is locked.""" - if self.coordinator.data is None or self.coordinator.data["lockStatus"] is None: + if self.data is None or self.data["doorLockStatus"] is None: return None - return self.coordinator.data["lockStatus"]["value"] == "LOCKED" + return self.data["doorLockStatus"][0]["value"] == "LOCKED" @property def icon(self): """Return MDI Icon""" return "mdi:car-door-lock" + + @property + def name(self): + """Return Name""" + return "fordpass_doorlock" \ No newline at end of file diff --git a/custom_components/fordpass/manifest.json b/custom_components/fordpass/manifest.json index 05f9bd8..e11203f 100644 --- a/custom_components/fordpass/manifest.json +++ b/custom_components/fordpass/manifest.json @@ -1,17 +1,17 @@ { - "domain": "fordpass", - "name": "FordPass", - "codeowners": ["@itchannel"], - "config_flow": true, - "dependencies": [], - "documentation": "https://github.com/itchannel/fordpass-ha", - "homekit": {}, - "integration_type": "device", - "iot_class": "cloud_polling", - "issue_tracker": "https://github.com/itchannel/fordpass-ha/issues", - "loggers": ["custom_components.fordpass"], - "requirements": [], - "ssdp": [], - "version": "0.1.52", - "zeroconf": [] - } \ No newline at end of file + "domain": "fordpass", + "name": "FordPass", + "codeowners": ["@itchannel"], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/itchannel/fordpass-ha", + "homekit": {}, + "integration_type": "device", + "iot_class": "cloud_polling", + "issue_tracker": "https://github.com/itchannel/fordpass-ha/issues", + "loggers": ["custom_components.fordpass"], + "requirements": [], + "ssdp": [], + "version": "0.1.53", + "zeroconf": [] +} \ No newline at end of file diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 94df35e..d91df42 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -28,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if "zoneLighting" in sensor.coordinator.data: sensors.append(sensor) elif key == "elVeh": - if sensor.coordinator.data["elVehDTE"] is not None: + if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): @@ -59,6 +59,7 @@ def __init__(self, coordinator, sensor, options): self.fordoptions = options self._attr = {} self.coordinator = coordinator + self.data = coordinator.data["metrics"] self._device_id = "fordpass_" + sensor # Required for HA 2022.7 self.coordinator_context = object() @@ -70,98 +71,100 @@ def get_value(self, ftype): if self.fordoptions[CONF_DISTANCE_UNIT] is not None: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": if DISTANCE_CONVERSION_DISABLED in self.fordoptions and self.fordoptions[DISTANCE_CONVERSION_DISABLED] is True: - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] return round( - float(self.coordinator.data[self.sensor]["value"]) / 1.60934 + float(self.data[self.sensor]["value"]) / 1.60934 ) - return self.coordinator.data[self.sensor]["value"] - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "fuel": - if self.coordinator.data[self.sensor] is None: + if "fuelLevel" in self.data: + if self.data["fuelLevel"] is None: + return None + return round(self.data["fuelLevel"]["value"]) + elif "xevBatteryStateOfCharge": + return round(self.data["xevBatteryStateOfCharge"]["value"]) + else: return None - return round(self.coordinator.data[self.sensor]["fuelLevel"]) if self.sensor == "battery": - return self.coordinator.data[self.sensor]["batteryHealth"]["value"] + return round(self.data["batteryStateOfCharge"]["value"]) if self.sensor == "oil": - return self.coordinator.data[self.sensor]["oilLife"] + return round(self.data["oilLifeRemaining"]["value"]) if self.sensor == "tirePressure": - return self.coordinator.data[self.sensor]["value"] + return self.data["tirePressureSystemStatus"][0]["value"] if self.sensor == "gps": - if self.coordinator.data[self.sensor] is None: + if self.data["position"] is None: return "Unsupported" - return self.coordinator.data[self.sensor]["gpsState"] + return self.data["position"]["value"] if self.sensor == "alarm": - return self.coordinator.data[self.sensor]["value"] + return self.data["alarmStatus"]["value"] if self.sensor == "ignitionStatus": - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "firmwareUpgInProgress": - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "deepSleepInProgress": - return self.coordinator.data[self.sensor]["value"] + return self.data[self.sensor]["value"] if self.sensor == "doorStatus": - for key, value in self.coordinator.data[self.sensor].items(): + for value in self.data["doorStatus"]: if value["value"] == "Invalid": continue - if value["value"] != "Closed": + if value["value"] != "CLOSED": return "Open" return "Closed" if self.sensor == "windowPosition": - if self.coordinator.data[self.sensor] is None: - return "Unsupported" - status = "Closed" - for key, value in self.coordinator.data[self.sensor].items(): - if "open" in value["value"].lower(): - status = "Open" - return status + if "windowStatus" in self.data: + if self.data["windowStatus"] is None: + return "Unsupported" + status = "Closed" + for window in self.data["windowStatus"]: + windowrange = window["value"]["doubleRange"] + if windowrange["lowerBound"] != 0.0 and windowrange["upperBound"] != 0.0: + status = "Open" + return status + return "Unsupported" if self.sensor == "lastRefresh": return dt.as_local( datetime.strptime( - self.coordinator.data[self.sensor] + "+0000", "%m-%d-%Y %H:%M:%S%z" + self.coordinator.data["updateTime"], "%Y-%m-%dT%H:%M:%S.%fz" ) ) if self.sensor == "elVeh": - if self.coordinator.data["elVehDTE"] is not None: + if "xevBatteryRange" in self.data: if self.fordoptions[CONF_DISTANCE_UNIT] is not None: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": return round( - float(self.coordinator.data["elVehDTE"]["value"]) / 1.60934 + float(self.data["xevBatteryRange"]["value"]) / 1.60934 ) - return float(self.coordinator.data["elVehDTE"]["value"]) - return float(self.coordinator.data["elVehDTE"]["value"]) + return float(self.data["xevBatteryRange"]["value"]) + return float(self.data["xevBatteryRange"]["value"]) return "Unsupported" if self.sensor == "zoneLighting": - if "zoneLighting" not in self.coordinator.data: + if "zoneLighting" not in self.data: return "Unsupported" if ( - self.coordinator.data["zoneLighting"] is not None and self.coordinator.data["zoneLighting"]["activationData"] is not None + self.data["zoneLighting"] is not None and self.data["zoneLighting"]["activationData"] is not None ): - return self.coordinator.data["zoneLighting"]["activationData"][ + return self.data["zoneLighting"]["activationData"][ "value" ] return "Unsupported" if self.sensor == "remoteStartStatus": - if self.coordinator.data["remoteStartStatus"] is None: + if self.data["remoteStartCountdownTimer"] is None: return None - if self.coordinator.data["remoteStartStatus"]["value"] == 1: + if self.data["remoteStartCountdownTimer"]["value"] > 0: return "Active" return "Inactive" if self.sensor == "messages": if self.coordinator.data["messages"] is None: return None return len(self.coordinator.data["messages"]) - if self.sensor == "chargeStatus": - if self.coordinator.data["chargeStatus"] is not None and self.coordinator.data["chargeStatus"]["power"] is not None: - power_wh = self.coordinator.data["chargeStatus"]["power"] - power_kwh = power_wh / 1000 - return round(power_kwh, 1) - return "Not Supported" if self.sensor == "dieselSystemStatus": - if self.coordinator.data["dieselSystemStatus"]["filterRegenerationStatus"] is not None: - return self.coordinator.data["dieselSystemStatus"]["filterRegenerationStatus"] + if self.data["dieselSystemStatus"]["filterRegenerationStatus"] is not None: + return self.data["dieselSystemStatus"]["filterRegenerationStatus"] return "Not Supported" if self.sensor == "exhaustFluidLevel": - if "value" in self.coordinator.data["dieselSystemStatus"]["exhaustFluidLevel"]: - return self.coordinator.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] + if "value" in self.data["dieselSystemStatus"]["exhaustFluidLevel"]: + return self.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] return "Not Supported" return None if ftype == "measurement": @@ -172,9 +175,9 @@ def get_value(self, ftype): if self.sensor == "fuel": return "%" if self.sensor == "battery": - return None + return "%" if self.sensor == "oil": - return None + return "%" if self.sensor == "tirePressure": return None if self.sensor == "gps": @@ -203,33 +206,31 @@ def get_value(self, ftype): if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": return "mi" return "km" - if self.sensor == "chargeStatus": - return "kWh" if self.sensor == "exhaustFluidLevel": return "%" return None if ftype == "attribute": if self.sensor == "odometer": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "fuel": - if self.coordinator.data[self.sensor] is None: + if self.data["fuelRange"] is None: return None if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - self.coordinator.data["fuel"]["distanceToEmpty"] = round( - float(self.coordinator.data["fuel"]["distanceToEmpty"]) / 1.60934 + self.data["fuelRange"]["value"] = round( + float(self.data["fuelRange"]["value"]) / 1.60934 ) - return self.coordinator.data[self.sensor].items() + return {"fuelRange": self.data["fuelRange"]["value"]} if self.sensor == "battery": return { - "Battery Voltage": self.coordinator.data[self.sensor][ - "batteryStatusActual" - ]["value"] + "Battery Voltage": self.data["batteryVoltage"]["value"] } if self.sensor == "oil": - return self.coordinator.data[self.sensor].items() + return self.data["oilLifeRemaining"].items() if self.sensor == "tirePressure": - if self.coordinator.data["TPMS"] is not None: + if self.data["tirePressure"] is not None: + _LOGGER.debug(self.fordoptions[CONF_PRESSURE_UNIT]) if self.fordoptions[CONF_PRESSURE_UNIT] == "PSI": + _LOGGER.debug("PSIIIII") sval = 0.1450377377 rval = 1 decimal = 0 @@ -242,187 +243,133 @@ def get_value(self, ftype): rval = 6.8947572932 decimal = 0 else: + _LOGGER.debug("HITT") sval = 1 rval = 1 decimal = 0 tirepress = {} - for key, value in self.coordinator.data["TPMS"].items(): - if "TirePressure" in key and value is not None and value != '': - if "recommended" in key: - tirepress[key] = round(float(value["value"]) * rval, decimal) - else: - tirepress[key] = round(float(value["value"]) * sval, decimal) + for value in self.data["tirePressure"]: + # if "recommended" in key: + # tirepress[key] = round(float(value["value"]) * rval, decimal) + # else: + tirepress[value["vehicleWheel"]] = round(float(value["value"]) * sval, decimal) return tirepress return None if self.sensor == "gps": - if self.coordinator.data[self.sensor] is None: + if self.data["position"] is None: return None - return self.coordinator.data[self.sensor].items() + return self.data["position"].items() if self.sensor == "alarm": - return self.coordinator.data[self.sensor].items() + return self.data["alarmStatus"].items() if self.sensor == "ignitionStatus": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "firmwareUpgInProgress": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "deepSleepInProgress": - return self.coordinator.data[self.sensor].items() + return self.data[self.sensor].items() if self.sensor == "doorStatus": doors = {} - for key, value in self.coordinator.data[self.sensor].items(): - doors[key] = value["value"] + for value in self.data[self.sensor]: + doors[value["vehicleDoor"]] = value["value"] return doors if self.sensor == "windowPosition": - if self.coordinator.data[self.sensor] is None: + if "windowStatus" not in self.data: return None windows = {} - for key, value in self.coordinator.data[self.sensor].items(): - windows[key] = value["value"] - if "open" in value["value"].lower(): - if "btwn" in value["value"].lower(): - windows[key] = "Open-Partial" - else: - windows[key] = "Open" - elif "closed" in value["value"].lower(): - windows[key] = "Closed" + for window in self.data["windowStatus"]: + windows[window["vehicleWindow"]] = window return windows if self.sensor == "lastRefresh": return None if self.sensor == "elVeh": - if self.coordinator.data["elVehDTE"] is None: + if self.data["xevBatteryCapacity"] is None: return None elecs = {} if ( - self.coordinator.data["elVehDTE"] is not None and self.coordinator.data["elVehDTE"]["value"] is not None + self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None ): - elecs["elVehDTE"] = self.coordinator.data["elVehDTE"]["value"] + elecs["xevBatteryCapacity"] = self.data["xevBatteryCapacity"]["value"] if ( - self.coordinator.data["plugStatus"] is not None and self.coordinator.data["plugStatus"]["value"] is not None + self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None ): - elecs["Plug Status"] = self.coordinator.data["plugStatus"][ + elecs["Plug Status"] = self.data["xevPlugChargerStatus"][ "value" ] if ( - self.coordinator.data["chargingStatus"] is not None and self.coordinator.data["chargingStatus"]["value"] is not None - ): - elecs["Charging Status"] = self.coordinator.data[ - "chargingStatus" - ]["value"] - - if ( - self.coordinator.data["chargeStartTime"] is not None and self.coordinator.data["chargeStartTime"]["value"] is not None - ): - elecs["Charge Start Time"] = self.coordinator.data[ - "chargeStartTime" - ]["value"] - - if ( - self.coordinator.data["chargeEndTime"] is not None and self.coordinator.data["chargeEndTime"]["value"] is not None + self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None ): - elecs["Charge End Time"] = self.coordinator.data[ - "chargeEndTime" + elecs["Charging Status"] = self.data[ + "xevBatteryChargeDisplayStatus" ]["value"] if ( - self.coordinator.data["batteryFillLevel"] is not None and self.coordinator.data["batteryFillLevel"]["value"] is not None + self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None ): - elecs["Battery Fill Level"] = int(self.coordinator.data[ - "batteryFillLevel" - ]["value"]) - - if ( - self.coordinator.data["chargerPowertype"] is not None and self.coordinator.data["chargerPowertype"]["value"] is not None - ): - elecs["Charger Power Type"] = self.coordinator.data[ - "chargerPowertype" + elecs["Charger Power Type"] = self.data[ + "xevChargeStationPowerType" ]["value"] if ( - self.coordinator.data["batteryChargeStatus"] is not None and self.coordinator.data["batteryChargeStatus"]["value"] is not None + self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None ): - elecs["Battery Charge Status"] = self.coordinator.data[ - "batteryChargeStatus" + elecs["Battery Charge Status"] = self.data[ + "xevChargeStationCommunicationStatus" ]["value"] if ( - self.coordinator.data["batteryPerfStatus"] is not None and self.coordinator.data["batteryPerfStatus"]["value"] is not None + self.data["xevBatteryPerformanceStatus"] is not None and self.data["xevBatteryPerformanceStatus"]["value"] is not None ): - elecs["Battery Performance Status"] = self.coordinator.data[ - "batteryPerfStatus" + elecs["Battery Performance Status"] = self.data[ + "xevBatteryPerformanceStatus" ]["value"] return elecs - if self.sensor == "chargeStatus": - if self.coordinator.data["chargeStatus"] is None: - return None - cs = {} - if self.coordinator.data[self.sensor]["chargerType"] is not None: - cs["chargerType"] = self.coordinator.data[self.sensor]["chargerType"] - if self.coordinator.data[self.sensor]["currentTargetSoc"] is not None: - cs["currentTargetSoc"] = self.coordinator.data[self.sensor]["currentTargetSoc"] - if ( - self.coordinator.data[self.sensor]["plugDetails"] is not None and - self.coordinator.data[self.sensor]["plugDetails"]["plugInTime"] is not None - ): - cs["Plug In Time"] = self.coordinator.data[self.sensor]["plugDetails"]["plugInTime"] - if ( - self.coordinator.data[self.sensor]["plugDetails"] is not None and - self.coordinator.data[self.sensor]["plugDetails"]["totalPluggedInTime"] is not None - ): - cs["Total Plugged In Time"] = self.coordinator.data[self.sensor]["plugDetails"]["totalPluggedInTime"] - - if self.coordinator.data[self.sensor]["power"] is not None: - power_wh = self.coordinator.data["chargeStatus"]["power"] - power_kwh = power_wh / 1000 - cs["Power Level kWh"] = round(power_kwh, 1) - if self.coordinator.data[self.sensor]["energyConsumed"] is not None: - cs["Energy Consumed"] = self.coordinator.data[self.sensor]["energyConsumed"] - return cs if self.sensor == "zoneLighting": - if "zoneLighting" not in self.coordinator.data: + if "zoneLighting" not in self.data: return None if ( - self.coordinator.data[self.sensor] is not None and self.coordinator.data[self.sensor]["zoneStatusData"] is not None + self.data[self.sensor] is not None and self.data[self.sensor]["zoneStatusData"] is not None ): zone = {} - if self.coordinator.data[self.sensor]["zoneStatusData"] is not None: - for key, value in self.coordinator.data[self.sensor][ + if self.data[self.sensor]["zoneStatusData"] is not None: + for key, value in self.data[self.sensor][ "zoneStatusData" ].items(): zone["zone_" + key] = value["value"] if ( - self.coordinator.data[self.sensor]["lightSwitchStatusData"] + self.data[self.sensor]["lightSwitchStatusData"] is not None ): - for key, value in self.coordinator.data[self.sensor][ + for key, value in self.data[self.sensor][ "lightSwitchStatusData" ].items(): if value is not None: zone[key] = value["value"] if ( - self.coordinator.data[self.sensor]["zoneLightingFaultStatus"] + self.data[self.sensor]["zoneLightingFaultStatus"] is not None ): - zone["zoneLightingFaultStatus"] = self.coordinator.data[ + zone["zoneLightingFaultStatus"] = self.data[ self.sensor ]["zoneLightingFaultStatus"]["value"] if ( - self.coordinator.data[self.sensor][ + self.data[self.sensor][ "zoneLightingShutDownWarning" ] is not None ): - zone["zoneLightingShutDownWarning"] = self.coordinator.data[ + zone["zoneLightingShutDownWarning"] = self.data[ self.sensor ]["zoneLightingShutDownWarning"]["value"] return zone return None if self.sensor == "remoteStartStatus": - if self.coordinator.data["remoteStart"] is None: + if self.data["remoteStartCountdownTimer"] is None: return None - return self.coordinator.data["remoteStart"].items() + return { "Countdown": self.data["remoteStartCountdownTimer"]["value"] } if self.sensor == "messages": if self.coordinator.data["messages"] is None: return None @@ -432,9 +379,9 @@ def get_value(self, ftype): messages[value["messageSubject"]] = value["createdDate"] return messages if self.sensor == "dieselSystemStatus": - return self.coordinator.data["dieselSystemStatus"] + return self.data["dieselSystemStatus"] if self.sensor == "exhaustFluidLevel": - return self.coordinator.data["dieselSystemStatus"] + return self.data["dieselSystemStatus"] return None return None @@ -487,4 +434,4 @@ def device_class(self): return SensorDeviceClass.DISTANCE if SENSORS[self.sensor]["device_class"] == "timestamp": return SensorDeviceClass.TIMESTAMP - return None + return None \ No newline at end of file diff --git a/custom_components/fordpass/sensorEV.py b/custom_components/fordpass/sensorEV.py new file mode 100644 index 0000000..063b71c --- /dev/null +++ b/custom_components/fordpass/sensorEV.py @@ -0,0 +1,490 @@ +"""All vehicle sensors from the accessible by the API""" +import logging +from datetime import datetime + +from homeassistant.util import dt + +from homeassistant.components.sensor import ( + SensorEntity, + SensorDeviceClass, + SensorStateClass +) + +from . import FordPassEntity +from .const import CONF_DISTANCE_UNIT, CONF_PRESSURE_UNIT, DOMAIN, SENSORS, COORDINATOR, DISTANCE_CONVERSION_DISABLED + + +_LOGGER = logging.getLogger(__name__) + + +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][COORDINATOR] + sensors = [] + for key in SENSORS: + sensor = CarSensor(entry, key, config_entry.options) + # Add support for only adding compatible sensors for the given vehicle + if key == "zoneLighting": + if "zoneLighting" in sensor.coordinator.data: + sensors.append(sensor) + elif key == "elVeh": + if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: + sensors.append(sensor) + elif key == "elVehCharging": + if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: + sensors.append(sensor) + elif key == "dieselSystemStatus": + if sensor.coordinator.data.get("dieselSystemStatus", {}): + if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): + sensors.append(sensor) + elif key == "exhaustFluidLevel": + if sensor.coordinator.data.get("dieselSystemStatus", {}): + if sensor.coordinator.data.get("dieselSystemStatus", {}).get("exhaustFluidLevel"): + sensors.append(sensor) + else: + sensors.append(sensor) + async_add_entities(sensors, True) + + +class CarSensor( + FordPassEntity, + SensorEntity, +): + def __init__(self, coordinator, sensor, options): + + super().__init__( + device_id="fordpass_" + sensor, + name="fordpass_" + sensor, + coordinator=coordinator + ) + + self.sensor = sensor + self.fordoptions = options + self._attr = {} + self.coordinator = coordinator + self.data = coordinator.data["metrics"] + self._device_id = "fordpass_" + sensor + # Required for HA 2022.7 + self.coordinator_context = object() + + def get_value(self, ftype): + """Get sensor value and attributes from coordinator data""" + if ftype == "state": + if self.sensor == "odometer": + if self.fordoptions[CONF_DISTANCE_UNIT] is not None: + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + if DISTANCE_CONVERSION_DISABLED in self.fordoptions and self.fordoptions[DISTANCE_CONVERSION_DISABLED] is True: + return self.data[self.sensor]["value"] + return round( + float(self.data[self.sensor]["value"]) / 1.60934 + ) + return self.data[self.sensor]["value"] + return self.data[self.sensor]["value"] + if self.sensor == "fuel": + if "fuelLevel" in self.data: + if self.data["fuelLevel"] is None: + return None + return round(self.data["fuelLevel"]["value"]) + elif "xevBatteryStateOfCharge": + return round(self.data["xevBatteryStateOfCharge"]["value"]) + else: + return None + if self.sensor == "battery": + return round(self.data["batteryStateOfCharge"]["value"]) + if self.sensor == "oil": + return round(self.data["oilLifeRemaining"]["value"]) + if self.sensor == "tirePressure": + return self.data["tirePressureSystemStatus"][0]["value"] + if self.sensor == "gps": + if self.data["position"] is None: + return "Unsupported" + return self.data["position"]["value"] + if self.sensor == "alarm": + return self.data["alarmStatus"]["value"] + if self.sensor == "ignitionStatus": + return self.data[self.sensor]["value"] + if self.sensor == "firmwareUpgInProgress": + return self.data[self.sensor]["value"] + if self.sensor == "deepSleepInProgress": + return self.data[self.sensor]["value"] + if self.sensor == "doorStatus": + for value in self.data["doorStatus"]: + if value["value"] == "Invalid": + continue + if value["value"] != "CLOSED": + return "Open" + return "Closed" + if self.sensor == "windowPosition": + if "windowStatus" in self.data: + if self.data["windowStatus"] is None: + return "Unsupported" + status = "Closed" + for window in self.data["windowStatus"]: + windowrange = window["value"]["doubleRange"] + if windowrange["lowerBound"] != 0.0 and windowrange["upperBound"] != 0.0: + status = "Open" + return status + return "Unsupported" + if self.sensor == "lastRefresh": + return dt.as_local( + datetime.strptime( + self.coordinator.data["updateTime"], "%Y-%m-%dT%H:%M:%S.%fz" + ) + ) + if self.sensor == "elVeh": + if "xevBatteryRange" in self.data: + if self.fordoptions[CONF_DISTANCE_UNIT] is not None: + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + return round( + float(self.data["xevBatteryRange"]["value"]) / 1.60934 + ) + return float(self.data["xevBatteryRange"]["value"]) + return float(self.data["xevBatteryRange"]["value"]) + return "Unsupported" + if self.sensor == "zoneLighting": + if "zoneLighting" not in self.data: + return "Unsupported" + if ( + self.data["zoneLighting"] is not None and self.data["zoneLighting"]["activationData"] is not None + ): + return self.data["zoneLighting"]["activationData"][ + "value" + ] + return "Unsupported" + if self.sensor == "remoteStartStatus": + if self.data["remoteStartCountdownTimer"] is None: + return None + if self.data["remoteStartCountdownTimer"]["value"] > 0: + return "Active" + return "Inactive" + if self.sensor == "messages": + if self.coordinator.data["messages"] is None: + return None + return len(self.coordinator.data["messages"]) + if self.sensor == "dieselSystemStatus": + if self.data["dieselSystemStatus"]["filterRegenerationStatus"] is not None: + return self.data["dieselSystemStatus"]["filterRegenerationStatus"] + return "Not Supported" + if self.sensor == "exhaustFluidLevel": + if "value" in self.data["dieselSystemStatus"]["exhaustFluidLevel"]: + return self.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] + return "Not Supported" + return None + if ftype == "measurement": + if self.sensor == "odometer": + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + return "mi" + return "km" + if self.sensor == "fuel": + return "%" + if self.sensor == "battery": + return "%" + if self.sensor == "oil": + return "%" + if self.sensor == "tirePressure": + return None + if self.sensor == "gps": + return None + if self.sensor == "alarm": + return None + if self.sensor == "ignitionStatus": + return None + if self.sensor == "firmwareUpgInProgress": + return None + if self.sensor == "deepSleepInProgress": + return None + if self.sensor == "doorStatus": + return None + if self.sensor == "windowsPosition": + return None + if self.sensor == "lastRefresh": + return None + if self.sensor == "zoneLighting": + return None + if self.sensor == "remoteStartStatus": + return None + if self.sensor == "messages": + return "Messages" + if self.sensor == "elVeh": + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + return "mi" + return "km" + if self.sensor == "exhaustFluidLevel": + return "%" + return None + if ftype == "attribute": + if self.sensor == "odometer": + return self.data[self.sensor].items() + if self.sensor == "fuel": + if self.data["fuelRange"] is None: + return None + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + self.data["fuelRange"]["value"] = round( + float(self.data["fuelRange"]["value"]) / 1.60934 + ) + return {"fuelRange": self.data["fuelRange"]["value"]} + if self.sensor == "battery": + return { + "Battery Voltage": self.data["batteryVoltage"]["value"] + } + if self.sensor == "oil": + return self.data["oilLifeRemaining"].items() + if self.sensor == "tirePressure": + if self.data["tirePressure"] is not None: + _LOGGER.debug(self.fordoptions[CONF_PRESSURE_UNIT]) + if self.fordoptions[CONF_PRESSURE_UNIT] == "PSI": + _LOGGER.debug("PSIIIII") + sval = 0.1450377377 + rval = 1 + decimal = 0 + elif self.fordoptions[CONF_PRESSURE_UNIT] == "BAR": + sval = 0.01 + rval = 0.0689475729 + decimal = 2 + elif self.fordoptions[CONF_PRESSURE_UNIT] == "kPa": + sval = 1 + rval = 6.8947572932 + decimal = 0 + else: + _LOGGER.debug("HITT") + sval = 1 + rval = 1 + decimal = 0 + tirepress = {} + for value in self.data["tirePressure"]: + # if "recommended" in key: + # tirepress[key] = round(float(value["value"]) * rval, decimal) + # else: + tirepress[value["vehicleWheel"]] = round(float(value["value"]) * sval, decimal) + return tirepress + return None + if self.sensor == "gps": + if self.data["position"] is None: + return None + return self.data["position"].items() + if self.sensor == "alarm": + return self.data["alarmStatus"].items() + if self.sensor == "ignitionStatus": + return self.data[self.sensor].items() + if self.sensor == "firmwareUpgInProgress": + return self.data[self.sensor].items() + if self.sensor == "deepSleepInProgress": + return self.data[self.sensor].items() + if self.sensor == "doorStatus": + doors = {} + for value in self.data[self.sensor]: + doors[value["vehicleDoor"]] = value["value"] + return doors + if self.sensor == "windowPosition": + if "windowStatus" not in self.data: + return None + windows = {} + for window in self.data["windowStatus"]: + windows[window["vehicleWindow"]] = window + return windows + if self.sensor == "lastRefresh": + return None + if self.sensor == "elVeh": + if self.data["xevBatteryCapacity"] is None: + return None + elecs = {} + if ( + self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None + ): + elecs["xevBatteryCapacity"] = self.data["xevBatteryCapacity"]["value"] + if ( + self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None + ): + elecs["Plug Status"] = self.data["xevPlugChargerStatus"][ + "value" + ] + + if ( + self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None + ): + elecs["Charging Status"] = self.data[ + "xevBatteryChargeDisplayStatus" + ]["value"] + + if ( + self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None + ): + elecs["Charger Power Type"] = self.data[ + "xevChargeStationPowerType" + ]["value"] + + if ( + self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None + ): + elecs["Battery Charge Status"] = self.data[ + "xevChargeStationCommunicationStatus" + ]["value"] + + if ( + self.data["xevBatteryPerformanceStatus"] is not None and self.data["xevBatteryPerformanceStatus"]["value"] is not None + ): + elecs["Battery Performance Status"] = self.data[ + "xevBatteryPerformanceStatus" + ]["value"] + + return elecs + + if self.sensor == "elVehCharging": + if self.data["xevBatteryChargeEvent"] is None: + return None + elecs = {} + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Battery Charge Status"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargeDisplayStatus"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Percentage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryStateOfCharge"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Type"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Voltage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargerVoltageOutput"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Amperage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargerCurrentOutput"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + chAmps = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] + chVolt = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] + elecs["Charging kW"] = chVolt * chAmps + + return elecs + + if self.sensor == "zoneLighting": + if "zoneLighting" not in self.data: + return None + if ( + self.data[self.sensor] is not None and self.data[self.sensor]["zoneStatusData"] is not None + ): + zone = {} + if self.data[self.sensor]["zoneStatusData"] is not None: + for key, value in self.data[self.sensor][ + "zoneStatusData" + ].items(): + zone["zone_" + key] = value["value"] + + if ( + self.data[self.sensor]["lightSwitchStatusData"] + is not None + ): + for key, value in self.data[self.sensor][ + "lightSwitchStatusData" + ].items(): + if value is not None: + zone[key] = value["value"] + + if ( + self.data[self.sensor]["zoneLightingFaultStatus"] + is not None + ): + zone["zoneLightingFaultStatus"] = self.data[ + self.sensor + ]["zoneLightingFaultStatus"]["value"] + if ( + self.data[self.sensor][ + "zoneLightingShutDownWarning" + ] + is not None + ): + zone["zoneLightingShutDownWarning"] = self.data[ + self.sensor + ]["zoneLightingShutDownWarning"]["value"] + return zone + return None + if self.sensor == "remoteStartStatus": + if self.data["remoteStartCountdownTimer"] is None: + return None + return { "Countdown": self.data["remoteStartCountdownTimer"]["value"] } + if self.sensor == "messages": + if self.coordinator.data["messages"] is None: + return None + messages = {} + for value in self.coordinator.data["messages"]: + + messages[value["messageSubject"]] = value["createdDate"] + return messages + if self.sensor == "dieselSystemStatus": + return self.data["dieselSystemStatus"] + if self.sensor == "exhaustFluidLevel": + return self.data["dieselSystemStatus"] + return None + return None + + @property + def name(self): + """Return Sensor Name""" + return "fordpass_" + self.sensor + + @property + def state(self): + """Return Sensor State""" + return self.get_value("state") + + @property + def device_id(self): + """Return Sensor Device ID""" + return self.device_id + + @property + def extra_state_attributes(self): + """Return sensor attributes""" + return self.get_value("attribute") + + @property + def native_unit_of_measurement(self): + """Return sensor measurement""" + return self.get_value("measurement") + + @property + def icon(self): + """Return sensor icon""" + return SENSORS[self.sensor]["icon"] + + @property + def state_class(self): + """Return sensor state_class for statistics""" + if "state_class" in SENSORS[self.sensor]: + if SENSORS[self.sensor]["state_class"] == "total": + return SensorStateClass.TOTAL + if SENSORS[self.sensor]["state_class"] == "measurement": + return SensorStateClass.MEASUREMENT + return None + return None + + @property + def device_class(self): + """Return sensor device class for statistics""" + if "device_class" in SENSORS[self.sensor]: + if SENSORS[self.sensor]["device_class"] == "distance": + return SensorDeviceClass.DISTANCE + if SENSORS[self.sensor]["device_class"] == "timestamp": + return SensorDeviceClass.TIMESTAMP + return None \ No newline at end of file diff --git a/custom_components/fordpass/switch.py b/custom_components/fordpass/switch.py index f8ded13..1f5c277 100644 --- a/custom_components/fordpass/switch.py +++ b/custom_components/fordpass/switch.py @@ -15,17 +15,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # switches = [Switch(entry)] # async_add_entities(switches, False) - for key in SWITCHES: - switch = Switch(entry, key, config_entry.options) + for key, value in SWITCHES.items(): + sw = Switch(entry, key, config_entry.options) # Only add guard entity if supported by the car if key == "guardmode": - if "guardstatus" in switch.coordinator.data: - if switch.coordinator.data["guardstatus"]["returnCode"] == 200: - async_add_entities([switch], False) + if "guardstatus" in sw.coordinator.data: + if sw.coordinator.data["guardstatus"]["returnCode"] == 200: + async_add_entities([sw], False) else: _LOGGER.debug("Guard mode not supported on this vehicle") else: - async_add_entities([switch], False) + async_add_entities([sw], False) class Switch(FordPassEntity, SwitchEntity): @@ -33,15 +33,10 @@ class Switch(FordPassEntity, SwitchEntity): def __init__(self, coordinator, switch, options): """Initialize""" - super().__init__( - device_id="fordpass_doorlock", - name="fordpass_doorlock", - coordinator=coordinator, - ) - self._device_id = "fordpass_" + switch self.switch = switch self.coordinator = coordinator + self.data = coordinator.data["metrics"] # Required for HA 2022.7 self.coordinator_context = object() @@ -88,10 +83,11 @@ def is_on(self): """Check status of switch""" if self.switch == "ignition": if ( - self.coordinator.data is None or self.coordinator.data["remoteStartStatus"] is None + self.data is None or self.data["ignitionStatus"] is None ): return None - return self.coordinator.data["remoteStartStatus"]["value"] + if self.data["ignitionStatus"]["value"] == "OFF": + return False if self.switch == "guardmode": # Need to find the correct response for enabled vs disabled so this may be spotty at the moment guardstatus = self.coordinator.data["guardstatus"] @@ -109,4 +105,4 @@ def is_on(self): @property def icon(self): """Return icon for switch""" - return SWITCHES[self.switch]["icon"] + return SWITCHES[self.switch]["icon"] \ No newline at end of file From 12d0993a01647b73f81c28434b000259e4e2bf30 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 22:02:40 -0400 Subject: [PATCH 30/63] Added elVehCharging sensor --- custom_components/fordpass/sensor.py | 53 +++ custom_components/fordpass/sensorEV.py | 490 ------------------------- 2 files changed, 53 insertions(+), 490 deletions(-) delete mode 100644 custom_components/fordpass/sensorEV.py diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index d91df42..063b71c 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -30,6 +30,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): elif key == "elVeh": if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: sensors.append(sensor) + elif key == "elVehCharging": + if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: + sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): @@ -325,6 +328,56 @@ def get_value(self, ftype): ]["value"] return elecs + + if self.sensor == "elVehCharging": + if self.data["xevBatteryChargeEvent"] is None: + return None + elecs = {} + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Battery Charge Status"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargeDisplayStatus"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Percentage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryStateOfCharge"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Type"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Voltage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargerVoltageOutput"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Amperage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargerCurrentOutput"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + chAmps = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] + chVolt = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] + elecs["Charging kW"] = chVolt * chAmps + + return elecs + if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return None diff --git a/custom_components/fordpass/sensorEV.py b/custom_components/fordpass/sensorEV.py deleted file mode 100644 index 063b71c..0000000 --- a/custom_components/fordpass/sensorEV.py +++ /dev/null @@ -1,490 +0,0 @@ -"""All vehicle sensors from the accessible by the API""" -import logging -from datetime import datetime - -from homeassistant.util import dt - -from homeassistant.components.sensor import ( - SensorEntity, - SensorDeviceClass, - SensorStateClass -) - -from . import FordPassEntity -from .const import CONF_DISTANCE_UNIT, CONF_PRESSURE_UNIT, DOMAIN, SENSORS, COORDINATOR, DISTANCE_CONVERSION_DISABLED - - -_LOGGER = logging.getLogger(__name__) - - -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][COORDINATOR] - sensors = [] - for key in SENSORS: - sensor = CarSensor(entry, key, config_entry.options) - # Add support for only adding compatible sensors for the given vehicle - if key == "zoneLighting": - if "zoneLighting" in sensor.coordinator.data: - sensors.append(sensor) - elif key == "elVeh": - if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: - sensors.append(sensor) - elif key == "elVehCharging": - if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: - sensors.append(sensor) - elif key == "dieselSystemStatus": - if sensor.coordinator.data.get("dieselSystemStatus", {}): - if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): - sensors.append(sensor) - elif key == "exhaustFluidLevel": - if sensor.coordinator.data.get("dieselSystemStatus", {}): - if sensor.coordinator.data.get("dieselSystemStatus", {}).get("exhaustFluidLevel"): - sensors.append(sensor) - else: - sensors.append(sensor) - async_add_entities(sensors, True) - - -class CarSensor( - FordPassEntity, - SensorEntity, -): - def __init__(self, coordinator, sensor, options): - - super().__init__( - device_id="fordpass_" + sensor, - name="fordpass_" + sensor, - coordinator=coordinator - ) - - self.sensor = sensor - self.fordoptions = options - self._attr = {} - self.coordinator = coordinator - self.data = coordinator.data["metrics"] - self._device_id = "fordpass_" + sensor - # Required for HA 2022.7 - self.coordinator_context = object() - - def get_value(self, ftype): - """Get sensor value and attributes from coordinator data""" - if ftype == "state": - if self.sensor == "odometer": - if self.fordoptions[CONF_DISTANCE_UNIT] is not None: - if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - if DISTANCE_CONVERSION_DISABLED in self.fordoptions and self.fordoptions[DISTANCE_CONVERSION_DISABLED] is True: - return self.data[self.sensor]["value"] - return round( - float(self.data[self.sensor]["value"]) / 1.60934 - ) - return self.data[self.sensor]["value"] - return self.data[self.sensor]["value"] - if self.sensor == "fuel": - if "fuelLevel" in self.data: - if self.data["fuelLevel"] is None: - return None - return round(self.data["fuelLevel"]["value"]) - elif "xevBatteryStateOfCharge": - return round(self.data["xevBatteryStateOfCharge"]["value"]) - else: - return None - if self.sensor == "battery": - return round(self.data["batteryStateOfCharge"]["value"]) - if self.sensor == "oil": - return round(self.data["oilLifeRemaining"]["value"]) - if self.sensor == "tirePressure": - return self.data["tirePressureSystemStatus"][0]["value"] - if self.sensor == "gps": - if self.data["position"] is None: - return "Unsupported" - return self.data["position"]["value"] - if self.sensor == "alarm": - return self.data["alarmStatus"]["value"] - if self.sensor == "ignitionStatus": - return self.data[self.sensor]["value"] - if self.sensor == "firmwareUpgInProgress": - return self.data[self.sensor]["value"] - if self.sensor == "deepSleepInProgress": - return self.data[self.sensor]["value"] - if self.sensor == "doorStatus": - for value in self.data["doorStatus"]: - if value["value"] == "Invalid": - continue - if value["value"] != "CLOSED": - return "Open" - return "Closed" - if self.sensor == "windowPosition": - if "windowStatus" in self.data: - if self.data["windowStatus"] is None: - return "Unsupported" - status = "Closed" - for window in self.data["windowStatus"]: - windowrange = window["value"]["doubleRange"] - if windowrange["lowerBound"] != 0.0 and windowrange["upperBound"] != 0.0: - status = "Open" - return status - return "Unsupported" - if self.sensor == "lastRefresh": - return dt.as_local( - datetime.strptime( - self.coordinator.data["updateTime"], "%Y-%m-%dT%H:%M:%S.%fz" - ) - ) - if self.sensor == "elVeh": - if "xevBatteryRange" in self.data: - if self.fordoptions[CONF_DISTANCE_UNIT] is not None: - if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - return round( - float(self.data["xevBatteryRange"]["value"]) / 1.60934 - ) - return float(self.data["xevBatteryRange"]["value"]) - return float(self.data["xevBatteryRange"]["value"]) - return "Unsupported" - if self.sensor == "zoneLighting": - if "zoneLighting" not in self.data: - return "Unsupported" - if ( - self.data["zoneLighting"] is not None and self.data["zoneLighting"]["activationData"] is not None - ): - return self.data["zoneLighting"]["activationData"][ - "value" - ] - return "Unsupported" - if self.sensor == "remoteStartStatus": - if self.data["remoteStartCountdownTimer"] is None: - return None - if self.data["remoteStartCountdownTimer"]["value"] > 0: - return "Active" - return "Inactive" - if self.sensor == "messages": - if self.coordinator.data["messages"] is None: - return None - return len(self.coordinator.data["messages"]) - if self.sensor == "dieselSystemStatus": - if self.data["dieselSystemStatus"]["filterRegenerationStatus"] is not None: - return self.data["dieselSystemStatus"]["filterRegenerationStatus"] - return "Not Supported" - if self.sensor == "exhaustFluidLevel": - if "value" in self.data["dieselSystemStatus"]["exhaustFluidLevel"]: - return self.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] - return "Not Supported" - return None - if ftype == "measurement": - if self.sensor == "odometer": - if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - return "mi" - return "km" - if self.sensor == "fuel": - return "%" - if self.sensor == "battery": - return "%" - if self.sensor == "oil": - return "%" - if self.sensor == "tirePressure": - return None - if self.sensor == "gps": - return None - if self.sensor == "alarm": - return None - if self.sensor == "ignitionStatus": - return None - if self.sensor == "firmwareUpgInProgress": - return None - if self.sensor == "deepSleepInProgress": - return None - if self.sensor == "doorStatus": - return None - if self.sensor == "windowsPosition": - return None - if self.sensor == "lastRefresh": - return None - if self.sensor == "zoneLighting": - return None - if self.sensor == "remoteStartStatus": - return None - if self.sensor == "messages": - return "Messages" - if self.sensor == "elVeh": - if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - return "mi" - return "km" - if self.sensor == "exhaustFluidLevel": - return "%" - return None - if ftype == "attribute": - if self.sensor == "odometer": - return self.data[self.sensor].items() - if self.sensor == "fuel": - if self.data["fuelRange"] is None: - return None - if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - self.data["fuelRange"]["value"] = round( - float(self.data["fuelRange"]["value"]) / 1.60934 - ) - return {"fuelRange": self.data["fuelRange"]["value"]} - if self.sensor == "battery": - return { - "Battery Voltage": self.data["batteryVoltage"]["value"] - } - if self.sensor == "oil": - return self.data["oilLifeRemaining"].items() - if self.sensor == "tirePressure": - if self.data["tirePressure"] is not None: - _LOGGER.debug(self.fordoptions[CONF_PRESSURE_UNIT]) - if self.fordoptions[CONF_PRESSURE_UNIT] == "PSI": - _LOGGER.debug("PSIIIII") - sval = 0.1450377377 - rval = 1 - decimal = 0 - elif self.fordoptions[CONF_PRESSURE_UNIT] == "BAR": - sval = 0.01 - rval = 0.0689475729 - decimal = 2 - elif self.fordoptions[CONF_PRESSURE_UNIT] == "kPa": - sval = 1 - rval = 6.8947572932 - decimal = 0 - else: - _LOGGER.debug("HITT") - sval = 1 - rval = 1 - decimal = 0 - tirepress = {} - for value in self.data["tirePressure"]: - # if "recommended" in key: - # tirepress[key] = round(float(value["value"]) * rval, decimal) - # else: - tirepress[value["vehicleWheel"]] = round(float(value["value"]) * sval, decimal) - return tirepress - return None - if self.sensor == "gps": - if self.data["position"] is None: - return None - return self.data["position"].items() - if self.sensor == "alarm": - return self.data["alarmStatus"].items() - if self.sensor == "ignitionStatus": - return self.data[self.sensor].items() - if self.sensor == "firmwareUpgInProgress": - return self.data[self.sensor].items() - if self.sensor == "deepSleepInProgress": - return self.data[self.sensor].items() - if self.sensor == "doorStatus": - doors = {} - for value in self.data[self.sensor]: - doors[value["vehicleDoor"]] = value["value"] - return doors - if self.sensor == "windowPosition": - if "windowStatus" not in self.data: - return None - windows = {} - for window in self.data["windowStatus"]: - windows[window["vehicleWindow"]] = window - return windows - if self.sensor == "lastRefresh": - return None - if self.sensor == "elVeh": - if self.data["xevBatteryCapacity"] is None: - return None - elecs = {} - if ( - self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None - ): - elecs["xevBatteryCapacity"] = self.data["xevBatteryCapacity"]["value"] - if ( - self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None - ): - elecs["Plug Status"] = self.data["xevPlugChargerStatus"][ - "value" - ] - - if ( - self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None - ): - elecs["Charging Status"] = self.data[ - "xevBatteryChargeDisplayStatus" - ]["value"] - - if ( - self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None - ): - elecs["Charger Power Type"] = self.data[ - "xevChargeStationPowerType" - ]["value"] - - if ( - self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None - ): - elecs["Battery Charge Status"] = self.data[ - "xevChargeStationCommunicationStatus" - ]["value"] - - if ( - self.data["xevBatteryPerformanceStatus"] is not None and self.data["xevBatteryPerformanceStatus"]["value"] is not None - ): - elecs["Battery Performance Status"] = self.data[ - "xevBatteryPerformanceStatus" - ]["value"] - - return elecs - - if self.sensor == "elVehCharging": - if self.data["xevBatteryChargeEvent"] is None: - return None - elecs = {} - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Battery Charge Status"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargeDisplayStatus"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Percentage"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryStateOfCharge"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Type"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Voltage"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargerVoltageOutput"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Amperage"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargerCurrentOutput"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - chAmps = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] - chVolt = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] - elecs["Charging kW"] = chVolt * chAmps - - return elecs - - if self.sensor == "zoneLighting": - if "zoneLighting" not in self.data: - return None - if ( - self.data[self.sensor] is not None and self.data[self.sensor]["zoneStatusData"] is not None - ): - zone = {} - if self.data[self.sensor]["zoneStatusData"] is not None: - for key, value in self.data[self.sensor][ - "zoneStatusData" - ].items(): - zone["zone_" + key] = value["value"] - - if ( - self.data[self.sensor]["lightSwitchStatusData"] - is not None - ): - for key, value in self.data[self.sensor][ - "lightSwitchStatusData" - ].items(): - if value is not None: - zone[key] = value["value"] - - if ( - self.data[self.sensor]["zoneLightingFaultStatus"] - is not None - ): - zone["zoneLightingFaultStatus"] = self.data[ - self.sensor - ]["zoneLightingFaultStatus"]["value"] - if ( - self.data[self.sensor][ - "zoneLightingShutDownWarning" - ] - is not None - ): - zone["zoneLightingShutDownWarning"] = self.data[ - self.sensor - ]["zoneLightingShutDownWarning"]["value"] - return zone - return None - if self.sensor == "remoteStartStatus": - if self.data["remoteStartCountdownTimer"] is None: - return None - return { "Countdown": self.data["remoteStartCountdownTimer"]["value"] } - if self.sensor == "messages": - if self.coordinator.data["messages"] is None: - return None - messages = {} - for value in self.coordinator.data["messages"]: - - messages[value["messageSubject"]] = value["createdDate"] - return messages - if self.sensor == "dieselSystemStatus": - return self.data["dieselSystemStatus"] - if self.sensor == "exhaustFluidLevel": - return self.data["dieselSystemStatus"] - return None - return None - - @property - def name(self): - """Return Sensor Name""" - return "fordpass_" + self.sensor - - @property - def state(self): - """Return Sensor State""" - return self.get_value("state") - - @property - def device_id(self): - """Return Sensor Device ID""" - return self.device_id - - @property - def extra_state_attributes(self): - """Return sensor attributes""" - return self.get_value("attribute") - - @property - def native_unit_of_measurement(self): - """Return sensor measurement""" - return self.get_value("measurement") - - @property - def icon(self): - """Return sensor icon""" - return SENSORS[self.sensor]["icon"] - - @property - def state_class(self): - """Return sensor state_class for statistics""" - if "state_class" in SENSORS[self.sensor]: - if SENSORS[self.sensor]["state_class"] == "total": - return SensorStateClass.TOTAL - if SENSORS[self.sensor]["state_class"] == "measurement": - return SensorStateClass.MEASUREMENT - return None - return None - - @property - def device_class(self): - """Return sensor device class for statistics""" - if "device_class" in SENSORS[self.sensor]: - if SENSORS[self.sensor]["device_class"] == "distance": - return SensorDeviceClass.DISTANCE - if SENSORS[self.sensor]["device_class"] == "timestamp": - return SensorDeviceClass.TIMESTAMP - return None \ No newline at end of file From 24b3daba7a9b350a978cfce52cfdde7bf23803c5 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 22:07:52 -0400 Subject: [PATCH 31/63] Beta4 --- README.md | 3 -- custom_components/fordpass/__init__.py | 2 +- custom_components/fordpass/config_flow.py | 2 +- custom_components/fordpass/const.py | 3 +- custom_components/fordpass/device_tracker.py | 2 +- custom_components/fordpass/fordpass_new.py | 2 +- custom_components/fordpass/lock.py | 2 +- custom_components/fordpass/manifest.json | 32 ++++++------ custom_components/fordpass/sensor.py | 55 +------------------- custom_components/fordpass/switch.py | 2 +- info.md | 8 +++ 11 files changed, 32 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 7692b9b..cd250d8 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ - https://github.com/JacobWasFramed - Updated unit conversions - https://github.com/heehoo59 - French Translation -## As of 10/10/2023 Ford has switched to a new API! -This has caused the integration to stop working, I have started work on integrating the new API and there is a Beta but there is a lot of changes so it will take time to get it back and 100% operational again. - ## Account Warning (Sep 2023) A number of users have encountered their accounts being banned for containing "+" symbols in their email. It appears Ford thinks this is a disposable email. So if you have a + in your email I recommend changing it. diff --git a/custom_components/fordpass/__init__.py b/custom_components/fordpass/__init__.py index edde988..41eaa72 100644 --- a/custom_components/fordpass/__init__.py +++ b/custom_components/fordpass/__init__.py @@ -269,4 +269,4 @@ def device_info(self): "name": f"{VEHICLE} ({self.coordinator.vin})", "model": f"{model}", "manufacturer": MANUFACTURER, - } \ No newline at end of file + } diff --git a/custom_components/fordpass/config_flow.py b/custom_components/fordpass/config_flow.py index 699fb28..bd7c2bb 100644 --- a/custom_components/fordpass/config_flow.py +++ b/custom_components/fordpass/config_flow.py @@ -187,4 +187,4 @@ class InvalidAuth(exceptions.HomeAssistantError): class InvalidVin(exceptions.HomeAssistantError): - """Error to indicate the wrong vin""" \ No newline at end of file + """Error to indicate the wrong vin""" diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index 6c0da58..fc47d94 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,7 +42,6 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, - "elVehCharging": {"icon": "mdi:ev-station"}, # "deepSleepInProgress": { # "icon": "mdi:power-sleep", # "name": "Deep Sleep Mode Active", @@ -76,4 +75,4 @@ SWITCHES = { "ignition": {"icon": "hass:power"}, # "guardmode": {"icon": "mdi:shield-key"} -} \ No newline at end of file +} diff --git a/custom_components/fordpass/device_tracker.py b/custom_components/fordpass/device_tracker.py index c9d1366..c675c7d 100644 --- a/custom_components/fordpass/device_tracker.py +++ b/custom_components/fordpass/device_tracker.py @@ -65,4 +65,4 @@ def extra_state_attributes(self): @property def icon(self): """Return device tracker icon""" - return "mdi:radar" \ No newline at end of file + return "mdi:radar" diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 952ee28..f6e5829 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -574,4 +574,4 @@ def __request_and_poll(self, method, url): if "commandId" in result: return self.__poll_status(url, result["commandId"]) return False - return False \ No newline at end of file + return False diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index deed51d..c6f9d1d 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -73,4 +73,4 @@ def icon(self): @property def name(self): """Return Name""" - return "fordpass_doorlock" \ No newline at end of file + return "fordpass_doorlock" diff --git a/custom_components/fordpass/manifest.json b/custom_components/fordpass/manifest.json index e11203f..a02b2cd 100644 --- a/custom_components/fordpass/manifest.json +++ b/custom_components/fordpass/manifest.json @@ -1,17 +1,17 @@ { - "domain": "fordpass", - "name": "FordPass", - "codeowners": ["@itchannel"], - "config_flow": true, - "dependencies": [], - "documentation": "https://github.com/itchannel/fordpass-ha", - "homekit": {}, - "integration_type": "device", - "iot_class": "cloud_polling", - "issue_tracker": "https://github.com/itchannel/fordpass-ha/issues", - "loggers": ["custom_components.fordpass"], - "requirements": [], - "ssdp": [], - "version": "0.1.53", - "zeroconf": [] -} \ No newline at end of file + "domain": "fordpass", + "name": "FordPass", + "codeowners": ["@itchannel"], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/itchannel/fordpass-ha", + "homekit": {}, + "integration_type": "device", + "iot_class": "cloud_polling", + "issue_tracker": "https://github.com/itchannel/fordpass-ha/issues", + "loggers": ["custom_components.fordpass"], + "requirements": [], + "ssdp": [], + "version": "0.1.53", + "zeroconf": [] + } \ No newline at end of file diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 063b71c..8c8c36f 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -30,9 +30,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): elif key == "elVeh": if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: sensors.append(sensor) - elif key == "elVehCharging": - if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: - sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): @@ -328,56 +325,6 @@ def get_value(self, ftype): ]["value"] return elecs - - if self.sensor == "elVehCharging": - if self.data["xevBatteryChargeEvent"] is None: - return None - elecs = {} - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Battery Charge Status"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargeDisplayStatus"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Percentage"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryStateOfCharge"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Type"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Voltage"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargerVoltageOutput"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - elecs["Charging Amperage"] = self.data[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargerCurrentOutput"]["value"] - - if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None - ): - chAmps = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] - chVolt = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] - elecs["Charging kW"] = chVolt * chAmps - - return elecs - if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return None @@ -487,4 +434,4 @@ def device_class(self): return SensorDeviceClass.DISTANCE if SENSORS[self.sensor]["device_class"] == "timestamp": return SensorDeviceClass.TIMESTAMP - return None \ No newline at end of file + return None diff --git a/custom_components/fordpass/switch.py b/custom_components/fordpass/switch.py index 1f5c277..4fa2e2b 100644 --- a/custom_components/fordpass/switch.py +++ b/custom_components/fordpass/switch.py @@ -105,4 +105,4 @@ def is_on(self): @property def icon(self): """Return icon for switch""" - return SWITCHES[self.switch]["icon"] \ No newline at end of file + return SWITCHES[self.switch]["icon"] diff --git a/info.md b/info.md index adc5f13..ae0048a 100644 --- a/info.md +++ b/info.md @@ -1,4 +1,12 @@ ## **Changelog** +### Version 1.53 +*Warning this version is only a BETA and has a lot broken due to the massive API changes from Ford use at your own risk or wait a week!* +- Updated vehicle endpoint to use new Autonomics API +- Added secondary Autonomic token +- Remapped commands to use new "command" API endpoint +- Remapped existing sensors to new json variables (Some are missinge) + +There is a LOT more coming soon as the new API exposes an excessive amount of information including speed, pedal position, crash sensors and way more. ### Version 1.52 - Update for discontinued API endpoints (Update, lock, remote start) ### Version 1.51 From 1409bda355c236971238c4444ebd08bcf3e65b66 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 22:09:01 -0400 Subject: [PATCH 32/63] Added elVehCharging sensor --- custom_components/fordpass/const.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index fc47d94..c161313 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,6 +42,8 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, + "elVehCharging": {"icon": "mdi:ev-station"}, + # "deepSleepInProgress": { # "icon": "mdi:power-sleep", # "name": "Deep Sleep Mode Active", From 2f060dd784d37a21b1321a7d41508841f21566e3 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 22:20:56 -0400 Subject: [PATCH 33/63] Fixed display for elVeh Battery --- custom_components/fordpass/sensor.py | 59 ++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 8c8c36f..2cda32c 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -10,7 +10,7 @@ SensorStateClass ) -from . import FordPassEntity +from ..fordpass-ha import FordPassEntity from .const import CONF_DISTANCE_UNIT, CONF_PRESSURE_UNIT, DOMAIN, SENSORS, COORDINATOR, DISTANCE_CONVERSION_DISABLED @@ -30,6 +30,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): elif key == "elVeh": if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: sensors.append(sensor) + elif key == "elVehCharging": + if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: + sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): @@ -288,7 +291,7 @@ def get_value(self, ftype): if ( self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None ): - elecs["xevBatteryCapacity"] = self.data["xevBatteryCapacity"]["value"] + elecs["Battery State Of Charge"] = self.data["xevBatteryStateOfCharge"]["value"] if ( self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None ): @@ -325,6 +328,56 @@ def get_value(self, ftype): ]["value"] return elecs + + if self.sensor == "elVehCharging": + if self.data["xevBatteryChargeEvent"] is None: + return None + elecs = {} + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Battery Charge Status"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargeDisplayStatus"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Percentage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryStateOfCharge"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Type"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Voltage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargerVoltageOutput"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + elecs["Charging Amperage"] = self.data[ + "xevBatteryChargeEvent" + ]["value"]["xevBatteryChargerCurrentOutput"]["value"] + + if ( + self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + ): + chAmps = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] + chVolt = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] + elecs["Charging kW"] = chVolt * chAmps + + return elecs + if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return None @@ -434,4 +487,4 @@ def device_class(self): return SensorDeviceClass.DISTANCE if SENSORS[self.sensor]["device_class"] == "timestamp": return SensorDeviceClass.TIMESTAMP - return None + return None \ No newline at end of file From bdf63ff75b5cdb43b885187cf7bf56fc9c72d2a1 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 12:40:07 +1000 Subject: [PATCH 34/63] fix lock function sending wrong command --- custom_components/fordpass/fordpass_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index f6e5829..cc129cb 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -458,7 +458,7 @@ def lock(self): """ Issue a lock command to the doors """ - return self.__request_and_poll_command("unlock") + return self.__request_and_poll_command("lock") def unlock(self): """ From 921e55241b58790f53b5f42cbace129cc1493683 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 22:40:14 -0400 Subject: [PATCH 35/63] weird update issue --- custom_components/fordpass/sensor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 2cda32c..1a7de44 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -10,7 +10,7 @@ SensorStateClass ) -from ..fordpass-ha import FordPassEntity +from . import FordPassEntity from .const import CONF_DISTANCE_UNIT, CONF_PRESSURE_UNIT, DOMAIN, SENSORS, COORDINATOR, DISTANCE_CONVERSION_DISABLED @@ -285,13 +285,13 @@ def get_value(self, ftype): if self.sensor == "lastRefresh": return None if self.sensor == "elVeh": - if self.data["xevBatteryCapacity"] is None: + if self.data["xevBatteryStateOfCharge"] is None: return None elecs = {} if ( - self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None + self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None ): - elecs["Battery State Of Charge"] = self.data["xevBatteryStateOfCharge"]["value"] + elecs["Battery State of Charge"] = self.data["xevBatteryStateOfCharge"]["value"] if ( self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None ): @@ -328,7 +328,7 @@ def get_value(self, ftype): ]["value"] return elecs - + if self.sensor == "elVehCharging": if self.data["xevBatteryChargeEvent"] is None: return None From f65b76bfc8c2bbb53823fa106aeb633deced0c5a Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 23:17:16 -0400 Subject: [PATCH 36/63] fixed for sensor to read from events, not metrics --- custom_components/fordpass/sensor.py | 35 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 1a7de44..0c0aaf0 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -63,6 +63,7 @@ def __init__(self, coordinator, sensor, options): self._attr = {} self.coordinator = coordinator self.data = coordinator.data["metrics"] + self.events = coordinator.data["events"] self._device_id = "fordpass_" + sensor # Required for HA 2022.7 self.coordinator_context = object() @@ -330,53 +331,53 @@ def get_value(self, ftype): return elecs if self.sensor == "elVehCharging": - if self.data["xevBatteryChargeEvent"] is None: + if self.events["xevBatteryChargeEvent"] is None: return None - elecs = {} + cs = {} if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None ): - elecs["Battery Charge Status"] = self.data[ + cs["Battery Charge Status"] = self.events[ "xevBatteryChargeEvent" ]["value"]["xevBatteryChargeDisplayStatus"]["value"] if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None ): - elecs["Charging Percentage"] = self.data[ + cs["Charging Percentage"] = self.events[ "xevBatteryChargeEvent" ]["value"]["xevBatteryStateOfCharge"]["value"] if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None ): - elecs["Charging Type"] = self.data[ + cs["Charging Type"] = self.events[ "xevBatteryChargeEvent" ]["value"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None ): - elecs["Charging Voltage"] = self.data[ + cs["Charging Voltage"] = self.events[ "xevBatteryChargeEvent" ]["value"]["xevBatteryChargerVoltageOutput"]["value"] if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None ): - elecs["Charging Amperage"] = self.data[ + cs["Charging Amperage"] = self.events[ "xevBatteryChargeEvent" ]["value"]["xevBatteryChargerCurrentOutput"]["value"] if ( - self.data["xevBatteryChargeEvent"] is not None and self.data["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None ): - chAmps = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] - chVolt = self.data["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] - elecs["Charging kW"] = chVolt * chAmps + chAmps = self.events["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] + chVolt = self.events["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] + cs["Charging kW"] = chVolt * chAmps - return elecs + return cs if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: From c01e95c6057a53fd1e943784bc67a2072a6861b7 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Tue, 10 Oct 2023 23:58:34 -0400 Subject: [PATCH 37/63] sensor work in progress --- custom_components/fordpass/sensor.py | 37 ++++++++++------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 0c0aaf0..bb883c3 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -335,46 +335,35 @@ def get_value(self, ftype): return None cs = {} - if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None + if (self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None ): - cs["Battery Charge Status"] = self.events[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargeDisplayStatus"]["value"] + cs["Battery Charge Status"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None ): - cs["Charging Percentage"] = self.events[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryStateOfCharge"]["value"] + cs["Charging Type"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None ): - cs["Charging Type"] = self.events[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] + cs["Charging Percentage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None ): - cs["Charging Voltage"] = self.events[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargerVoltageOutput"]["value"] + cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None ): - cs["Charging Amperage"] = self.events[ - "xevBatteryChargeEvent" - ]["value"]["xevBatteryChargerCurrentOutput"]["value"] + cs["Charging Amperage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["value"] is not None + self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None ): - chAmps = self.events["xevBatteryChargeEvent"]["value"]["xevBatteryChargerCurrentOutput"]["value"] - chVolt = self.events["xevBatteryChargeEvent"]["value"]["xevBatteryChargerVoltageOutput"]["value"] + chAmps = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] + chVolt = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] cs["Charging kW"] = chVolt * chAmps return cs From af57dc61a43ef2e585707ff89353205a1b49c2c5 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 00:18:12 -0400 Subject: [PATCH 38/63] more sensor updates during debugging --- custom_components/fordpass/sensor.py | 31 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index bb883c3..e81581a 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -333,39 +333,44 @@ def get_value(self, ftype): if self.sensor == "elVehCharging": if self.events["xevBatteryChargeEvent"] is None: return None - cs = {} - if (self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None - ): - cs["Battery Charge Status"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] + cs = {} if ( self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None ): - cs["Charging Type"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] + cs["Charging Percentage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] + if (self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] is not None + ): + cs["Battery Charge Status"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None + self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] is not None ): - cs["Charging Percentage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] + cs["Charging Type"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None + self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None ): cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] - if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None + self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None ): cs["Charging Amperage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] - if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None + self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None ): + cs["Charging Amperage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] chAmps = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] + if ( + self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None + ): + cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] chVolt = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] + if ( + self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None + ): cs["Charging kW"] = chVolt * chAmps - return cs if self.sensor == "zoneLighting": From 34b39921f48844a2c70adc7c967b4af35be420c1 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 00:25:33 -0400 Subject: [PATCH 39/63] more sensor updates during debugging --- custom_components/fordpass/sensor.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index e81581a..f826b8d 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -337,10 +337,9 @@ def get_value(self, ftype): cs = {} if ( - self.events["xevBatteryChargeEvent"] is not None and self.events["xevBatteryChargeEvent"]["metrics"] is not None + self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] is not None ): cs["Charging Percentage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] - if (self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] is not None ): cs["Battery Charge Status"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] @@ -348,17 +347,12 @@ def get_value(self, ftype): self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] is not None ): cs["Charging Type"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] - if ( self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None ): cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] if ( self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None - ): - cs["Charging Amperage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] - if ( - self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None ): cs["Charging Amperage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] chAmps = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] @@ -368,7 +362,7 @@ def get_value(self, ftype): cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] chVolt = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] if ( - self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None + chAmps is not None and chVolt is not None ): cs["Charging kW"] = chVolt * chAmps return cs From ced1d1db352c1e09eeabeb91fc842b74fc90c5d6 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 00:26:44 -0400 Subject: [PATCH 40/63] more sensor updates during debugging --- custom_components/fordpass/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index f826b8d..14e0930 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -362,7 +362,7 @@ def get_value(self, ftype): cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] chVolt = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] if ( - chAmps is not None and chVolt is not None + self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None ): cs["Charging kW"] = chVolt * chAmps return cs From 1bf7c5bd541001e7f96595f775ae17eea255cdc1 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 18:21:41 +1000 Subject: [PATCH 41/63] Saving Auto token locally and checking expiry --- custom_components/fordpass/fordpass_new.py | 53 ++++++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index cc129cb..81f36c0 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -60,6 +60,7 @@ def __init__( self.expires_at = None self.refresh_token = None self.auto_token = None + self.auto_expires_at = None retry = Retry(connect=3, backoff_factor=0.5) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) @@ -188,15 +189,19 @@ def auth(self): self.token = result["access_token"] self.refresh_token = result["refresh_token"] self.expires_at = time.time() + result["expires_in"] + auto_token = self.get_auto_token() + self.auto_token = auto_token["access_token"] + self.auto_expires_at = time.time() + result["expires_in"] if self.save_token: result["expiry_date"] = time.time() + result["expires_in"] + result["auto_token"] = auto_token["access_token"] + result["auto_refresh"] = auto_token["refresh_token"] + result["auto_expiry"] = time.time() + auto_token["expires_in"] + self.write_token(result) session.cookies.clear() return True response.raise_for_status() - # Code to get Auto token - if self.get_auto_token(): - return True return False def refresh_token_func(self, token): @@ -221,9 +226,12 @@ def refresh_token_func(self, token): _LOGGER.debug("401 response stage 2: refresh stage 1 token") self.auth() + + def __acquire_token(self): # Fetch and refresh token as needed # If file exists read in token file and check it's valid + _LOGGER.debug("Fetching token") if self.save_token: if os.path.isfile(self.token_location): data = self.read_token() @@ -232,24 +240,39 @@ def __acquire_token(self): data["access_token"] = self.token data["refresh_token"] = self.refresh_token data["expiry_date"] = self.expires_at + data["auto_token"] = self.auto_token + data["auto_expiry"] = self.auto_expires_at else: data = {} data["access_token"] = self.token data["refresh_token"] = self.refresh_token data["expiry_date"] = self.expires_at + data["auto_token"] = self.auto_token + data["auto_expiry"] = self.auto_expires_at self.token = data["access_token"] self.expires_at = data["expiry_date"] + _LOGGER.debug(self.auto_token) + _LOGGER.debug(self.auto_expires_at) + if self.auto_token is None or self.auto_expires_at is None: + self.auth() + pass + self.auto_token = data["auto_token"] + self.auto_expires_at = data["auto_expiry"] if self.expires_at: if time.time() >= self.expires_at: _LOGGER.debug("No token, or has expired, requesting new token") self.refresh_token_func(data) # self.auth() + if self.auto_expires_at: + if time.time() >= self.auto_expires_at: + _LOGGER.debug("Autonomic token expired") + self.auth() if self.token is None: + _LOGGER.debug("Fetching token4") # No existing token exists so refreshing library self.auth() else: _LOGGER.debug("Token is valid, continuing") - self.get_auto_token() def write_token(self, token): """Save token to file for reuse""" @@ -308,7 +331,7 @@ def get_auto_token(self): _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) self.auto_token = result["access_token"] - return True + return result return False def status(self): @@ -323,6 +346,7 @@ def status(self): "auth-token": self.token, "Application-Id": self.region, } + _LOGGER.debug(self.auto_token) if NEW_API: headers = { @@ -334,7 +358,7 @@ def status(self): f"{AUTONOMIC_URL}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers ) if r.status_code == 200: - # _LOGGER.debug(r.text) + _LOGGER.debug(r.text) result = r.json() return result else: @@ -419,7 +443,7 @@ def vehicles(self): _LOGGER.debug(result) return result - # _LOGGER.debug(response.text) + _LOGGER.debug(response.text) response.raise_for_status() return None @@ -496,8 +520,10 @@ def request_update(self, vin=""): vinnum = vin else: vinnum = self.vin - status = self.__requestAndPollCommand("statusRefresh", vinnum) - return status + status = self.__make_request( + "PUT", f"{BASE_URL}/vehicles/v5/{vinnum}/status", None, None + ) + return status.json()["status"] def __make_request(self, method, url, data, params): """ @@ -546,23 +572,20 @@ def __request_and_poll_command(self, command, vin=None): } if vin is None: r = session.post( - f"{AUTONOMIC_URL}/command/vehicles/{self.vin}/commands", + f"{AUTONOMIC_URL}command/vehicles/{self.vin}/commands", data=json.dumps(data), headers=headers ) - else: r = session.post( - f"{AUTONOMIC_URL}/command/vehicles/{vin}/commands", + f"{AUTONOMIC_URL}command/vehicles/{vin}/commands", data=json.dumps(data), headers=headers ) + _LOGGER.debug("Testing command") _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) - if r.status_code == 201: - return True - return False def __request_and_poll(self, method, url): """Poll API until status code is reached, locking + remote start""" From 362355398af82510e88e0833d34bf19a0f23414d Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 18:29:05 +1000 Subject: [PATCH 42/63] Fix missing commit --- custom_components/fordpass/fordpass_new.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 81f36c0..70e1bd2 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -520,10 +520,8 @@ def request_update(self, vin=""): vinnum = vin else: vinnum = self.vin - status = self.__make_request( - "PUT", f"{BASE_URL}/vehicles/v5/{vinnum}/status", None, None - ) - return status.json()["status"] + status = self.__requestAndPollCommand("statusRefresh", vinnum) + return status def __make_request(self, method, url, data, params): """ @@ -572,13 +570,13 @@ def __request_and_poll_command(self, command, vin=None): } if vin is None: r = session.post( - f"{AUTONOMIC_URL}command/vehicles/{self.vin}/commands", + f"{AUTONOMIC_URL}/command/vehicles/{self.vin}/commands", data=json.dumps(data), headers=headers ) else: r = session.post( - f"{AUTONOMIC_URL}command/vehicles/{vin}/commands", + f"{AUTONOMIC_URL}/command/vehicles/{self.vin}/commands", data=json.dumps(data), headers=headers ) @@ -586,6 +584,9 @@ def __request_and_poll_command(self, command, vin=None): _LOGGER.debug("Testing command") _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) + if r.status_code == 201: + return True + return False def __request_and_poll(self, method, url): """Poll API until status code is reached, locking + remote start""" From 43eb136acba9237be8f79ce6a76c39631cd52710 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 18:57:59 +1000 Subject: [PATCH 43/63] Fix for fuelrange on electric vehicles --- custom_components/fordpass/sensor.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 8c8c36f..0b9c8cb 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -213,13 +213,18 @@ def get_value(self, ftype): if self.sensor == "odometer": return self.data[self.sensor].items() if self.sensor == "fuel": - if self.data["fuelRange"] is None: - return None - if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - self.data["fuelRange"]["value"] = round( - float(self.data["fuelRange"]["value"]) / 1.60934 - ) - return {"fuelRange": self.data["fuelRange"]["value"]} + if "FuelRange" in self.data: + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + self.data["fuelRange"]["value"] = round( + float(self.data["fuelRange"]["value"]) / 1.60934 + ) + return {"fuelRange": self.data["fuelRange"]["value"]} + elif "xevBatteryRange" in self.data: + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + self.data["xevBatteryRange"]["value"] = round( + float(self.data["xevBatteryRange"]["value"]) / 1.60934 + ) + return {"batteryRange": self.data["xevBatteryRange"]["value"]} if self.sensor == "battery": return { "Battery Voltage": self.data["batteryVoltage"]["value"] From 80238064b3593ac9c3ea4dfc3044bdd61c97c6ac Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 19:00:33 +1000 Subject: [PATCH 44/63] Add battery charge percentage for electric vehicle --- custom_components/fordpass/sensor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 0b9c8cb..70535da 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -329,6 +329,12 @@ def get_value(self, ftype): "xevBatteryPerformanceStatus" ]["value"] + if ( + self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None + ): + elecs["Battery Charge"] = self.data[ + "xevBatteryStateOfCharge" + ]["value"] return elecs if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: From 7439d7d70056fb588aa1c2a7ee69fcf44df9b6c0 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 19:06:39 +1000 Subject: [PATCH 45/63] Add speed sensor --- custom_components/fordpass/sensor.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 70535da..be80798 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -166,6 +166,8 @@ def get_value(self, ftype): if "value" in self.data["dieselSystemStatus"]["exhaustFluidLevel"]: return self.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] return "Not Supported" + if self.sensor == "speed": + return self.data[self.sensor]["value"] return None if ftype == "measurement": if self.sensor == "odometer": @@ -206,6 +208,10 @@ def get_value(self, ftype): if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": return "mi" return "km" + if self.sensor == "speed": + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + return "mph" + return "km/h" if self.sensor == "exhaustFluidLevel": return "%" return None @@ -393,6 +399,8 @@ def get_value(self, ftype): return self.data["dieselSystemStatus"] if self.sensor == "exhaustFluidLevel": return self.data["dieselSystemStatus"] + if self.sensor == "speed": + return None return None return None From 41c702557477734d3b2a68355e94cfb03f405ae3 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 19:14:28 +1000 Subject: [PATCH 46/63] add missing sensor in const.py --- custom_components/fordpass/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index fc47d94..c0adb27 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,6 +42,7 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, + "speed": {"icon": "mdi:speedometer"}, # "deepSleepInProgress": { # "icon": "mdi:power-sleep", # "name": "Deep Sleep Mode Active", From d7bc00459a8c08c2bc9f82629d09dfddb30f0499 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 19:31:58 +1000 Subject: [PATCH 47/63] fix fuelrange spelling --- custom_components/fordpass/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index be80798..68cd6ba 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -219,7 +219,7 @@ def get_value(self, ftype): if self.sensor == "odometer": return self.data[self.sensor].items() if self.sensor == "fuel": - if "FuelRange" in self.data: + if "fuelRange" in self.data: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": self.data["fuelRange"]["value"] = round( float(self.data["fuelRange"]["value"]) / 1.60934 From 17b18fdf5e6c1c34366d63fbae1ce608400b7985 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 20:30:33 +1000 Subject: [PATCH 48/63] remove locking/unlocking visual status --- custom_components/fordpass/lock.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index c6f9d1d..921fa0d 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -33,7 +33,7 @@ def __init__(self, coordinator): async def async_lock(self, **kwargs): """Locks the vehicle.""" - self._attr_is_locking = True + # self._attr_is_locking = True self.async_write_ha_state() _LOGGER.debug("Locking %s", self.coordinator.vin) status = await self.coordinator.hass.async_add_executor_job( @@ -42,20 +42,20 @@ async def async_lock(self, **kwargs): _LOGGER.debug(status) await self.coordinator.async_request_refresh() _LOGGER.debug("Locking here") - self._attr_is_locking = False + # self._attr_is_locking = False self.async_write_ha_state() async def async_unlock(self, **kwargs): """Unlocks the vehicle.""" _LOGGER.debug("Unlocking %s", self.coordinator.vin) - self._attr_is_unlocking = True + # self._attr_is_unlocking = True self.async_write_ha_state() status = await self.coordinator.hass.async_add_executor_job( self.coordinator.vehicle.unlock ) _LOGGER.debug(status) await self.coordinator.async_request_refresh() - self._attr_is_unlocking = False + # self._attr_is_unlocking = False self.async_write_ha_state() @property From fbdbd5b86efab5261d77dfbec789376bf05cc556 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 11 Oct 2023 20:31:24 +1000 Subject: [PATCH 49/63] Fix status function referencing wrong function --- custom_components/fordpass/fordpass_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index 70e1bd2..c63e736 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -520,7 +520,7 @@ def request_update(self, vin=""): vinnum = vin else: vinnum = self.vin - status = self.__requestAndPollCommand("statusRefresh", vinnum) + status = self.__request_and_poll_command("statusRefresh", vinnum) return status def __make_request(self, method, url, data, params): From 36f38335b4de4cc38a6b3066f64056bd0062bcdd Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 08:12:28 -0400 Subject: [PATCH 50/63] Beta6 --- custom_components/fordpass/const.py | 3 +- custom_components/fordpass/fordpass_new.py | 46 ++++++++++++++++------ custom_components/fordpass/lock.py | 8 ++-- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index c161313..c0adb27 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,8 +42,7 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, - "elVehCharging": {"icon": "mdi:ev-station"}, - + "speed": {"icon": "mdi:speedometer"}, # "deepSleepInProgress": { # "icon": "mdi:power-sleep", # "name": "Deep Sleep Mode Active", diff --git a/custom_components/fordpass/fordpass_new.py b/custom_components/fordpass/fordpass_new.py index f6e5829..c63e736 100644 --- a/custom_components/fordpass/fordpass_new.py +++ b/custom_components/fordpass/fordpass_new.py @@ -60,6 +60,7 @@ def __init__( self.expires_at = None self.refresh_token = None self.auto_token = None + self.auto_expires_at = None retry = Retry(connect=3, backoff_factor=0.5) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) @@ -188,15 +189,19 @@ def auth(self): self.token = result["access_token"] self.refresh_token = result["refresh_token"] self.expires_at = time.time() + result["expires_in"] + auto_token = self.get_auto_token() + self.auto_token = auto_token["access_token"] + self.auto_expires_at = time.time() + result["expires_in"] if self.save_token: result["expiry_date"] = time.time() + result["expires_in"] + result["auto_token"] = auto_token["access_token"] + result["auto_refresh"] = auto_token["refresh_token"] + result["auto_expiry"] = time.time() + auto_token["expires_in"] + self.write_token(result) session.cookies.clear() return True response.raise_for_status() - # Code to get Auto token - if self.get_auto_token(): - return True return False def refresh_token_func(self, token): @@ -221,9 +226,12 @@ def refresh_token_func(self, token): _LOGGER.debug("401 response stage 2: refresh stage 1 token") self.auth() + + def __acquire_token(self): # Fetch and refresh token as needed # If file exists read in token file and check it's valid + _LOGGER.debug("Fetching token") if self.save_token: if os.path.isfile(self.token_location): data = self.read_token() @@ -232,24 +240,39 @@ def __acquire_token(self): data["access_token"] = self.token data["refresh_token"] = self.refresh_token data["expiry_date"] = self.expires_at + data["auto_token"] = self.auto_token + data["auto_expiry"] = self.auto_expires_at else: data = {} data["access_token"] = self.token data["refresh_token"] = self.refresh_token data["expiry_date"] = self.expires_at + data["auto_token"] = self.auto_token + data["auto_expiry"] = self.auto_expires_at self.token = data["access_token"] self.expires_at = data["expiry_date"] + _LOGGER.debug(self.auto_token) + _LOGGER.debug(self.auto_expires_at) + if self.auto_token is None or self.auto_expires_at is None: + self.auth() + pass + self.auto_token = data["auto_token"] + self.auto_expires_at = data["auto_expiry"] if self.expires_at: if time.time() >= self.expires_at: _LOGGER.debug("No token, or has expired, requesting new token") self.refresh_token_func(data) # self.auth() + if self.auto_expires_at: + if time.time() >= self.auto_expires_at: + _LOGGER.debug("Autonomic token expired") + self.auth() if self.token is None: + _LOGGER.debug("Fetching token4") # No existing token exists so refreshing library self.auth() else: _LOGGER.debug("Token is valid, continuing") - self.get_auto_token() def write_token(self, token): """Save token to file for reuse""" @@ -308,7 +331,7 @@ def get_auto_token(self): _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) self.auto_token = result["access_token"] - return True + return result return False def status(self): @@ -323,6 +346,7 @@ def status(self): "auth-token": self.token, "Application-Id": self.region, } + _LOGGER.debug(self.auto_token) if NEW_API: headers = { @@ -334,7 +358,7 @@ def status(self): f"{AUTONOMIC_URL}/telemetry/sources/fordpass/vehicles/{self.vin}", params=params, headers=headers ) if r.status_code == 200: - # _LOGGER.debug(r.text) + _LOGGER.debug(r.text) result = r.json() return result else: @@ -419,7 +443,7 @@ def vehicles(self): _LOGGER.debug(result) return result - # _LOGGER.debug(response.text) + _LOGGER.debug(response.text) response.raise_for_status() return None @@ -458,7 +482,7 @@ def lock(self): """ Issue a lock command to the doors """ - return self.__request_and_poll_command("unlock") + return self.__request_and_poll_command("lock") def unlock(self): """ @@ -496,7 +520,7 @@ def request_update(self, vin=""): vinnum = vin else: vinnum = self.vin - status = self.__requestAndPollCommand("statusRefresh", vinnum) + status = self.__request_and_poll_command("statusRefresh", vinnum) return status def __make_request(self, method, url, data, params): @@ -550,13 +574,13 @@ def __request_and_poll_command(self, command, vin=None): data=json.dumps(data), headers=headers ) - else: r = session.post( - f"{AUTONOMIC_URL}/command/vehicles/{vin}/commands", + f"{AUTONOMIC_URL}/command/vehicles/{self.vin}/commands", data=json.dumps(data), headers=headers ) + _LOGGER.debug("Testing command") _LOGGER.debug(r.status_code) _LOGGER.debug(r.text) diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index c6f9d1d..921fa0d 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -33,7 +33,7 @@ def __init__(self, coordinator): async def async_lock(self, **kwargs): """Locks the vehicle.""" - self._attr_is_locking = True + # self._attr_is_locking = True self.async_write_ha_state() _LOGGER.debug("Locking %s", self.coordinator.vin) status = await self.coordinator.hass.async_add_executor_job( @@ -42,20 +42,20 @@ async def async_lock(self, **kwargs): _LOGGER.debug(status) await self.coordinator.async_request_refresh() _LOGGER.debug("Locking here") - self._attr_is_locking = False + # self._attr_is_locking = False self.async_write_ha_state() async def async_unlock(self, **kwargs): """Unlocks the vehicle.""" _LOGGER.debug("Unlocking %s", self.coordinator.vin) - self._attr_is_unlocking = True + # self._attr_is_unlocking = True self.async_write_ha_state() status = await self.coordinator.hass.async_add_executor_job( self.coordinator.vehicle.unlock ) _LOGGER.debug(status) await self.coordinator.async_request_refresh() - self._attr_is_unlocking = False + # self._attr_is_unlocking = False self.async_write_ha_state() @property From 7fbc9735c6c8c6beb66d6e94e89ed9d6307d33d2 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 08:54:01 -0400 Subject: [PATCH 51/63] Added Charging Sensor. elVehCharging --- custom_components/fordpass/const.py | 1 + custom_components/fordpass/sensor.py | 57 ++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index c0adb27..40a3b45 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,6 +42,7 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, + "elVehCharging": {"icon": "mdi:ev-station"}, "speed": {"icon": "mdi:speedometer"}, # "deepSleepInProgress": { # "icon": "mdi:power-sleep", diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 14e0930..bf9f32e 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): sensors.append(sensor) elif key == "elVehCharging": if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: - sensors.append(sensor) + sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): @@ -142,6 +142,14 @@ def get_value(self, ftype): return float(self.data["xevBatteryRange"]["value"]) return float(self.data["xevBatteryRange"]["value"]) return "Unsupported" + if self.sensor == "elVehCharging": + if "xevBatteryChargeDisplayStatus" in self.data: + return self.data["xevBatteryChargeDisplayStatus"]["value"] + return "Unsupported" + if self.sensor == "elVehCharging": + if "xevBatteryRange" in self.data: + float(self.data["xevBatteryRange"]["value"]) / 1.60934 + return "Unsupported" if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return "Unsupported" @@ -170,6 +178,8 @@ def get_value(self, ftype): if "value" in self.data["dieselSystemStatus"]["exhaustFluidLevel"]: return self.data["dieselSystemStatus"]["exhaustFluidLevel"]["value"] return "Not Supported" + if self.sensor == "speed": + return self.data[self.sensor]["value"] return None if ftype == "measurement": if self.sensor == "odometer": @@ -210,6 +220,10 @@ def get_value(self, ftype): if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": return "mi" return "km" + if self.sensor == "speed": + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + return "mph" + return "km/h" if self.sensor == "exhaustFluidLevel": return "%" return None @@ -217,13 +231,18 @@ def get_value(self, ftype): if self.sensor == "odometer": return self.data[self.sensor].items() if self.sensor == "fuel": - if self.data["fuelRange"] is None: - return None - if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - self.data["fuelRange"]["value"] = round( - float(self.data["fuelRange"]["value"]) / 1.60934 - ) - return {"fuelRange": self.data["fuelRange"]["value"]} + if "fuelRange" in self.data: + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + self.data["fuelRange"]["value"] = round( + float(self.data["fuelRange"]["value"]) / 1.60934 + ) + return {"fuelRange": self.data["fuelRange"]["value"]} + elif "xevBatteryRange" in self.data: + if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": + self.data["xevBatteryRange"]["value"] = round( + float(self.data["xevBatteryRange"]["value"]) / 1.60934 + ) + return {"batteryRange": self.data["xevBatteryRange"]["value"]} if self.sensor == "battery": return { "Battery Voltage": self.data["batteryVoltage"]["value"] @@ -286,13 +305,13 @@ def get_value(self, ftype): if self.sensor == "lastRefresh": return None if self.sensor == "elVeh": - if self.data["xevBatteryStateOfCharge"] is None: + if self.data["xevBatteryCapacity"] is None: return None elecs = {} if ( - self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None + self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None ): - elecs["Battery State of Charge"] = self.data["xevBatteryStateOfCharge"]["value"] + elecs["xevBatteryCapacity"] = self.data["xevBatteryCapacity"]["value"] if ( self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None ): @@ -328,6 +347,12 @@ def get_value(self, ftype): "xevBatteryPerformanceStatus" ]["value"] + if ( + self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None + ): + elecs["Battery Charge"] = self.data[ + "xevBatteryStateOfCharge" + ]["value"] return elecs if self.sensor == "elVehCharging": @@ -339,10 +364,10 @@ def get_value(self, ftype): if ( self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] is not None ): - cs["Charging Percentage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] + cs["Charging State of Charge"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] if (self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] is not None ): - cs["Battery Charge Status"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] + cs["Charging Status"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] if ( self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] is not None ): @@ -364,7 +389,7 @@ def get_value(self, ftype): if ( self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None ): - cs["Charging kW"] = chVolt * chAmps + cs["Charging kW"] = (chVolt * chAmps) / 1000 return cs if self.sensor == "zoneLighting": @@ -424,6 +449,8 @@ def get_value(self, ftype): return self.data["dieselSystemStatus"] if self.sensor == "exhaustFluidLevel": return self.data["dieselSystemStatus"] + if self.sensor == "speed": + return None return None return None @@ -476,4 +503,4 @@ def device_class(self): return SensorDeviceClass.DISTANCE if SENSORS[self.sensor]["device_class"] == "timestamp": return SensorDeviceClass.TIMESTAMP - return None \ No newline at end of file + return None From 81040805947b147e8428e8ca478080e70a5e3304 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 13:49:11 -0400 Subject: [PATCH 52/63] should be up to date with beta6 --- custom_components/fordpass/const.py | 1 - custom_components/fordpass/sensor.py | 50 ---------------------------- 2 files changed, 51 deletions(-) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index 40a3b45..c0adb27 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,7 +42,6 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, - "elVehCharging": {"icon": "mdi:ev-station"}, "speed": {"icon": "mdi:speedometer"}, # "deepSleepInProgress": { # "icon": "mdi:power-sleep", diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index bf9f32e..68cd6ba 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -30,9 +30,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): elif key == "elVeh": if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: sensors.append(sensor) - elif key == "elVehCharging": - if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: - sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): @@ -63,7 +60,6 @@ def __init__(self, coordinator, sensor, options): self._attr = {} self.coordinator = coordinator self.data = coordinator.data["metrics"] - self.events = coordinator.data["events"] self._device_id = "fordpass_" + sensor # Required for HA 2022.7 self.coordinator_context = object() @@ -142,14 +138,6 @@ def get_value(self, ftype): return float(self.data["xevBatteryRange"]["value"]) return float(self.data["xevBatteryRange"]["value"]) return "Unsupported" - if self.sensor == "elVehCharging": - if "xevBatteryChargeDisplayStatus" in self.data: - return self.data["xevBatteryChargeDisplayStatus"]["value"] - return "Unsupported" - if self.sensor == "elVehCharging": - if "xevBatteryRange" in self.data: - float(self.data["xevBatteryRange"]["value"]) / 1.60934 - return "Unsupported" if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return "Unsupported" @@ -354,44 +342,6 @@ def get_value(self, ftype): "xevBatteryStateOfCharge" ]["value"] return elecs - - if self.sensor == "elVehCharging": - if self.events["xevBatteryChargeEvent"] is None: - return None - - cs = {} - - if ( - self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] is not None - ): - cs["Charging State of Charge"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryStateOfCharge"]["value"] - if (self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] is not None - ): - cs["Charging Status"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["value"] - if ( - self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] is not None - ): - cs["Charging Type"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargeDisplayStatus"]["xevChargerPowerType"] - if ( - self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None - ): - cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] - if ( - self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None - ): - cs["Charging Amperage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] - chAmps = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] - if ( - self.events["xevBatteryChargeEvent"]["metrics"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None - ): - cs["Charging Voltage"] = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] - chVolt = self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] - if ( - self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerVoltageOutput"]["value"] is not None and self.events["xevBatteryChargeEvent"]["metrics"]["xevBatteryChargerCurrentOutput"]["value"] is not None - ): - cs["Charging kW"] = (chVolt * chAmps) / 1000 - return cs - if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return None From 12fa2b9cd93066224070aeae7d7fb3c1002e84d7 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 13:54:25 -0400 Subject: [PATCH 53/63] Added elVehCharging sensor --- custom_components/fordpass/const.py | 1 + custom_components/fordpass/sensor.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/custom_components/fordpass/const.py b/custom_components/fordpass/const.py index c0adb27..40a3b45 100644 --- a/custom_components/fordpass/const.py +++ b/custom_components/fordpass/const.py @@ -42,6 +42,7 @@ "windowPosition": {"icon": "mdi:car-door"}, "lastRefresh": {"icon": "mdi:clock", "device_class": "timestamp"}, "elVeh": {"icon": "mdi:ev-station"}, + "elVehCharging": {"icon": "mdi:ev-station"}, "speed": {"icon": "mdi:speedometer"}, # "deepSleepInProgress": { # "icon": "mdi:power-sleep", diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 68cd6ba..7e6039a 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -30,6 +30,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): elif key == "elVeh": if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: sensors.append(sensor) + ## SquidBytes: Added elVehCharging + elif key == "elVehCharging": + if "xevBatteryChargeEvent" in sensor.coordinator.data["events"]: + sensors.append(sensor) elif key == "dieselSystemStatus": if sensor.coordinator.data.get("dieselSystemStatus", {}): if sensor.coordinator.data.get("dieselSystemStatus", {}).get("filterRegenerationStatus"): @@ -138,6 +142,12 @@ def get_value(self, ftype): return float(self.data["xevBatteryRange"]["value"]) return float(self.data["xevBatteryRange"]["value"]) return "Unsupported" + ## SquidBytes: Added elVehCharging + if self.sensor == "elVehCharging": + if "xevBatteryChargeDisplayStatus" in self.data: + ## Default sensor type is the status of charge (might be better to have the kW as the value, but for now I'll do this) + return float(self.data["xevBatteryChargeDisplayStatus"]["value"]) + return "Unsupported" if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return "Unsupported" @@ -342,6 +352,54 @@ def get_value(self, ftype): "xevBatteryStateOfCharge" ]["value"] return elecs + ## SquidBytes: Added elVehCharging + if self.sensor == "elVehCharging": + if self.data["xevPlugChargerStatus"] is None: + return None + + cs = {} + + if ( + self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None + ): + cs["Charging State of Charge"] = self.data["xevBatteryStateOfCharge"]["value"] + if (self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None + ): + cs["Charging Status"] = self.data["xevBatteryChargeDisplayStatus"]["value"] + if ( + self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None + ): + cs["Charging Type"] = self.data["xevChargeStationPowerType"]["value"] + if ( + self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None + ): + cs["Charge Station Status"] = self.data["xevChargeStationCommunicationStatus"]["value"] + if ( + self.data["xevBatteryTemperature"] is not None and self.data["xevBatteryTemperature"]["value"] is not None + ): + cs["Battery Temperature"] = self.data["xevBatteryTemperature"]["value"] + if ( + self.data["xevBatteryChargerVoltageOutput"] is not None and self.data["xevBatteryChargerVoltageOutput"]["value"] is not None + ): + cs["Charging Voltage"] = float(self.data["xevBatteryChargerVoltageOutput"]["value"]) + chVolt = cs["Charging Voltage"] + if ( + self.data["xevBatteryChargerCurrentOutput"] is not None and self.data["xevBatteryChargerCurrentOutput"]["value"] is not None + ): + cs["Charging Amperage"] = float(self.data["xevBatteryChargerCurrentOutput"]["value"]) + chAmps = cs["Charging Amperage"] + if ( + self.data["xevBatteryChargerCurrentOutput"]["value"] is not None and self.data["xevBatteryChargerVoltageOutput"]["value"] is not None + ): + cs["Charging kW"] = round((chVolt * chAmps) / 1000, 2) + + if ( + self.data["xevBatteryTimeToFullCharge"] is not None and self.data["xevBatteryTimeToFullCharge"]["value"] is not None + ): + cs["Time To Full Charge"] = self.data["xevBatteryTimeToFullCharge"]["value"] + + return cs + if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: return None From 9ddd2b77469128fe38da2b2170f29f57a242fd8e Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 14:08:36 -0400 Subject: [PATCH 54/63] elVehCharging sensor is working in Hass, if you're charging --- custom_components/fordpass/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 7e6039a..2b27e7a 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -146,7 +146,7 @@ def get_value(self, ftype): if self.sensor == "elVehCharging": if "xevBatteryChargeDisplayStatus" in self.data: ## Default sensor type is the status of charge (might be better to have the kW as the value, but for now I'll do this) - return float(self.data["xevBatteryChargeDisplayStatus"]["value"]) + return self.data["xevBatteryChargeDisplayStatus"]["value"] return "Unsupported" if self.sensor == "zoneLighting": if "zoneLighting" not in self.data: From c2035eb45f0c2e835e01253d4ece74b6d27c6643 Mon Sep 17 00:00:00 2001 From: SquidBytes Date: Wed, 11 Oct 2023 16:37:25 -0400 Subject: [PATCH 55/63] odometer fixed for me, not sure why this fixed it. Might be a weird key error --- custom_components/fordpass/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 2b27e7a..0d8439c 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -80,7 +80,7 @@ def get_value(self, ftype): float(self.data[self.sensor]["value"]) / 1.60934 ) return self.data[self.sensor]["value"] - return self.data[self.sensor]["value"] + return self.data["odometer"]["value"] if self.sensor == "fuel": if "fuelLevel" in self.data: if self.data["fuelLevel"] is None: From 0356b5e72a44f18686cebebd95965a1cbd28d6a2 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 07:24:26 +1000 Subject: [PATCH 56/63] Check value exists before parsing --- custom_components/fordpass/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index df8218e..fb7f498 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -303,7 +303,7 @@ def get_value(self, ftype): if self.sensor == "lastRefresh": return None if self.sensor == "elVeh": - if self.data["xevBatteryCapacity"] is None: + if "xevBatteryCapacity" not in self.data: return None elecs = {} if ( @@ -354,7 +354,7 @@ def get_value(self, ftype): return elecs ## SquidBytes: Added elVehCharging if self.sensor == "elVehCharging": - if self.data["xevPlugChargerStatus"] is None: + if "xevPlugChargerStatus" not in self.data: return None cs = {} From e779ed83b20aefa17e9900da1a764f2ace057af9 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 07:35:45 +1000 Subject: [PATCH 57/63] hack to make last refresh work depdning on format --- custom_components/fordpass/sensor.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index fb7f498..b285045 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -127,11 +127,25 @@ def get_value(self, ftype): return status return "Unsupported" if self.sensor == "lastRefresh": - return dt.as_local( - datetime.strptime( - self.coordinator.data["updateTime"], "%Y-%m-%dT%H:%M:%S.%fz" + try: + return dt.as_local( + datetime.strptime( + self.coordinator.data["updateTime"], "%Y-%m-%dT%H:%M:%S.%fz" + ) + ) + except: + _LOGGER.debug("%f conversion failed") + try: + return dt.as_local( + datetime.strptime( + self.coordinator.data["updateTime"], "%Y-%m-%dT%H:%M:%Sz" + ) ) - ) + except: + _LOGGER.debug("%s conversion failed") + refresh = "" + + return refresh if self.sensor == "elVeh": if "xevBatteryRange" in self.data: if self.fordoptions[CONF_DISTANCE_UNIT] is not None: From bf9828feb8eb2c43d3c68f31f37cacf2b77083fb Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 12:07:21 +1000 Subject: [PATCH 58/63] Add check for missing electric variables --- custom_components/fordpass/sensor.py | 39 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index b285045..6f29fdd 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -28,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if "zoneLighting" in sensor.coordinator.data: sensors.append(sensor) elif key == "elVeh": - if "xevBatteryCapacity" in sensor.coordinator.data["metrics"]: + if "xevBatteryRange" in sensor.coordinator.data["metrics"]: sensors.append(sensor) ## SquidBytes: Added elVehCharging elif key == "elVehCharging": @@ -86,7 +86,7 @@ def get_value(self, ftype): if self.data["fuelLevel"] is None: return None return round(self.data["fuelLevel"]["value"]) - elif "xevBatteryStateOfCharge": + elif "xevBatteryStateOfCharge" in self.data: return round(self.data["xevBatteryStateOfCharge"]["value"]) else: return None @@ -144,7 +144,6 @@ def get_value(self, ftype): except: _LOGGER.debug("%s conversion failed") refresh = "" - return refresh if self.sensor == "elVeh": if "xevBatteryRange" in self.data: @@ -317,50 +316,50 @@ def get_value(self, ftype): if self.sensor == "lastRefresh": return None if self.sensor == "elVeh": - if "xevBatteryCapacity" not in self.data: + if "xevBatteryRange" not in self.data: return None elecs = {} if ( - self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None + "xevBatteryCapacity" in self.data and self.data["xevBatteryCapacity"] is not None and self.data["xevBatteryCapacity"]["value"] is not None ): elecs["xevBatteryCapacity"] = self.data["xevBatteryCapacity"]["value"] if ( - self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None + "xevPlugChargerStatus" in self.data and self.data["xevPlugChargerStatus"] is not None and self.data["xevPlugChargerStatus"]["value"] is not None ): elecs["Plug Status"] = self.data["xevPlugChargerStatus"][ "value" ] if ( - self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None + "xevBatteryChargeDisplayStatus" in self.data and self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None ): elecs["Charging Status"] = self.data[ "xevBatteryChargeDisplayStatus" ]["value"] if ( - self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None + "xevChargeStationPowerType" in self.data and self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None ): elecs["Charger Power Type"] = self.data[ "xevChargeStationPowerType" ]["value"] if ( - self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None + "xevChargeStationCommunicationStatus" in self.data and self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None ): elecs["Battery Charge Status"] = self.data[ "xevChargeStationCommunicationStatus" ]["value"] if ( - self.data["xevBatteryPerformanceStatus"] is not None and self.data["xevBatteryPerformanceStatus"]["value"] is not None + "xevBatteryPerformanceStatus" in self.data and self.data["xevBatteryPerformanceStatus"] is not None and self.data["xevBatteryPerformanceStatus"]["value"] is not None ): elecs["Battery Performance Status"] = self.data[ "xevBatteryPerformanceStatus" ]["value"] if ( - self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None + "xevBatteryStateOfCharge" in self.data and self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None ): elecs["Battery Charge"] = self.data[ "xevBatteryStateOfCharge" @@ -374,41 +373,41 @@ def get_value(self, ftype): cs = {} if ( - self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None + "xevBatteryStateOfCharge" in self.data and self.data["xevBatteryStateOfCharge"] is not None and self.data["xevBatteryStateOfCharge"]["value"] is not None ): cs["Charging State of Charge"] = self.data["xevBatteryStateOfCharge"]["value"] - if (self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None + if ("xevBatteryChargeDisplayStatus" in self.data and self.data["xevBatteryChargeDisplayStatus"] is not None and self.data["xevBatteryChargeDisplayStatus"]["value"] is not None ): cs["Charging Status"] = self.data["xevBatteryChargeDisplayStatus"]["value"] if ( - self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None + "xevChargeStationPowerType" in self.data and self.data["xevChargeStationPowerType"] is not None and self.data["xevChargeStationPowerType"]["value"] is not None ): cs["Charging Type"] = self.data["xevChargeStationPowerType"]["value"] if ( - self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None + "xevChargeStationCommunicationStatus" in self.data and self.data["xevChargeStationCommunicationStatus"] is not None and self.data["xevChargeStationCommunicationStatus"]["value"] is not None ): cs["Charge Station Status"] = self.data["xevChargeStationCommunicationStatus"]["value"] if ( - self.data["xevBatteryTemperature"] is not None and self.data["xevBatteryTemperature"]["value"] is not None + "xevBatteryTemperature" in self.data and self.data["xevBatteryTemperature"] is not None and self.data["xevBatteryTemperature"]["value"] is not None ): cs["Battery Temperature"] = self.data["xevBatteryTemperature"]["value"] if ( - self.data["xevBatteryChargerVoltageOutput"] is not None and self.data["xevBatteryChargerVoltageOutput"]["value"] is not None + "xevBatteryChargerVoltageOutput" in self.data and self.data["xevBatteryChargerVoltageOutput"] is not None and self.data["xevBatteryChargerVoltageOutput"]["value"] is not None ): cs["Charging Voltage"] = float(self.data["xevBatteryChargerVoltageOutput"]["value"]) chVolt = cs["Charging Voltage"] if ( - self.data["xevBatteryChargerCurrentOutput"] is not None and self.data["xevBatteryChargerCurrentOutput"]["value"] is not None + "xevBatteryChargerCurrentOutput" in self.data and self.data["xevBatteryChargerCurrentOutput"] is not None and self.data["xevBatteryChargerCurrentOutput"]["value"] is not None ): cs["Charging Amperage"] = float(self.data["xevBatteryChargerCurrentOutput"]["value"]) chAmps = cs["Charging Amperage"] if ( - self.data["xevBatteryChargerCurrentOutput"]["value"] is not None and self.data["xevBatteryChargerVoltageOutput"]["value"] is not None + "xevBatteryChargerCurrentOutput" in self.data and self.data["xevBatteryChargerCurrentOutput"]["value"] is not None and self.data["xevBatteryChargerVoltageOutput"]["value"] is not None ): cs["Charging kW"] = round((chVolt * chAmps) / 1000, 2) if ( - self.data["xevBatteryTimeToFullCharge"] is not None and self.data["xevBatteryTimeToFullCharge"]["value"] is not None + "xevBatteryTimeToFullCharge" in self.data and self.data["xevBatteryTimeToFullCharge"] is not None and self.data["xevBatteryTimeToFullCharge"]["value"] is not None ): cs["Time To Full Charge"] = self.data["xevBatteryTimeToFullCharge"]["value"] From 725ed2a62ebcfa8b56989a3d879fbcb5dde05074 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 13:15:49 +1000 Subject: [PATCH 59/63] Fix incorrect distance for electric vehicles --- custom_components/fordpass/sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 6f29fdd..12d4bea 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -244,15 +244,15 @@ def get_value(self, ftype): if self.sensor == "fuel": if "fuelRange" in self.data: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - self.data["fuelRange"]["value"] = round( + return {"fuelRange:": round( float(self.data["fuelRange"]["value"]) / 1.60934 - ) + )} return {"fuelRange": self.data["fuelRange"]["value"]} elif "xevBatteryRange" in self.data: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - self.data["xevBatteryRange"]["value"] = round( + return {"batteryRange": round( float(self.data["xevBatteryRange"]["value"]) / 1.60934 - ) + )} return {"batteryRange": self.data["xevBatteryRange"]["value"]} if self.sensor == "battery": return { From faccdd7e4b3027a8ff39a167df6c23e4c1d15e5e Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 13:48:08 +1000 Subject: [PATCH 60/63] Fix update and tirepressure error --- custom_components/fordpass/device_tracker.py | 4 ++-- custom_components/fordpass/lock.py | 4 ++-- custom_components/fordpass/sensor.py | 7 +++++-- custom_components/fordpass/switch.py | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/custom_components/fordpass/device_tracker.py b/custom_components/fordpass/device_tracker.py index c675c7d..348661b 100644 --- a/custom_components/fordpass/device_tracker.py +++ b/custom_components/fordpass/device_tracker.py @@ -35,12 +35,12 @@ def __init__(self, coordinator, sensor): @property def latitude(self): """Return latitude""" - return float(self.data["position"]["value"]["location"]["lat"]) + return float(self.coordinator.data["metrics"]["position"]["value"]["location"]["lat"]) @property def longitude(self): """Return longtitude""" - return float(self.data["position"]["value"]["location"]["lon"]) + return float(self.coordinator.data["metrics"]["position"]["value"]["location"]["lon"]) @property def source_type(self): diff --git a/custom_components/fordpass/lock.py b/custom_components/fordpass/lock.py index 921fa0d..1a500a4 100644 --- a/custom_components/fordpass/lock.py +++ b/custom_components/fordpass/lock.py @@ -61,9 +61,9 @@ async def async_unlock(self, **kwargs): @property def is_locked(self): """Determine if the lock is locked.""" - if self.data is None or self.data["doorLockStatus"] is None: + if self.coordinator.data["metrics"] is None or self.coordinator.data["metrics"]["doorLockStatus"] is None: return None - return self.data["doorLockStatus"][0]["value"] == "LOCKED" + return self.coordinator.data["metrics"]["doorLockStatus"][0]["value"] == "LOCKED" @property def icon(self): diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 12d4bea..5ede02a 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -70,6 +70,7 @@ def __init__(self, coordinator, sensor, options): def get_value(self, ftype): """Get sensor value and attributes from coordinator data""" + self.data = self.coordinator.data["metrics"] if ftype == "state": if self.sensor == "odometer": if self.fordoptions[CONF_DISTANCE_UNIT] is not None: @@ -95,7 +96,9 @@ def get_value(self, ftype): if self.sensor == "oil": return round(self.data["oilLifeRemaining"]["value"]) if self.sensor == "tirePressure": - return self.data["tirePressureSystemStatus"][0]["value"] + if "tirePressureSystemStatus" in self.data: + return self.data["tirePressureSystemStatus"][0]["value"] + return "Not Supported" if self.sensor == "gps": if self.data["position"] is None: return "Unsupported" @@ -261,7 +264,7 @@ def get_value(self, ftype): if self.sensor == "oil": return self.data["oilLifeRemaining"].items() if self.sensor == "tirePressure": - if self.data["tirePressure"] is not None: + if "tirePressure" in self.data : _LOGGER.debug(self.fordoptions[CONF_PRESSURE_UNIT]) if self.fordoptions[CONF_PRESSURE_UNIT] == "PSI": _LOGGER.debug("PSIIIII") diff --git a/custom_components/fordpass/switch.py b/custom_components/fordpass/switch.py index 4fa2e2b..9b80621 100644 --- a/custom_components/fordpass/switch.py +++ b/custom_components/fordpass/switch.py @@ -83,10 +83,10 @@ def is_on(self): """Check status of switch""" if self.switch == "ignition": if ( - self.data is None or self.data["ignitionStatus"] is None + self.coordinator.data["metrics"] is None or self.coordinator.data["metrics"]["ignitionStatus"] is None ): return None - if self.data["ignitionStatus"]["value"] == "OFF": + if self.coordinator.data["metrics"]["ignitionStatus"]["value"] == "OFF": return False if self.switch == "guardmode": # Need to find the correct response for enabled vs disabled so this may be spotty at the moment From 477d5a67445d1bd0c7a01eb175734b9939a8be80 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 16:00:05 +1000 Subject: [PATCH 61/63] Fix incorrect door status attributes --- custom_components/fordpass/sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 5ede02a..188661c 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -307,7 +307,11 @@ def get_value(self, ftype): if self.sensor == "doorStatus": doors = {} for value in self.data[self.sensor]: - doors[value["vehicleDoor"]] = value["value"] + _LOGGER.debug(value) + if "vehicleSide" in value: + doors[f"{value['vehicleSide']} : {value['vehicleDoor']}"] = value['value'] + else: + doors[value["vehicleDoor"]] = value['value'] return doors if self.sensor == "windowPosition": if "windowStatus" not in self.data: From debd44727607d0cf751ea92c6025573c2aa5d517 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 18:29:01 +1000 Subject: [PATCH 62/63] Fix extra colon in fuelRange --- custom_components/fordpass/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/fordpass/sensor.py b/custom_components/fordpass/sensor.py index 188661c..bb876f6 100644 --- a/custom_components/fordpass/sensor.py +++ b/custom_components/fordpass/sensor.py @@ -247,7 +247,7 @@ def get_value(self, ftype): if self.sensor == "fuel": if "fuelRange" in self.data: if self.fordoptions[CONF_DISTANCE_UNIT] == "mi": - return {"fuelRange:": round( + return {"fuelRange": round( float(self.data["fuelRange"]["value"]) / 1.60934 )} return {"fuelRange": self.data["fuelRange"]["value"]} From 40b3acac2297a8a260040b95a0c21bbc07060519 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 12 Oct 2023 18:30:49 +1000 Subject: [PATCH 63/63] update info.md --- info.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/info.md b/info.md index ae0048a..1261184 100644 --- a/info.md +++ b/info.md @@ -1,10 +1,13 @@ ## **Changelog** ### Version 1.53 -*Warning this version is only a BETA and has a lot broken due to the massive API changes from Ford use at your own risk or wait a week!* - Updated vehicle endpoint to use new Autonomics API - Added secondary Autonomic token - Remapped commands to use new "command" API endpoint - Remapped existing sensors to new json variables (Some are missinge) +- Added charge status sensor (Thanks @SquidBytes) +- Added new speed sensor (Will be adding more attributes to this like pedal position and torque settings soon) + +*Please report any bugs as a separate issue so I can keep track easier* There is a LOT more coming soon as the new API exposes an excessive amount of information including speed, pedal position, crash sensors and way more. ### Version 1.52