Skip to content

Commit

Permalink
Merge pull request #372 from itchannel/1.58
Browse files Browse the repository at this point in the history
1.58
  • Loading branch information
itchannel authored Oct 18, 2023
2 parents a3ea0dd + de15dc9 commit c1c3173
Show file tree
Hide file tree
Showing 11 changed files with 605 additions and 360 deletions.
4 changes: 2 additions & 2 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ static_analysis:
tags: ["dev","python"]
allow_failure: true
script:
- flake8 --max-line-length=280 --ignore=W605,E275
- pylint . --recursive=y --disable=W0613,C0115,E0401,R0911,R0912,R0913,R0915,R0903,W0201,R1702,W1401,R0902,R0914 --max-line-length=280
- flake8 --max-line-length=280 --ignore=W605,E275,W503
- pylint . --recursive=y --disable=W0613,C0115,E0401,R0911,R0912,R0913,R0915,R0903,W0201,R1702,W1401,R0902,R0914,W0718,W1203,R0916,C0103,W0231,W0719,R0904 --max-line-length=280
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,49 @@

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/itchannel)

# Important Update: New Ford API Challenges and Updates

Dear FordPass Integration Users,

We want to keep you informed about the latest developments regarding the integration. Ford has recently introduced a new API, which has presented both opportunities and challenges. Here are some key points to note:

## Challenges and Issues:

- **Increased Complexity:** The new API has brought about several challenges and complexities. While it has enabled new attributes and sensors for different vehicles, it has also made integration more intricate.

- **Vehicle Variety:** In addition to an entirely new data structure, it is difficult to determine the variety of vehicles that can be supported - without knowing their capabilities. We are assessing which vehicles contain the information necessary to enable different sensors.

- **Unstable Nature of the New API:** Currently, the API appears to be unstable and/or changing. Any changes to its structure can disrupt sensor data. It is difficult to determine why: vehicles may still be in the process of migration, needing an update, or have a different data structure. Either way, instability has been noticed, and any updates or changes to the API can impact this integration.

## Ongoing Work and Future Updates:

- **Ongoing Work:** We both want to continue enhancing the integration and ensuring its functionality. However, it's important to note that we have limited time to allocate to this effort.

- **Future Updates:** We are continuing our efforts to adapt to new changes and persue further enhancments.


#### Please understand that there may be issues, or disruptions, to different sensors during this process.

If you have any questions or concerns, please either open a new issue or comment on an existing issue related to yours.

Thank you,

itchannel and SquidBytes

## Credit
- https://github.com/clarkd - Initial Home Assistant automation idea and Python code (Lock/Unlock)
- https://github.com/pinballnewf - Figuring out the application ID issue
- https://github.com/degrashopper - Fixing 401 error for certain installs
- https://github.com/tonesto7 - Extra window statuses and sensors
- https://github.com/JacobWasFramed - Updated unit conversions
- https://github.com/heehoo59 - French Translation
- https://github.com/SquidBytes - EV updates and documentation

## 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.

## 1.50 Change
As of 1.50 VIN number is no longer required for setup. Integration should display a list of vehicles associated with your Fordpass account

## 1.47 Change
If you are experiencing issues with the odometer displaying wrong, please try enabling the checkbox in options for "Disable Distance Conversion"

## **Changelog**
[Updates](info.md)

## Install
Use HACS and add as a custom repo. Once the integration is installed go to your integrations and follow the configuration options to specify the below:
Expand Down Expand Up @@ -75,4 +101,4 @@ This service allows you to manually refresh/poll the API without waiting the set

## Disclaimer

This integration is not officially supported by Ford and as such using this integration could result in your account being locked out!
This integration is not officially supported by Ford and as such using this integration could result in your account being locked out!
17 changes: 10 additions & 7 deletions custom_components/fordpass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ async def async_refresh_status_service(service_call):
await hass.async_add_executor_job(
refresh_status, hass, service_call, coordinator
)
await coordinator.async_request_refresh()

async def async_clear_tokens_service(service_call):
await hass.async_add_executor_job(clear_tokens, hass, service_call, coordinator)
Expand Down Expand Up @@ -157,10 +158,10 @@ def refresh_status(hass, service, coordinator):
_LOGGER.debug("Running Service")
vin = service.data.get("vin", "")
status = coordinator.vehicle.request_update(vin)
if status == 401:
_LOGGER.debug("Invalid VIN")
elif status == 200:
if status:
_LOGGER.debug("Refresh Sent")
return True
return False


def clear_tokens(hass, service, coordinator):
Expand Down Expand Up @@ -223,12 +224,14 @@ async def _async_update_data(self):

return data
except Exception as ex:
self._available = False # Mark as unavailable
# self._available = False # Mark as unavailable
_LOGGER.warning(str(ex))
_LOGGER.warning("Error communicating with FordPass for %s", self.vin)
raise UpdateFailed(
f"Error communicating with FordPass for {self.vin}"
) from ex
_LOGGER.warning("Returning Stale data to prevent unavaliable status")
if self.data:
if "metrics" in self.data:
return self.data
raise UpdateFailed(f"Error communicating with FordPass for {self.vin}") from ex


class FordPassEntity(CoordinatorEntity):
Expand Down
92 changes: 49 additions & 43 deletions custom_components/fordpass/autonomicData.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Script for grabbing vehicle JSON data from Autonomic API"""
from datetime import datetime
import glob
import json
import requests
import sys
import os
import re
import glob
from datetime import datetime
import requests


# Place this script in the /config/custom_components/fordpass folder on your HomeAssistant
# Add the details below
Expand All @@ -14,24 +16,23 @@
# USER INPUT DATA

# Required: Enter the VIN to query
fp_vin = ""
FP_VIN = ""

# Automatically redact json? (True or False) False is only recommended if you would like to save your json for personal use
redaction = True


REDACTION = True

# Optional: Enter your vehicle year (example: 2023)
vicYear = ""
VIC_YEAR = ""

# Optional: Enter your vehicle model (example: Lightning)
vicModel = ""

VIC_MODEL = ""

# You can turn off print statements if you want to use this script for other purposes (True or False)
verbose = True
VERBOSE = True


def get_autonomic_token(ford_access_token):
"""Get Autonomic API token from FordPass token"""
url = "https://accounts.autonomic.ai/v1/auth/oidc/token"
headers = {
"accept": "*/*",
Expand All @@ -46,15 +47,16 @@ def get_autonomic_token(ford_access_token):
}

try:
response = requests.post(url, headers=headers, data=data)
response = requests.post(url, headers=headers, data=data, timeout=10)
response.raise_for_status()
autonomic_token_data = response.json()
return autonomic_token_data

except requests.exceptions.HTTPError as errh:
print(f"HTTP Error: {errh}")
print(f"Trying refresh token")
get_autonomic_token(fpRefresh)
# print("Trying refresh token")
# get_autonomic_token(fp_refresh)
return None
except requests.exceptions.ConnectionError as errc:
print(f"Error Connecting: {errc}")
sys.exit()
Expand All @@ -67,26 +69,27 @@ def get_autonomic_token(ford_access_token):


def get_vehicle_status(vin, access_token):
BASE_URL = "https://api.autonomic.ai/"
"""Get vehicle status from Autonomic API"""
base_url = "https://api.autonomic.ai/"
endpoint = f"v1beta/telemetry/sources/fordpass/vehicles/{vin}:query"
url = f"{BASE_URL}{endpoint}"
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": "*/*"
}
redactionItems = ["lat", "lon", "vehicleId", "vin", "latitude", "longitude"]
redaction_items = ["lat", "lon", "vehicleId", "vin", "latitude", "longitude"]

try:
response = requests.post(url, headers=headers, json={})
response = requests.post(url, headers=headers, json={}, timeout=10)
response.raise_for_status() # Raise HTTPError for bad requests (4xx and 5xx status codes)

# Parse the JSON response
vehicle_status_data = response.json()

# Redact sensitive information
if redaction:
redact_json(vehicle_status_data, redactionItems)
if REDACTION:
redact_json(vehicle_status_data, redaction_items)
return vehicle_status_data

except requests.exceptions.HTTPError as errh:
Expand All @@ -97,8 +100,11 @@ def get_vehicle_status(vin, access_token):
print(f"Timeout Error: {errt}")
except requests.exceptions.RequestException as err:
print(f"Something went wrong: {err}")
return None


def redact_json(data, redaction):
"""Redact sensitive information from JSON data"""
# Regular expression to match GPS coordinates
gps_pattern = r'"gpsDegree":\s*-?\d+\.\d+,\s*"gpsFraction":\s*-?\d+\.\d+,\s*"gpsSign":\s*-?\d+\.\d+'

Expand All @@ -119,53 +125,53 @@ def redact_json(data, redaction):
elif isinstance(data, list):
for item in data:
redact_json(item, redaction)



if __name__ == "__main__":
fordPassDir = "/config/custom_components/fordpass"
existingfordToken = os.path.join(fordPassDir, "*_fordpass_token.txt")
FORD_PASS_DIR = "/config/custom_components/fordpass"
existingfordToken = os.path.join(FORD_PASS_DIR, "*_fordpass_token.txt")
userToken = glob.glob(existingfordToken)

if userToken:
for userTokenMatch in userToken:
with open(userTokenMatch, 'r') as file:
with open(userTokenMatch, 'r', encoding="utf-8") as file:
fp_token_data = json.load(file)
fpToken = fp_token_data['access_token']
fpRefresh = fp_token_data['refresh_token']
else:
print(f"Error finding FordPass token text file: {existingfordToken}, {userToken}")
sys.exit()

if fp_vin == "":
if FP_VIN == "":
print("Please enter your VIN into the python script")
sys.exit()

if verbose:
if VERBOSE:
print("Starting")
if redaction:
redactionStatus = "_REDACTED"
if verbose:
if REDACTION:
REDACTION_STATUS = "_REDACTED"
if VERBOSE:
print("Automatically redacting json")
else:
redactionStatus = ""
if verbose:
REDACTION_STATUS = ""
if VERBOSE:
print("WARNING: json will contain sensitive information!")
# Exchange Fordpass token for Autonomic Token
autonomic_token = get_autonomic_token(fpToken)
vehicle_status = get_vehicle_status(fp_vin, autonomic_token["access_token"])
vehicle_status = get_vehicle_status(FP_VIN, autonomic_token["access_token"])
current_datetime = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
if vicYear != "":
vicYear = vicYear.replace(" ", "_") + "-"
if vicModel != "":
vicModel = vicModel.replace(" ", "_")
if VIC_YEAR != "":
VIC_YEAR = VIC_YEAR.replace(" ", "_") + "-"
if VIC_MODEL != "":
VIC_MODEL = VIC_MODEL.replace(" ", "_")
else:
vicModel = "my"

VIC_MODEL = "my"

fileName = os.path.join(fordPassDir, f"{vicYear}{vicModel}_status_{current_datetime}{redactionStatus}.json")
fileName = os.path.join(FORD_PASS_DIR, f"{VIC_YEAR}{VIC_MODEL}_status_{current_datetime}{REDACTION_STATUS}.json")

# Write the redacted JSON data to the file
with open(fileName, 'w') as file:
with open(fileName, 'w', encoding="utf-8") as file:
json.dump(vehicle_status, file, indent=4)
if verbose:
if VERBOSE:
print(f"File saved: {fileName}")
print("Note: json file will be deleted if fordpass-ha is updated")
print("Note: json file will be deleted if fordpass-ha is updated")
6 changes: 3 additions & 3 deletions custom_components/fordpass/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
"elVehCharging": {"icon": "mdi:ev-station"},
"speed": {"icon": "mdi:speedometer"},
"indicators": {"icon": "mdi:engine-outline"},
"coolantTemp" : {"icon": "mdi:coolant-temperature", "state_class": "measurement", "device_class": "temperature"},
"outsideTemp" : {"icon": "mdi:thermometer", "state_class": "measurement", "device_class": "temperature"},
"engineOilTemp" : {"icon": "mdi:oil-temperature", "state_class": "measurement", "device_class": "temperature"},
"coolantTemp": {"icon": "mdi:coolant-temperature", "state_class": "measurement", "device_class": "temperature"},
"outsideTemp": {"icon": "mdi:thermometer", "state_class": "measurement", "device_class": "temperature"},
"engineOilTemp": {"icon": "mdi:oil-temperature", "state_class": "measurement", "device_class": "temperature"},
# "deepSleepInProgress": {
# "icon": "mdi:power-sleep",
# "name": "Deep Sleep Mode Active",
Expand Down
2 changes: 2 additions & 0 deletions custom_components/fordpass/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):


class CarTracker(FordPassEntity, TrackerEntity):
"""Class for the Car Tracker"""
def __init__(self, coordinator, sensor):

self._attr = {}
Expand Down Expand Up @@ -59,6 +60,7 @@ def device_id(self):

@property
def extra_state_attributes(self):
"""Return device tracker attributes"""
atts = {}
if "alt" in self.coordinator.data["metrics"]["position"]["value"]["location"]:
atts["Altitude"] = self.coordinator.data["metrics"]["position"]["value"]["location"]["alt"]
Expand Down
Loading

0 comments on commit c1c3173

Please sign in to comment.