├── images ├── 001.png ├── 002.png ├── 004.png ├── 005.png ├── 006.png ├── 007.png ├── 008.png ├── 009.png ├── 010.png ├── 011.png ├── 012.png ├── 003a.png └── 003b.png ├── .gitignore ├── hacs.json ├── .github ├── workflows │ ├── hassfest.yaml │ ├── hacs.yaml │ └── release.yaml └── ISSUE_TEMPLATE │ ├── 2-feature-request.yaml │ └── 1-report-an-issue.yaml ├── custom_components └── fordpass │ ├── manifest.json │ ├── services.yaml │ ├── device_tracker.py │ ├── button.py │ ├── switch.py │ ├── lock.py │ ├── sensor.py │ ├── number.py │ ├── select.py │ ├── const.py │ ├── translations │ ├── en.json │ └── de.json │ └── config_flow.py ├── doc ├── DEV-TOOLS.md ├── OBTAINING_TOKEN.md └── EVCC.md ├── info.md └── README.md /images/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/001.png -------------------------------------------------------------------------------- /images/002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/002.png -------------------------------------------------------------------------------- /images/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/004.png -------------------------------------------------------------------------------- /images/005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/005.png -------------------------------------------------------------------------------- /images/006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/006.png -------------------------------------------------------------------------------- /images/007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/007.png -------------------------------------------------------------------------------- /images/008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/008.png -------------------------------------------------------------------------------- /images/009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/009.png -------------------------------------------------------------------------------- /images/010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/010.png -------------------------------------------------------------------------------- /images/011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/011.png -------------------------------------------------------------------------------- /images/012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/012.png -------------------------------------------------------------------------------- /images/003a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/003a.png -------------------------------------------------------------------------------- /images/003b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marq24/ha-fordpass/HEAD/images/003b.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | pythonenv* 3 | .python-version 4 | .coverage 5 | venv 6 | .venv 7 | core.* 8 | *.iml -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fordpass", 3 | "content_in_root": false, 4 | "homeassistant": "2024.12.0", 5 | "hacs": "1.18.0", 6 | "render_readme": true 7 | } -------------------------------------------------------------------------------- /.github/workflows/hassfest.yaml: -------------------------------------------------------------------------------- 1 | name: Validate with hassfest 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * *' 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: "actions/checkout@v2" 14 | - uses: "home-assistant/actions/hassfest@master" 15 | -------------------------------------------------------------------------------- /.github/workflows/hacs.yaml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | hacs: 11 | name: HACS Action 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - uses: "actions/checkout@v2" 15 | - name: HACS Action 16 | uses: "hacs/action@main" 17 | with: 18 | category: "integration" 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this home assistant integration 3 | labels: enhancement 4 | body: 5 | - type: textarea 6 | id: content 7 | attributes: 8 | label: Description 9 | placeholder: "Let me know what do you miss..." 10 | - type: textarea 11 | id: logs 12 | attributes: 13 | label: JSON Object/Key 14 | placeholder: "When you miss some data - please let me know the JSON object, attribute or key that provide the data - TIA" 15 | render: shell -------------------------------------------------------------------------------- /custom_components/fordpass/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "fordpass", 3 | "name": "FordPass", 4 | "codeowners": ["@marq24", "@SquidBytes", "@itchannel"], 5 | "config_flow": true, 6 | "dependencies": [], 7 | "documentation": "https://github.com/marq24/ha-fordpass", 8 | "homekit": {}, 9 | "integration_type": "device", 10 | "iot_class": "cloud_push", 11 | "issue_tracker": "https://github.com/marq24/ha-fordpass/issues", 12 | "loggers": ["custom_components.fordpass"], 13 | "requirements": [], 14 | "ssdp": [], 15 | "version": "2025.12.9", 16 | "zeroconf": [] 17 | } -------------------------------------------------------------------------------- /custom_components/fordpass/services.yaml: -------------------------------------------------------------------------------- 1 | refresh_status: 2 | description: "Poll car for latest status (Takes up to 5mins to update once this function has been run!)" 3 | fields: 4 | vin: 5 | name: Vin 6 | description: "Parse a vin number to only refresh the specified vehicle (Default refreshes all added vehicles)" 7 | example: "1C4GJ25342B521742" 8 | selector: 9 | text: 10 | clear_tokens: 11 | description: "Clear the cached tokens" 12 | reload: 13 | name: Reload 14 | description: "Reload the Fordpass Integration" 15 | poll_api: 16 | name: Poll API 17 | description: "Manually poll API for data update (Warning: doing this too often could result in a ban)" -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Zip Release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | release: 10 | name: Release fordpass 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | steps: 15 | # Step 1: Check out the code 16 | - name: ⤵️ Check out code from GitHub 17 | uses: actions/checkout@v4 18 | 19 | # Step 2: (Optional, but recommended) Install yq (in case it's not present) 20 | #- name: Install yq 21 | # run: sudo apt-get update && sudo apt-get install -y yq 22 | 23 | # Step 3: Update version in manifest.json to match the release tag 24 | #- name: 🔢 Adjust version number 25 | # shell: bash 26 | # run: | 27 | # version="${{ github.event.release.tag_name }}" 28 | # version="${version,,}" # convert to lowercase (e.g., V2024.10.1 -> v2024.10.1) 29 | # version="${version#v}" # remove leading 'v' if present 30 | # yq e -P -o=json \ 31 | # -i ".version = \"${version}\"" \ 32 | # "${{ github.workspace }}/custom_components/fordpass/manifest.json" 33 | 34 | # Step 4: Create a ZIP archive of the component directory 35 | - name: 📦 Create zipped release package 36 | shell: bash 37 | run: | 38 | cd "${{ github.workspace }}/custom_components/fordpass" 39 | zip fordpass.zip -r ./ 40 | 41 | # Step 5: Upload ZIP as a release asset (for HACS) 42 | - name: ⬆️ Upload zip to release 43 | uses: softprops/action-gh-release@v2 44 | with: 45 | files: ${{ github.workspace }}/custom_components/fordpass/fordpass.zip -------------------------------------------------------------------------------- /custom_components/fordpass/device_tracker.py: -------------------------------------------------------------------------------- 1 | """Vehicle Tracker Sensor""" 2 | import logging 3 | 4 | from homeassistant.components.device_tracker import SourceType 5 | from homeassistant.components.device_tracker.config_entry import TrackerEntity 6 | 7 | from custom_components.fordpass import FordPassEntity 8 | from custom_components.fordpass.const import DOMAIN, COORDINATOR_KEY 9 | from custom_components.fordpass.const_tags import Tag 10 | from custom_components.fordpass.fordpass_handler import FordpassDataHandler, UNSUPPORTED 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | async def async_setup_entry(hass, config_entry, async_add_entities): 16 | """Add the Entities from the config.""" 17 | coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR_KEY] 18 | _LOGGER.debug(f"{coordinator.vli}DEVICE_TRACKER async_setup_entry") 19 | 20 | # Added a check to see if the car supports GPS 21 | if FordpassDataHandler.get_gps_state(coordinator.data) != UNSUPPORTED: 22 | async_add_entities([FordPassCarTracker(coordinator)], True) 23 | else: 24 | _LOGGER.debug(f"{coordinator.vli}Vehicle does not support GPS") 25 | 26 | 27 | class FordPassCarTracker(FordPassEntity, TrackerEntity): 28 | def __init__(self, coordinator): 29 | super().__init__(a_tag=Tag.TRACKER, coordinator=coordinator) 30 | 31 | @property 32 | def latitude(self): 33 | return FordpassDataHandler.get_gps_lat(self.coordinator.data) 34 | 35 | @property 36 | def longitude(self): 37 | return FordpassDataHandler.get_gps_lon(self.coordinator.data) 38 | 39 | @property 40 | def source_type(self): 41 | """Set source type to GPS""" 42 | return SourceType.GPS 43 | 44 | @property 45 | def extra_state_attributes(self): 46 | """Return the state attributes of the tracker.""" 47 | # we don't need units here! 48 | return self._tag.get_attributes(self.coordinator.data, None ) 49 | 50 | @property 51 | def icon(self): 52 | """Return device tracker icon""" 53 | return "mdi:radar" 54 | -------------------------------------------------------------------------------- /doc/DEV-TOOLS.md: -------------------------------------------------------------------------------- 1 | ## Chromium Based (Chrome / Edge) 2 | 3 | ❗Photos are taken in Google Chrome. Edge will look differently but the same steps apply 4 | 5 | > [!NOTE] 6 | > It is recommended to disable any ad-blockers or extensions that might interfere with this process. 7 | Alternatively, you can use a private window. 8 | 9 | ### **Step 1:** 10 | 1. Open developer tools by pressing F12 on the keyboard 11 | 2. Navigate to the URL provided through HomeAssistant 12 | 13 | ![image](./../images/004.png) 14 | 15 | ### **Step 2:** 16 | 1. Enter your credentials to login to Ford with the developer tools opened 17 | > If developer tools are not expanded, ensure you are on the network section. 18 | 19 | ![image](./../images/005.png) 20 | 21 | 2. Press *Sign In* 22 | > The spinning circle will not load, this is normal. It is at this point you will obtain your token 23 | 3. Under the network tab look for an item starting with `?code=` 24 | 25 | ![image](./../images/006.png) 26 | 27 | 4. Select the `?code=` item within the `Name` box 28 | > A new window will open displaying the headers 29 | 30 | ![image](./../images/007.png) 31 | 32 | 5. Select **the entire string** and copy it to the clipboard. 33 | > Ctrl + C 34 | 6. Proceed with installation. 35 | 36 | *** 37 | 38 | ## Firefox 39 | 40 | > [!WARNING] 41 | > Even if Firefox support developer tools by pressing F12 on the keyboard, for some reason the logging of request URL's does not work in a similar way then with chrome based browsers. 42 | > 43 | > Technically it is also possible to make use of Firefox to capture the _required_ token URL via the developer tools - but for your mental safety please consider using a different browser for this operation. 44 | 45 | __I will keep the instructions for Firefox here as reference - but let me repeat, that I recommend to use a chrome based browser to perform this operation.__ 46 | 47 | --- 48 | ### Just as reference, if you really want to give it a try with Firefox 49 | 50 | > [!NOTE] 51 | > It is recommended to disable any ad-blockers or extension that might interfere with this process. Alternatively, you can use a private window. 52 | Users have also reported issues using Firefox specifically. 53 | **If you're having trouble using Firefox it is recommended to try using a Chromium based browser.** 54 | 55 | #### **Step 1:** 56 | 1. Open developer tools by pressing F12 on the keyboard 57 | 2. Navigate to the URL provided through HomeAssistant 58 | 59 | ![image](./../images/008.png) 60 | 61 | #### **Step 2:** 62 | 1. Enter your credentials to login to Ford with the developer tools opened 63 | 2. Press *Sign In* 64 | 3. Select the item populated under Network (Item 1 in the photo below) 65 | > This will open a new section 66 | 4. In the `Headers` tab, scroll down until you find `Response Headers` (Item 2 in the photo below) 67 | 68 | ![image](./../images/009.png) 69 | 70 | 5. Find the `Location` section and copy the entire string. 71 | 72 | ![image](./../images/010.png) 73 | 74 | 6. Proceed with installation 75 | -------------------------------------------------------------------------------- /custom_components/fordpass/button.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from homeassistant.components.button import ButtonEntity 4 | from homeassistant.config_entries import ConfigEntry 5 | from homeassistant.core import HomeAssistant 6 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 7 | 8 | from custom_components.fordpass import FordPassEntity, FordPassDataUpdateCoordinator 9 | from custom_components.fordpass.const import DOMAIN, COORDINATOR_KEY, REMOTE_START_STATE_ACTIVE 10 | from custom_components.fordpass.const_tags import BUTTONS, Tag, ExtButtonEntityDescription 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, add_entity_cb: AddEntitiesCallback): 16 | coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR_KEY] 17 | _LOGGER.debug(f"{coordinator.vli}BUTTON async_setup_entry") 18 | entities = [] 19 | 20 | for a_entity_description in BUTTONS: 21 | a_entity_description: ExtButtonEntityDescription 22 | 23 | if coordinator.tag_not_supported_by_vehicle(a_entity_description.tag): 24 | _LOGGER.debug(f"{coordinator.vli}BUTTON '{a_entity_description.tag}' not supported for this engine-type/vehicle") 25 | continue 26 | 27 | button = FordpassButton(coordinator, a_entity_description) 28 | entities.append(button) 29 | 30 | add_entity_cb(entities) 31 | 32 | 33 | class FordpassButton(FordPassEntity, ButtonEntity): 34 | def __init__(self, coordinator:FordPassDataUpdateCoordinator, entity_description:ExtButtonEntityDescription): 35 | super().__init__(a_tag=entity_description.tag, coordinator=coordinator, description=entity_description) 36 | 37 | async def async_press(self, **kwargs): 38 | try: 39 | await self._tag.async_push(self.coordinator, self.coordinator.bridge) 40 | except ValueError: 41 | return "unavailable" 42 | 43 | @property 44 | def available(self): 45 | """Return True if entity is available.""" 46 | state = super().available 47 | if self._tag in [Tag.EV_START, Tag.EV_CANCEL, Tag.EV_PAUSE]: 48 | return state and Tag.EVCC_STATUS.get_state(self.coordinator.data) in ["B", "C"] 49 | elif self._tag == Tag.EXTEND_REMOTE_START: 50 | return state and Tag.REMOTE_START_STATUS.get_state(self.coordinator.data) == REMOTE_START_STATE_ACTIVE 51 | 52 | # elif self._tag in [Tag.MESSAGES_DELETE_LAST, Tag.MESSAGES_DELETE_ALL]: 53 | # val = Tag.MESSAGES.get_state(self.coordinator.data) 54 | # if val is not None and int(val) > 0: 55 | # return state 56 | # else: 57 | # return False 58 | 59 | # elif self._tag == Tag.DOOR_LOCK: 60 | # return state and Tag.ALARM.get_state(self.coordinator.data).upper() != "ARMED" 61 | # elif self._tag == Tag.DOOR_UNLOCK: 62 | # return state and Tag.ALARM.get_state(self.coordinator.data).upper() != "DISARMED" 63 | 64 | return state -------------------------------------------------------------------------------- /custom_components/fordpass/switch.py: -------------------------------------------------------------------------------- 1 | """Fordpass Switch Entities""" 2 | import logging 3 | 4 | from homeassistant.components.switch import SwitchEntity 5 | 6 | from custom_components.fordpass import FordPassEntity, RCC_TAGS 7 | from custom_components.fordpass.const import DOMAIN, COORDINATOR_KEY 8 | from custom_components.fordpass.const_tags import SWITCHES, Tag 9 | from custom_components.fordpass.fordpass_handler import UNSUPPORTED 10 | 11 | _LOGGER = logging.getLogger(__name__) 12 | 13 | 14 | async def async_setup_entry(hass, config_entry, async_add_entities): 15 | """Add the Switch from the config.""" 16 | coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR_KEY] 17 | _LOGGER.debug(f"{coordinator.vli}SWITCH async_setup_entry") 18 | entities = [] 19 | for a_tag, value in SWITCHES.items(): 20 | if coordinator.tag_not_supported_by_vehicle(a_tag): 21 | _LOGGER.debug(f"{coordinator.vli}SWITCH '{a_tag}' not supported for this vehicle") 22 | continue 23 | 24 | sw = FordPassSwitch(coordinator, a_tag) 25 | entities.append(sw) 26 | 27 | async_add_entities(entities, True) 28 | 29 | 30 | class FordPassSwitch(FordPassEntity, SwitchEntity): 31 | """Define the Switch for turning ignition off/on""" 32 | 33 | def __init__(self, coordinator, a_tag: Tag): 34 | """Initialize""" 35 | super().__init__(a_tag=a_tag, coordinator=coordinator) 36 | 37 | async def async_turn_on(self, **kwargs): 38 | """Send request to vehicle on switch status on""" 39 | await self._tag.turn_on_off(self.coordinator.data, self.coordinator.bridge, True) 40 | await self.coordinator.async_request_refresh() 41 | self.async_write_ha_state() 42 | 43 | async def async_turn_off(self, **kwargs): 44 | """Send request to vehicle on switch status off""" 45 | await self._tag.turn_on_off(self.coordinator.data, self.coordinator.bridge, False) 46 | await self.coordinator.async_request_refresh() 47 | self.async_write_ha_state() 48 | 49 | @property 50 | def is_on(self): 51 | """Check the status of switch""" 52 | state = self._tag.get_state(self.coordinator.data) 53 | #_LOGGER.error(f"{self.coordinator.vli} SWITCH '{self._tag}' - state: {state}") 54 | if state is not None and state is not UNSUPPORTED: 55 | return state.upper() == "ON" 56 | else: 57 | return None 58 | 59 | @property 60 | def icon(self): 61 | """Return icon for switch""" 62 | return SWITCHES[self._tag]["icon"] 63 | 64 | @property 65 | def available(self): 66 | """Return True if entity is available.""" 67 | state = super().available 68 | if self._tag == Tag.ELVEH_CHARGE: 69 | return state and Tag.EVCC_STATUS.get_state(self.coordinator.data) in ["B", "C"] 70 | elif self._tag in RCC_TAGS: 71 | return state #and Tag.REMOTE_START_STATUS.get_state(self.coordinator.data) == REMOTE_START_STATE_ACTIVE 72 | return state 73 | -------------------------------------------------------------------------------- /doc/OBTAINING_TOKEN.md: -------------------------------------------------------------------------------- 1 | # Half-manual Login Flow 2 | ### This method does require some manual steps, **including the usage of the developer tools!** of your browser 3 | 4 | > [!WARNING] 5 | > Although this has been tested multiple times (in January–June 2025), It can't be guaranteed the method will last as Ford is consistently making changes! 6 | 7 | > [!NOTE] 8 | > This process requires [at least the 2025.10.2 version](https://github.com/marq24/ha-fordpass/releases/tag/2025.10.2) of this fordpass integration. 9 | 10 | 11 | ### **Step I:** 12 | 1. In Home Assistant: Enter your Fordpass username 13 | 2. You can select an alternative Region, __but currently I do not expect that any other region then `USA` will work__. 14 | 15 | ![image](./../images/001.png) 16 | 17 | 18 | ### **Step II:** 19 | 1. Copy the URL that has been generated and paste it in your additional/separate browser. You might like to use a private/incognito window for this. 20 | 21 | > [!IMPORTANT] 22 | > Ensure you have enabled the Developer tools before pressing "log in" as you will be required to capture a header once logged in! 23 | > - [:link: Chromium based dev tools helper (Chrome / Edge)](./DEV-TOOLS.md) 24 | > 25 | > As _alternative_, you might also like to use Firefox (but I __can not recommend__ this, since plenty of users have issues when using Firefox): 26 | > - [:link: Firefox dev tools helper](./DEV-TOOLS.md#firefox) 27 | 28 | 29 | ![image](./../images/002.png) 30 | 31 | 2. In your second/separate browser (where you paste the URL), the Ford Login dialog should be displayed. 32 | 33 | 3. Enter your FordPass credentials and click `Sign In`. 34 | > [!NOTE] 35 | > After you have pressed the login button, the Ford Login website will just show a spinner and will not continue to load — this is the intended behavior! At this point you are able to obtain the code by using the browser tools (see next step 4). 36 | 37 | ![webrequst](./../images/003a.png) 38 | 39 | 4. Now you must use the browser tools and select the `Network tab` of the web console and view the headers section. 40 | 41 | - The last request (probably already showed in red) is the one we are interested in... Since this last request contains the code we must capture for the integration, it should start with `userauthorized/...` 42 | - You are looking for the contents of the "Location Header" as shown in the pic below 43 | - The output should look similar to the following string, starting with `fordapp://` (or `lincolnapp://`): 44 | - ```fordapp://userauthorized/?code=eyJraWQiOiItSm9pdi1OX1ktUWNsa***************************``` 45 | - **Ensure you capture the entire string (copy the raw output and not the wrapped text)** and enter it into the text box of the home assistant setup dialog. 46 | - You then can close the Ford login browser window 47 | 48 | > [!NOTE] 49 | > Again - the Ford login website will not __fully loaded__. The login page will just continue to spin. 50 | 51 | ![webrequst](./../images/003b.png) 52 | 53 | 54 | ### **Step III:** 55 | - Once you've entered the copied token back in the home assistant integration setup dialog the integration should go off and get you a new set of tokens and then ask what vehicles you want to add. -------------------------------------------------------------------------------- /custom_components/fordpass/lock.py: -------------------------------------------------------------------------------- 1 | """Represents the primary lock of the vehicle.""" 2 | import asyncio 3 | import logging 4 | 5 | from homeassistant.components.lock import LockEntity 6 | 7 | from custom_components.fordpass import FordPassEntity 8 | from custom_components.fordpass.const import DOMAIN, COORDINATOR_KEY, VEHICLE_LOCK_STATE_LOCKED 9 | from custom_components.fordpass.const_tags import Tag 10 | from custom_components.fordpass.fordpass_handler import UNSUPPORTED 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | async def async_setup_entry(hass, config_entry, async_add_entities): 16 | """Add the lock from the config.""" 17 | coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR_KEY] 18 | _LOGGER.debug(f"{coordinator.vli}LOCK async_setup_entry") 19 | if coordinator.data is not None: 20 | lock_state = Tag.DOOR_LOCK.get_state(coordinator.data) 21 | if lock_state != UNSUPPORTED and lock_state.upper() != "ERROR": 22 | async_add_entities([FordPassLock(coordinator)], False) 23 | else: 24 | _LOGGER.debug(f"{coordinator.vli}Ford model doesn't support remote locking") 25 | else: 26 | _LOGGER.debug(f"{coordinator.vli}Ford model doesn't support remote locking") 27 | 28 | 29 | 30 | class FordPassLock(FordPassEntity, LockEntity): 31 | """Defines the vehicle's lock.""" 32 | def __init__(self, coordinator): 33 | super().__init__(a_tag=Tag.DOOR_LOCK, coordinator=coordinator) 34 | 35 | async def async_lock(self, **kwargs): 36 | """Locks the vehicle.""" 37 | self._attr_is_locking = True 38 | self.async_write_ha_state() 39 | status = await self.coordinator.bridge.lock() 40 | _LOGGER.debug(f"async_lock status: {status}") 41 | 42 | if self.coordinator._supports_ALARM: 43 | await asyncio.sleep(5) 44 | if Tag.ALARM.get_state(self.coordinator.data).upper() != "ARMED": 45 | await asyncio.sleep(25) 46 | if Tag.ALARM.get_state(self.coordinator.data).upper() != "ARMED": 47 | await self.coordinator.bridge.request_update() 48 | 49 | _LOGGER.debug(f"async_lock status: {status} - after waiting for alarm 'ARMED' state") 50 | 51 | self._attr_is_locking = False 52 | self.async_write_ha_state() 53 | 54 | async def async_unlock(self, **kwargs): 55 | """Unlocks the vehicle.""" 56 | self._attr_is_unlocking = True 57 | self.async_write_ha_state() 58 | status = await self.coordinator.bridge.unlock() 59 | _LOGGER.debug(f"async_unlock status: {status}") 60 | self._attr_is_unlocking = False 61 | self.async_write_ha_state() 62 | 63 | @property 64 | def is_locked(self): 65 | """Determine if the lock is locked.""" 66 | lock_state = self._tag.get_state(self.coordinator.data) 67 | if lock_state != UNSUPPORTED: 68 | return lock_state == VEHICLE_LOCK_STATE_LOCKED 69 | return None 70 | 71 | @property 72 | def icon(self): 73 | if self.is_locked: 74 | return "mdi:car-door-lock" 75 | else: 76 | return "mdi:car-door-lock-open" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-report-an-issue.yaml: -------------------------------------------------------------------------------- 1 | name: Problem Report 2 | description: please report any technical issue with this home assistant integration - please note this is not a official Ford repository or service 3 | labels: bug 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## HACs does not always notify you about new version's - you must check do this manually! 9 | Please follow this routine: 10 | 1. In Home Assistant go to `HACS` 11 | 2. In the list of installed integrations search for `fordpass` 12 | 3. Click on the 3-dot menu on the right side of the `Fordpass integration for Home Assistant [fork optimized for EV's & EVCC]` integration list entry 13 | 4. Click on `Update Information` 14 | 5. Note now that a new version is available 15 | 6. Install the update 16 | - type: checkboxes 17 | id: checklist 18 | attributes: 19 | label: Checklist 20 | description: Please go though this short checklist - read each point carefully - TIA 21 | options: 22 | - label: I am aware, that marq24 is a grumpy old guy, so __it can happen__ that my issue will be closed instantly when essential information is missing (e.g. no debug logs provided, no issue description, no steps to reproduce the issue, etc...). 23 | required: true 24 | - label: I know, that any attachment send via eMail reply to a github issue notifications will be __ignored and deleted__ by github automatically. With other words __this will not work__! 25 | required: true 26 | - label: I confirm that I have [read & understand the main Integration 'check-list'](https://github.com/marq24/ha-fordpass/discussions/80) 27 | required: true 28 | - label: I confirm, that I do not have any other fordpass integration installed/configured (please also check deactivated configurations) 29 | required: true 30 | - label: My home assistant version is up to date. 31 | required: true 32 | - label: I am using the **latest** version of the integration | See the [release list @github](https://github.com/marq24/ha-fordpass/releases) or in home assistant use the HACS 'Update Information' function to ensure that the **latest** released version of the integration is installed. This HACS function can be accessed via the 3-dot menu on the right side for each integration in the HACS integration list. 33 | required: true 34 | - label: I have [checked all issues (incl. the closed ones)](https://github.com/marq24/ha-fordpass/issues?q=is%3Aissue+is%3Aclosed) for similar issues in order to avoid reporting a duplicate issue . 35 | required: true 36 | - label: I have prepared DEBUG log output | In most of the cases of a technical error/issue or if you miss data, I would have the need to ask for DEBUG log output of the integration. There is a short [tutorial/guide 'How to provide DEBUG log' here](https://github.com/marq24/ha-senec-v3/blob/main/docs/HA_DEBUG.md). If no debug log is provided, the issue will be closed instantly. 37 | required: true 38 | - label: I confirm it's really an issue | In the case that you want to understand the functionality of a certain feature/sensor Please be so kind and make use if the discussion feature of this repo (and do not create an issue) - TIA 39 | - label: | 40 | I confirm, that I did not read any of the previous bulletin-points and just checked them all. | I don't wanted to waste my time with details, I don't read or follow any existing instructions. | Instead, I want that the maintainer of this repo will spend time explaining the world to me — that's marq24's job!. | I live by the motto: Better to ask twice, than to think about it once. | It's marq24's own fault that he provides open-source software and is willing to offer free support. 41 | - type: textarea 42 | id: content 43 | attributes: 44 | label: Add a description 45 | placeholder: "Please provide details about your issue - in the best case a short step by step instruction how to reproduce the issue - TIA." 46 | - type: textarea 47 | id: logs 48 | attributes: 49 | label: Add your DEBUG log output 50 | placeholder: "Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks." 51 | render: shell 52 | -------------------------------------------------------------------------------- /custom_components/fordpass/sensor.py: -------------------------------------------------------------------------------- 1 | """All vehicle sensors from the accessible by the API""" 2 | import logging 3 | from dataclasses import replace 4 | from numbers import Number 5 | 6 | from homeassistant.components.sensor import SensorEntity, SensorDeviceClass 7 | from homeassistant.config_entries import ConfigEntry 8 | from homeassistant.core import HomeAssistant 9 | from homeassistant.helpers.restore_state import RestoreEntity # , async_get, RestoreStateData 10 | 11 | from custom_components.fordpass import FordPassEntity, FordPassDataUpdateCoordinator, ROOT_METRICS 12 | from custom_components.fordpass.const import DOMAIN, COORDINATOR_KEY, REMOTE_START_STATE_ACTIVE 13 | from custom_components.fordpass.const_tags import SENSORS, ExtSensorEntityDescription, Tag 14 | from custom_components.fordpass.fordpass_handler import UNSUPPORTED 15 | 16 | _LOGGER = logging.getLogger(__name__) 17 | 18 | 19 | async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities): 20 | """Add the Entities from the config.""" 21 | coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR_KEY] 22 | _LOGGER.debug(f"{coordinator.vli}SENSOR async_setup_entry") 23 | sensors = [] 24 | 25 | check_data_availability = coordinator.data is not None and len(coordinator.data.get(ROOT_METRICS, {})) > 0 26 | #storage = async_get(hass) 27 | 28 | for a_entity_description in SENSORS: 29 | a_entity_description: ExtSensorEntityDescription 30 | 31 | if coordinator.tag_not_supported_by_vehicle(a_entity_description.tag): 32 | _LOGGER.debug(f"{coordinator.vli}SENSOR '{a_entity_description.tag}' not supported for this engine-type/vehicle") 33 | continue 34 | 35 | sensor = FordPassSensor(coordinator, a_entity_description) 36 | # # we want to restore the last known 'id' of the energyTransferLogs 37 | # if a_entity_description.tag == Tag.LAST_ENERGY_TRANSFER_LOG_ENTRY: 38 | # #restored_value = storage.last_states.get(sensor.entity_id, None) 39 | # restored_entity = storage.entities.get(sensor.entity_id, None) 40 | # if restored_entity is not None: 41 | # restored_extra_data = await restored_entity.async_get_last_extra_data() 42 | # if restored_extra_data is not None and "id" in restored_extra_data and restored_extra_data["id"] is not None: 43 | # coordinator._last_ENERGY_TRANSFER_LOG_ENTRY_ID = restored_extra_data["id"] 44 | # 45 | # # if restored_value is not None or restored_value != UNSUPPORTED: 46 | # # coordinator._last_ENERGY_TRANSFER_LOG_ENTRY_ID = restored_value 47 | # # _LOGGER.debug(f"{a_entity_description.tag} -> RESTORED value {restored_value}") 48 | # # else: 49 | # # coordinator._last_ENERGY_TRANSFER_LOG_ENTRY_ID = None 50 | # # _LOGGER.debug(f"{a_entity_description.tag} no VALUE to RESTORE {restored_value}") 51 | 52 | if a_entity_description.skip_existence_check or not check_data_availability: 53 | sensors.append(sensor) 54 | else: 55 | # calling the state reading function to check if the sensor should be added (if there is any data) 56 | value = a_entity_description.tag.state_fn(coordinator.data) 57 | if value is not None and ((isinstance(value, (str, Number)) and str(value) != UNSUPPORTED) or 58 | (isinstance(value, (dict, list)) and len(value) != 0) ): 59 | sensors.append(sensor) 60 | else: 61 | _LOGGER.debug(f"{coordinator.vli}SENSOR '{a_entity_description.tag}' skipping cause no data available: type: {type(value).__name__} - value:'{value}'") 62 | 63 | async_add_entities(sensors, True) 64 | 65 | # def check_if_previous_data_was_available(storage: RestoreStateData, sensor: RestoreEntity) -> bool: 66 | # last_sensor_data = storage.last_states.get(sensor.entity_id) 67 | # _LOGGER.error(f"{sensor._tag} {last_sensor_data}") 68 | # return last_sensor_data is not None and last_sensor_data.state not in (None, UNSUPPORTED) 69 | 70 | 71 | class FordPassSensor(FordPassEntity, SensorEntity, RestoreEntity): 72 | 73 | def __init__(self, coordinator:FordPassDataUpdateCoordinator, entity_description:ExtSensorEntityDescription): 74 | # make sure that we set the device class for battery sensors [see #89] 75 | if (coordinator.has_ev_soc and entity_description.tag == Tag.SOC) or (not coordinator.has_ev_soc and entity_description.tag == Tag.BATTERY): 76 | entity_description = replace( 77 | entity_description, 78 | device_class=SensorDeviceClass.BATTERY 79 | ) 80 | super().__init__(a_tag=entity_description.tag, coordinator=coordinator, description=entity_description) 81 | 82 | @property 83 | def extra_state_attributes(self): 84 | """Return sensor attributes""" 85 | return self._tag.get_attributes(self.coordinator.data, self.coordinator.units) 86 | 87 | @property 88 | def native_value(self): 89 | """Return Native Value""" 90 | return self._tag.get_state(self.coordinator.data) 91 | 92 | @property 93 | def available(self): 94 | """Return True if entity is available.""" 95 | state = super().available 96 | # the countdown sensor can be always active (does not hurt) 97 | # if self._tag == Tag.REMOTE_START_COUNTDOWN: 98 | # return state and Tag.REMOTE_START_STATUS.get_state(self.coordinator.data) == REMOTE_START_STATE_ACTIVE 99 | 100 | return state -------------------------------------------------------------------------------- /custom_components/fordpass/number.py: -------------------------------------------------------------------------------- 1 | """Fordpass Switch Entities""" 2 | import logging 3 | from dataclasses import replace 4 | from numbers import Number 5 | 6 | from homeassistant.components.number import NumberEntity 7 | from homeassistant.const import UnitOfTemperature 8 | 9 | from custom_components.fordpass import FordPassEntity, RCC_TAGS, FordPassDataUpdateCoordinator 10 | from custom_components.fordpass.const import DOMAIN, COORDINATOR_KEY 11 | from custom_components.fordpass.const_tags import Tag, NUMBERS, ExtNumberEntityDescription 12 | from custom_components.fordpass.fordpass_handler import UNSUPPORTED, ROOT_METRICS 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | async def async_setup_entry(hass, config_entry, async_add_entities): 18 | """Add the Switch from the config.""" 19 | coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR_KEY] 20 | _LOGGER.debug(f"{coordinator.vli}NUMBER async_setup_entry") 21 | entities = [] 22 | check_data_availability = coordinator.data is not None and len(coordinator.data.get(ROOT_METRICS, {})) > 0 23 | 24 | for a_entity_description in NUMBERS: 25 | a_entity_description: ExtNumberEntityDescription 26 | 27 | if coordinator.tag_not_supported_by_vehicle(a_entity_description.tag): 28 | _LOGGER.debug(f"{coordinator.vli}NUMBER '{a_entity_description.tag}' not supported for this engine-type/vehicle") 29 | continue 30 | 31 | entity = FordPassNumber(coordinator, a_entity_description) 32 | if a_entity_description.skip_existence_check or not check_data_availability: 33 | entities.append(entity) 34 | else: 35 | # calling the state reading function to check if the entity should be added (if there is any data) 36 | value = a_entity_description.tag.state_fn(coordinator.data) 37 | if value is not None and ((isinstance(value, (str, Number)) and str(value) != UNSUPPORTED) or 38 | (isinstance(value, (dict, list)) and len(value) != 0) ): 39 | entities.append(entity) 40 | else: 41 | _LOGGER.debug(f"{coordinator.vli}NUMBER '{a_entity_description.tag}' skipping cause no data available: type: {type(value).__name__} - value:'{value}'") 42 | 43 | async_add_entities(entities, True) 44 | 45 | 46 | class FordPassNumber(FordPassEntity, NumberEntity): 47 | """Define the Switch for turning ignition off/on""" 48 | 49 | def __init__(self, coordinator: FordPassDataUpdateCoordinator, entity_description: ExtNumberEntityDescription): 50 | self.translate_from_to_fahrenheit = False 51 | if entity_description.native_unit_of_measurement == UnitOfTemperature.CELSIUS and coordinator.units.temperature_unit == UnitOfTemperature.FAHRENHEIT: 52 | # C * 9/5 + 32 = F 53 | # (F - 32) * 5/9 = C 54 | self.translate_from_to_fahrenheit = True 55 | entity_description = replace( 56 | entity_description, 57 | native_unit_of_measurement=coordinator.units.temperature_unit, 58 | native_step=1, 59 | native_max_value=round(entity_description.native_max_value * 1.8 + 32, 0), 60 | native_min_value=round(entity_description.native_min_value * 1.8 + 32, 0) 61 | ) 62 | 63 | super().__init__(a_tag=entity_description.tag, coordinator=coordinator, description=entity_description) 64 | 65 | @property 66 | def extra_state_attributes(self): 67 | """Return sensor attributes""" 68 | return self._tag.get_attributes(self.coordinator.data, self.coordinator.units) 69 | 70 | @property 71 | def native_value(self): 72 | """Return Native Value""" 73 | try: 74 | value = self._tag.get_state(self.coordinator.data) 75 | if value is not None and str(value) != UNSUPPORTED: 76 | if self._tag == Tag.RCC_TEMPERATURE: 77 | # the latest fordPass App also support "HI" and "LO" 78 | # as 'valid' values for the remote temperature... 79 | # which sucks some sort of - since we must MAP this 80 | # to our number Field 81 | if str(value).upper() == "HI": 82 | value = 30.5 83 | elif str(value).upper() == "LO": 84 | value = 15.5 85 | 86 | if self.translate_from_to_fahrenheit: 87 | value = round(value * 1.8 + 32, 0) 88 | 89 | return value 90 | 91 | except ValueError: 92 | _LOGGER.debug(f"{self.coordinator.vli}NUMBER '{self._tag}' native_value() [or internal get_state()] failed with ValueError") 93 | 94 | return None 95 | 96 | async def async_set_native_value(self, value) -> None: 97 | try: 98 | if value is None or str(value) == "null" or str(value).lower() == "none": 99 | await self._tag.async_set_value(self.coordinator.data, self.coordinator.bridge, None) 100 | else: 101 | if self._tag == Tag.RCC_TEMPERATURE: 102 | if self.translate_from_to_fahrenheit: 103 | # we want the value in Celsius, but the user provided Fahrenheit... and we want it 104 | # in steps of 0.5 °C 105 | value = round(((float(value) - 32) / 1.8) * 2, 0) / 2 106 | 107 | # we use 15.5°C as LO and 30.5°C as HI 108 | if value < 16: 109 | value = "LO" 110 | elif value > 30: 111 | value = "HI" 112 | 113 | await self._tag.async_set_value(self.coordinator.data, self.coordinator.bridge, str(value)) 114 | 115 | except ValueError: 116 | return None 117 | 118 | @property 119 | def available(self): 120 | """Return True if entity is available.""" 121 | state = super().available 122 | if self._tag in RCC_TAGS: 123 | return state #and Tag.REMOTE_START_STATUS.get_state(self.coordinator.data) == REMOTE_START_STATE_ACTIVE 124 | return state 125 | -------------------------------------------------------------------------------- /custom_components/fordpass/select.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import replace 3 | from numbers import Number 4 | 5 | from homeassistant.components.select import SelectEntity 6 | from homeassistant.config_entries import ConfigEntry 7 | from homeassistant.const import UnitOfTemperature 8 | from homeassistant.core import HomeAssistant 9 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 10 | 11 | from custom_components.fordpass.const import ( 12 | DOMAIN, 13 | COORDINATOR_KEY, 14 | RCC_SEAT_MODE_HEAT_ONLY, RCC_SEAT_OPTIONS_HEAT_ONLY, RCC_TEMPERATURES_CELSIUS 15 | ) 16 | from custom_components.fordpass.const_tags import SELECTS, ExtSelectEntityDescription, Tag, RCC_TAGS 17 | from . import FordPassEntity, FordPassDataUpdateCoordinator, UNSUPPORTED, FordpassDataHandler, ROOT_METRICS 18 | 19 | _LOGGER = logging.getLogger(__name__) 20 | 21 | ELVEH_TARGET_CHARGE_TAG_TO_INDEX = { 22 | Tag.ELVEH_TARGET_CHARGE: 0, 23 | Tag.ELVEH_TARGET_CHARGE_ALT1: 1, 24 | Tag.ELVEH_TARGET_CHARGE_ALT2: 2 25 | } 26 | 27 | async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback): 28 | coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR_KEY] 29 | _LOGGER.debug(f"{coordinator.vli}SELECT async_setup_entry") 30 | 31 | entities = [] 32 | check_data_availability = coordinator.data is not None and len(coordinator.data.get(ROOT_METRICS, {})) > 0 33 | for a_entity_description in SELECTS: 34 | a_entity_description: ExtSelectEntityDescription 35 | 36 | if coordinator.tag_not_supported_by_vehicle(a_entity_description.tag): 37 | _LOGGER.debug(f"{coordinator.vli}SELECT '{a_entity_description.tag}' not supported for this engine-type/vehicle") 38 | continue 39 | 40 | # me must check the supported remote climate control options seat options/mode 41 | if (coordinator._supports_HEATED_HEATED_SEAT_MODE == RCC_SEAT_MODE_HEAT_ONLY and 42 | a_entity_description.tag in [Tag.RCC_SEAT_FRONT_LEFT, Tag.RCC_SEAT_FRONT_RIGHT, Tag.RCC_SEAT_REAR_LEFT, Tag.RCC_SEAT_REAR_RIGHT]): 43 | 44 | # heating-only mode - so we set the corresponding icon and the heating-only options... 45 | a_entity_description = replace( 46 | a_entity_description, 47 | icon="mdi:car-seat-heater", 48 | options=RCC_SEAT_OPTIONS_HEAT_ONLY 49 | ) 50 | 51 | # special handling for the ELVEH_TARGET_CHARGE tags [where we have to add the location name] 52 | if a_entity_description.tag in ELVEH_TARGET_CHARGE_TAG_TO_INDEX.keys(): 53 | if FordpassDataHandler.is_elev_target_charge_supported(coordinator.data, ELVEH_TARGET_CHARGE_TAG_TO_INDEX[a_entity_description.tag]): 54 | a_location_name = FordpassDataHandler.get_elev_target_charge_name(coordinator.data, ELVEH_TARGET_CHARGE_TAG_TO_INDEX[a_entity_description.tag]) 55 | if a_location_name is not UNSUPPORTED: 56 | a_entity_description = replace( 57 | a_entity_description, 58 | name_addon=f"{a_location_name}:" 59 | ) 60 | else: 61 | _LOGGER.debug(f"{coordinator.vli}SELECT '{a_entity_description.tag}' not supported/no valid data present") 62 | continue 63 | 64 | entity = FordPassSelect(coordinator, a_entity_description) 65 | if a_entity_description.skip_existence_check or not check_data_availability: 66 | entities.append(entity) 67 | else: 68 | # calling the state reading function to check if the entity should be added (if there is any data) 69 | value = a_entity_description.tag.state_fn(coordinator.data) 70 | if value is not None and ((isinstance(value, (str, Number)) and str(value) != UNSUPPORTED) or 71 | (isinstance(value, (dict, list)) and len(value) != 0) ): 72 | entities.append(entity) 73 | else: 74 | _LOGGER.debug(f"{coordinator.vli}SELECT '{a_entity_description.tag}' skipping cause no data available: type: {type(value).__name__} - value:'{value}'") 75 | 76 | async_add_entities(entities, True) 77 | 78 | 79 | class FordPassSelect(FordPassEntity, SelectEntity): 80 | def __init__(self, coordinator: FordPassDataUpdateCoordinator, entity_description: ExtSelectEntityDescription): 81 | super().__init__(a_tag=entity_description.tag, coordinator=coordinator, description=entity_description) 82 | 83 | 84 | async def add_to_platform_finish(self) -> None: 85 | if self._tag == Tag.RCC_TEMPERATURE: 86 | has_pf_data = hasattr(self.platform, "platform_data") 87 | has_pf_trans = hasattr(self.platform.platform_data, "platform_translations") if has_pf_data else hasattr(self.platform, "platform_translations") 88 | has_pf_default_lang_trans = hasattr(self.platform.platform_data, "default_language_platform_translations") if has_pf_data else hasattr(self.platform, "default_language_platform_translations") 89 | 90 | for a_key in RCC_TEMPERATURES_CELSIUS: 91 | a_trans_key = f"component.{DOMAIN}.entity.select.{Tag.RCC_TEMPERATURE.key.lower()}.state.{a_key.lower()}" 92 | 93 | if a_key.lower() == "hi": 94 | a_value = "☀|MAX" 95 | elif a_key.lower() == "lo": 96 | a_value = "❄|MIN" 97 | else: 98 | a_temperature = float(a_key.replace('_', '.')) 99 | if self.coordinator.units.temperature_unit == UnitOfTemperature.FAHRENHEIT: 100 | # C * 9/5 + 32 = F 101 | a_temperature = a_temperature * 1.8 + 32 102 | a_value = f"{a_temperature:.1f} °F" 103 | else: 104 | a_value = f"{a_temperature:.1f} °C" 105 | 106 | if has_pf_data: 107 | if has_pf_trans: 108 | self.platform.platform_data.platform_translations[a_trans_key] = a_value 109 | if has_pf_default_lang_trans: 110 | self.platform.platform_data.default_language_platform_translations[a_trans_key] = a_value 111 | else: 112 | # old HA compatible version... 113 | if has_pf_trans: 114 | self.platform.platform_translations[a_trans_key] = a_value 115 | if has_pf_default_lang_trans: 116 | self.platform.default_language_platform_translations[a_trans_key] = a_value 117 | 118 | await super().add_to_platform_finish() 119 | 120 | @property 121 | def extra_state_attributes(self): 122 | return self._tag.get_attributes(self.coordinator.data, self.coordinator.units) 123 | 124 | @property 125 | def current_option(self) -> str | None: 126 | try: 127 | value = self._tag.get_state(self.coordinator.data) 128 | if value is None or value == "" or str(value).lower() == "null" or str(value).lower() == "none": 129 | return None 130 | 131 | if isinstance(value, (int, float)): 132 | value = str(value) 133 | 134 | if self._tag == Tag.RCC_TEMPERATURE: 135 | # our option keys are all lower case... 136 | value = str(value).lower() 137 | # our option keys have _ instead of . 138 | value = value.replace('.', '_') 139 | # the full numbers of our option keys are just the plain number (no '_0' suffix) 140 | value = value.replace('_0', '') 141 | 142 | except KeyError as kerr: 143 | _LOGGER.debug(f"SELECT KeyError: '{self._tag}' - {kerr}") 144 | value = None 145 | except TypeError as terr: 146 | _LOGGER.debug(f"SELECT TypeError: '{self._tag}' - {terr}") 147 | value = None 148 | return value 149 | 150 | async def async_select_option(self, option: str) -> None: 151 | try: 152 | if option is None or option=="" or str(option).lower() == "null" or str(option).lower() == "none": 153 | await self._tag.async_select_option(self.coordinator.data, self.coordinator.bridge, None) 154 | else: 155 | if self._tag == Tag.RCC_TEMPERATURE: 156 | option = option.upper() 157 | await self._tag.async_select_option(self.coordinator.data, self.coordinator.bridge, option) 158 | 159 | except ValueError: 160 | return None 161 | 162 | @property 163 | def available(self): 164 | """Return True if entity is available.""" 165 | if self.current_option == UNSUPPORTED: 166 | return False 167 | 168 | state = super().available 169 | if self._tag in RCC_TAGS: 170 | return state #and Tag.REMOTE_START_STATUS.get_state(self.coordinator.data) == REMOTE_START_STATE_ACTIVE 171 | return state -------------------------------------------------------------------------------- /doc/EVCC.md: -------------------------------------------------------------------------------- 1 | # Using the fordpass integration as data provider for EVCC 2 | ### Required preparation 3 | 4 | #### Create a Long-lived access token 5 | You need a HA long-lived access token and the IP/hostname of your HA instance. For information how to create such a long-lived access token, please see @marq24 ['Use evcc with your Home Assistant sensor data' documentation](https://github.com/marq24/ha-evcc/blob/main/HA_AS_EVCC_SOURCE.md). 6 | 7 | #### Required replacement in the following yaml example 8 | Below you will find a valid evcc vehicle configuration — __but you have to make two replacements__: 9 | 1. The text '__[YOUR-HA-INSTANCE]__' has to be replaced with the IP/host name of your Home Assistant installation. 10 | 11 | E.g. when your HA is reachable via: http://192.168.10.20:8123, then you need to replaced `[YOUR-HA-INSTANCE]` with `192.168.10.20` 12 | 13 | 14 | 2. The text '__[YOUR-TOKEN-HERE]__' has to be replaced with the _Long-lived access token_ you have just created in HA. 15 | 16 | E.g. when your token is: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzNWVjNzg5M2Y0ZjQ0MzBmYjUwOGEwMmU4N2Q0MzFmNyIsImlhdCI6MTcxNTUwNzYxMCwiZXhwIjoyMDMwODY3NjEwfQ.GMWO8saHpawkjNzk-uokxYeaP0GFKPQSeDoP3lCO488`, then you need to replaced `[YOUR-TOKEN-HERE]` with this (long) token text. 17 | 18 | 3. The text '__[YOUR-VIN-HERE]__' has to be replaced with your vehicle identification number (VIN). 19 | 20 | E.g., when your VIN is: `WF0TK3R7XPMA01234`, then you need to replaced `[YOUR-TOKEN-HERE]` with the **lower case!** vin text. 21 | 22 | So as short example (with all replacements) would look like: 23 | 24 | ``` 25 | ... 26 | source: http 27 | uri: http://192.168.10.20:8123/api/states/sensor.fordpass_wf0tk3r7xpma01234_elveh 28 | method: GET 29 | headers: 30 | — Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzNWVjNzg5M2Y0ZjQ0MzBmYjUwOGEwMmU4N2Q0MzFmNyIsImlhdCI6MTcxNTUwNzYxMCwiZXhwIjoyMDMwODY3NjEwfQ.GMWO8saHpawkjNzk-uokxYeaP0GFKPQSeDoP3lCO488 31 | insecure: true 32 | ... 33 | ``` 34 | 39 | 74 | 75 | 106 | 107 | ### A sample evcc.yaml vehicle section that I use for my Ford MachE 108 | 109 | The vehicle `type:template ... template: homeassistant` was introduced in evcc 0.207.1 — If you use an older evcc version — please use the alternative example below this section (`type: custom`). 110 | 111 | > [!NOTE] 112 | > This is my evcc.config vehicle section for **my** Ford MachE — In HA it's configured in the fordpass integration as `fordpass_[YOUR-VIN-HERE]` and so the sensors in this yaml are prefixed with `fordpass_wf0tk3r7xpma01234` (obviously you must relace this with your own VIN): 113 | 114 | ```yaml 115 | vehicles: 116 | - name: ford_mach_e 117 | title: MachE GT-XXXXX 118 | capacity: 84.65 119 | type: template 120 | template: homeassistant 121 | uri: http://[YOUR-HA-INSTANCE]:8123 122 | token: [YOUR-TOKEN-HERE] 123 | soc: sensor.fordpass_[YOUR-VIN-HERE]_soc # Ladezustand [%] 124 | range: sensor.fordpass_[YOUR-VIN-HERE]_elveh # Restreichweite [km] 125 | status: sensor.fordpass_[YOUR-VIN-HERE]_evccstatus # Ladestatus 126 | limitSoc: select.fordpass_[YOUR-VIN-HERE]_elvehtargetcharge # Ziel-Ladezustand [%] 127 | odometer: sensor.fordpass_[YOUR-VIN-HERE]_odometer # Kilometerstand [km] 128 | climater: sensor.fordpass_[YOUR-VIN-HERE]_remotestartstatus # Klimatisierung aktiv 129 | ``` 130 | 131 | #### So the example (with all replacements) would look like: 132 | ```yaml 133 | vehicles: 134 | - name: ford_mach_e 135 | title: MachE GT-XXXXX 136 | capacity: 84.65 137 | type: template 138 | template: homeassistant 139 | uri: http://192.168.10.20:8123 140 | token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzNWVjNzg5M2Y0ZjQ0MzBmYjUwOGEwMmU4N2Q0MzFmNyIsImlhdCI6MTcxNTUwNzYxMCwiZXhwIjoyMDMwODY3NjEwfQ.GMWO8saHpawkjNzk-uokxYeaP0GFKPQSeDoP3lCO488 141 | soc: sensor.fordpass_wf0tk3r7xpma01234_soc # Ladezustand [%] 142 | range: sensor.fordpass_wf0tk3r7xpma01234_elveh # Restreichweite [km] 143 | status: sensor.fordpass_wf0tk3r7xpma01234_evccstatus # Ladestatus 144 | limitSoc: select.fordpass_wf0tk3r7xpma01234_elvehtargetcharge # Ziel-Ladezustand [%] 145 | odometer: sensor.fordpass_wf0tk3r7xpma01234_odometer # Kilometerstand [km] 146 | climater: sensor.fordpass_wf0tk3r7xpma01234_remotestartstatus # Klimatisierung aktiv 147 | ``` 148 | 149 | 150 | ### Alternative sample evcc.yaml vehicle section — before evcc 0.207.1 151 | 152 | The vehicle `type:template ... template: homeassistant` introduced in evcc 0.207.1 — If you use an older evcc version — please use this example (`type: custom`). 153 | 154 | > [!NOTE] 155 | > This is my evcc.config vehicle section for **my** Ford MachE — In HA it's configured in the fordpass integration as `fordpass_[YOUR-VIN-HERE]` and so all URL's for the sensors in this yaml are prefixed with `fordpass_wf0tk3r7xpma01234` (obviously you must relace this with your own VIN): 156 | 157 | ```yaml 158 | vehicles: 159 | - name: ford_mach_e 160 | title: MachE GT-XXXXX 161 | capacity: 84.65 162 | type: custom 163 | soc: 164 | source: http 165 | uri: http://[YOUR-HA-INSTANCE]:8123/api/states/sensor.fordpass_[YOUR-VIN-HERE]_soc 166 | method: GET 167 | headers: 168 | - Authorization: Bearer [YOUR-TOKEN-HERE] 169 | - Content-Type: application/json 170 | insecure: true 171 | jq: .state | tonumber 172 | timeout: 2s # timeout in golang duration format, see https://golang.org/pkg/time/#ParseDuration 173 | 174 | range: 175 | source: http 176 | uri: http://[YOUR-HA-INSTANCE]:8123/api/states/sensor.fordpass_[YOUR-VIN-HERE]_elveh 177 | method: GET 178 | headers: 179 | - Authorization: Bearer [YOUR-TOKEN-HERE] 180 | - Content-Type: application/json 181 | insecure: true 182 | jq: .state | tonumber 183 | timeout: 2s # timeout in golang duration format, see https://golang.org/pkg/time/#ParseDuration 184 | 185 | status: 186 | source: http 187 | uri: http://[YOUR-HA-INSTANCE]/api/states/sensor.fordpass_[YOUR-VIN-HERE]_evccstatus 188 | method: GET 189 | headers: 190 | - Authorization: Bearer [YOUR-TOKEN-HERE] 191 | - Content-Type: application/json 192 | insecure: true 193 | jq: .state[0:1] 194 | timeout: 2s # timeout in golang duration format, see https://golang.org/pkg/time/#ParseDuration 195 | ``` 196 | 197 | ### Troubleshooting — Testing your settings via command line 198 | 199 | - assume your [YOUR-HA-INSTANCE] instance is reachable via:
`http://192.168.10.20:8123` 200 | - assume your [VIN] is:
`WF0TK3R7XPMA01234` 201 | - assume your [AUTH-TOKEN] (Bearer token) is:
`eyJpc3MiOiIzNWVjNzg5M2Y0ZjQ0MzBmYjUwOGEwMmU4N2Q0MzFmNyIsImlhdCI6MTcxNTUwNzYxMCwiZXhwIjoyMDMwODY3NjEwfQ.GMWO8saHpawkjNzk-uokxYeaP0GFKPQSeDoP3lCO488` 202 | 203 | #### Windows (PowerShell) [template] 204 | ```PowerShell 205 | $uri = "[YOUR-HA-INSTANCE]/api/states/sensor.fordpass_[VIN]_soc" 206 | $headers = @{"Authorization" = "Bearer [AUTH-TOKEN]"} 207 | $response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers 208 | $response 209 | ``` 210 | #### Windows (PowerShell) [example] 211 | ```PowerShell 212 | $uri = "http://192.168.10.20:8123/api/states/sensor.fordpass_WF0TK3R7XPMA01234_soc" 213 | $headers = @{"Authorization" = "Bearer eyJpc3MiOiIzNWVjNzg5M2Y0ZjQ0MzBmYjUwOGEwMmU4N2Q0MzFmNyIsImlhdCI6MTcxNTUwNzYxMCwiZXhwIjoyMDMwODY3NjEwfQ.GMWO8saHpawkjNzk-uokxYeaP0GFKPQSeDoP3lCO488"} 214 | $response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers 215 | $response 216 | ``` 217 | 218 | #### Linux (using CURL) [template] 219 | ```bash 220 | curl -X GET "[YOUR-HA-INSTANCE]/api/states/sensor.fordpass_[VIN]_soc" -H "Authorization: Bearer [AUTH-TOKEN]" 221 | ``` 222 | #### Linux (using CURL) [example] 223 | ```bash 224 | curl -X GET "http://192.168.10.20:8123/api/states/sensor.fordpass_WF0TK3R7XPMA01234_soc" -H "Authorization: Bearer eyJpc3MiOiIzNWVjNzg5M2Y0ZjQ0MzBmYjUwOGEwMmU4N2Q0MzFmNyIsImlhdCI6MTcxNTUwNzYxMCwiZXhwIjoyMDMwODY3NjEwfQ.GMWO8saHpawkjNzk-uokxYeaP0GFKPQSeDoP3lCO488" 225 | ``` 226 | 227 | # Additional Resources: 228 | - [Create __Long-lived access Token__ in HA](https://github.com/marq24/ha-evcc/blob/main/HA_AS_EVCC_SOURCE.md#preparation-1st-make-home-assistant-sensor-data-accessible-via-api-calls) 229 | - [Provide HA PV/Grid Data to evcc](https://github.com/marq24/ha-evcc/blob/main/HA_AS_EVCC_SOURCE.md) 230 | - [Provide HA vehicle data to evcc](https://github.com/marq24/ha-fordpass/blob/main/doc/EVCC.md) 231 | - [Let evcc control your HA entities (PV surplus handling)](https://github.com/marq24/ha-evcc/blob/main/HA_CONTROLLED_BY_EVCC.md) 232 | -------------------------------------------------------------------------------- /custom_components/fordpass/const.py: -------------------------------------------------------------------------------- 1 | """Constants for the FordPass integration.""" 2 | import logging 3 | from enum import Enum 4 | from typing import Final 5 | 6 | _LOGGER = logging.getLogger(__name__) 7 | 8 | DOMAIN: Final = "fordpass" 9 | NAME: Final = "Fordpass integration for Home Assistant [optimized for EV's & EVCC]" 10 | ISSUE_URL: Final = "https://github.com/marq24/ha-fordpass/issues" 11 | MANUFACTURER_FORD: Final = "Ford Motor Company" 12 | MANUFACTURER_LINCOLN: Final = "Lincoln Motor Company" 13 | 14 | STARTUP_MESSAGE: Final = f""" 15 | ------------------------------------------------------------------- 16 | {NAME} - v%s 17 | This is a custom integration! 18 | If you have any issues with this you need to open an issue here: 19 | {ISSUE_URL} 20 | ------------------------------------------------------------------- 21 | """ 22 | 23 | CONFIG_VERSION: Final = 2 24 | CONFIG_MINOR_VERSION: Final = 0 25 | 26 | CONF_IS_SUPPORTED: Final = "is_supported" 27 | CONF_BRAND: Final = "brand" 28 | CONF_VIN: Final = "vin" 29 | 30 | CONF_PRESSURE_UNIT: Final = "pressure_unit" 31 | CONF_LOG_TO_FILESYSTEM: Final = "log_to_filesystem" 32 | CONF_FORCE_REMOTE_CLIMATE_CONTROL: Final = "force_remote_climate_control" 33 | COORDINATOR_KEY: Final = "coordinator" 34 | 35 | UPDATE_INTERVAL: Final = "update_interval" 36 | UPDATE_INTERVAL_DEFAULT: Final = 290 # it looks like that the default auto-access_token expires after 5 minutes (300 seconds) 37 | 38 | DEFAULT_PRESSURE_UNIT: Final = "kPa" 39 | PRESSURE_UNITS: Final = ["PSI", "kPa", "BAR"] 40 | 41 | # https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 42 | BRAND_OPTIONS = ["ford", "lincoln"] 43 | 44 | DEFAULT_REGION_FORD: Final = "rest_of_world" 45 | REGION_OPTIONS_FORD: Final = ["fra", "deu", "ita", "nld", "esp", "gbr", "rest_of_europe", "aus", "nzl", "zaf", "bra", "arg", "can", "mex", "usa", "rest_of_world"] 46 | 47 | REGION_OPTIONS_LINCOLN: Final = ["lincoln_usa"] 48 | DEFAULT_REGION_LINCOLN: Final = "lincoln_usa" 49 | 50 | LEGACY_REGION_KEYS: Final = ["USA", "Canada", "Australia", "UK&Europe", "Netherlands"] 51 | 52 | REGION_APP_IDS: Final = { 53 | "africa": "71AA9ED7-B26B-4C15-835E-9F35CC238561", # South Africa, ... 54 | "asia_pacific": "39CD6590-B1B9-42CB-BEF9-0DC1FDB96260", # Australia, Thailand, New Zealand, ... 55 | "europe": "667D773E-1BDC-4139-8AD0-2B16474E8DC7", # used for germany, france, italy, netherlands, uk, rest_of_europe 56 | "north_america": "BFE8C5ED-D687-4C19-A5DD-F92CDFC4503A", # used for canada, usa, mexico 57 | "south_america": "C1DFFEF5-5BA5-486A-9054-8B39A9DF9AFC", # Argentina, Brazil, ... 58 | } 59 | 60 | OAUTH_ID: Final = "4566605f-43a7-400a-946e-89cc9fdb0bd7" 61 | CLIENT_ID: Final = "09852200-05fd-41f6-8c21-d36d3497dc64" 62 | 63 | LINCOLN_REGION_APP_IDS: Final = { 64 | "north_america": "45133B88-0671-4AAF-B8D1-99E684ED4E45" 65 | } 66 | 67 | REGIONS: Final = { 68 | "lincoln_usa": { 69 | "app_id": LINCOLN_REGION_APP_IDS["north_america"], 70 | "locale": "en-US", 71 | "login_url": "https://login.lincoln.com", 72 | "sign_up_addon": "Lincoln_", 73 | "redirect_schema": "lincolnapp", 74 | "countrycode": "USA" 75 | }, 76 | 77 | # checked 2025/06/08 - working fine... 78 | "deu": { 79 | "app_id": REGION_APP_IDS["europe"], 80 | "locale": "de-DE", 81 | "login_url": "https://login.ford.de", 82 | "countrycode": "DEU" 83 | }, 84 | # checked 2025/06/08 - working fine... 85 | "fra": { 86 | "app_id": REGION_APP_IDS["europe"], 87 | "locale": "fr-FR", 88 | "login_url": "https://login.ford.com", 89 | "countrycode": "FRA" 90 | }, 91 | # checked 2025/06/08 - working fine... 92 | "ita": { 93 | "app_id": REGION_APP_IDS["europe"], 94 | "locale": "it-IT", 95 | "login_url": "https://login.ford.com", 96 | "countrycode": "ITA" 97 | }, 98 | # checked 2025/06/09 - working fine... 99 | "esp": { 100 | "app_id": REGION_APP_IDS["europe"], 101 | "locale": "es-ES", 102 | "login_url": "https://login.ford.com", 103 | "countrycode": "ESP" 104 | }, 105 | # checked 2025/06/08 - working fine... 106 | "nld": { 107 | "app_id": REGION_APP_IDS["europe"], # 1E8C7794-FF5F-49BC-9596-A1E0C86C5B19 108 | "locale": "nl-NL", 109 | "login_url": "https://login.ford.com", 110 | "countrycode": "NLD" 111 | }, 112 | # checked 2025/06/08 - working fine... 113 | "gbr": { 114 | "app_id": REGION_APP_IDS["europe"], # 1E8C7794-FF5F-49BC-9596-A1E0C86C5B19", 115 | "locale": "en-GB", 116 | "login_url": "https://login.ford.co.uk", 117 | "countrycode": "GBR" 118 | }, 119 | # using GBR as our default for the rest of europe... 120 | "rest_of_europe": { 121 | "app_id": REGION_APP_IDS["europe"], 122 | "locale": "en-GB", 123 | "login_url": "https://login.ford.com", 124 | "countrycode": "GBR" 125 | }, 126 | # checked 2025/06/08 - working fine... 127 | "can": { 128 | "app_id": REGION_APP_IDS["north_america"], 129 | "locale": "en-CA", 130 | "login_url": "https://login.ford.com", 131 | "countrycode": "CAN" 132 | }, 133 | # checked 2025/06/08 - working fine... 134 | "mex": { 135 | "app_id": REGION_APP_IDS["north_america"], 136 | "locale": "es-MX", 137 | "login_url": "https://login.ford.com", 138 | "countrycode": "MEX" 139 | }, 140 | # checked 2025/06/08 - working fine... 141 | "usa": { 142 | "app_id": REGION_APP_IDS["north_america"], 143 | "locale": "en-US", 144 | "login_url": "https://login.ford.com", 145 | "countrycode": "USA" 146 | }, 147 | 148 | # DOES NOT WORK... checked 2025/06/09 149 | "bra": { 150 | "app_id": REGION_APP_IDS["south_america"], 151 | "locale": "pt-BR", 152 | "login_url": "https://login.ford.com", 153 | "countrycode": "BRA" 154 | }, 155 | # DOES NOT WORK... checked 2025/06/09 156 | "arg": { 157 | "app_id": REGION_APP_IDS["south_america"], 158 | "locale": "es-AR", 159 | "login_url": "https://login.ford.com", 160 | "countrycode": "ARG" 161 | }, 162 | 163 | # NEED AN www.ford.com.au registered account!!! 164 | "aus": { 165 | "app_id": REGION_APP_IDS["asia_pacific"], 166 | "locale": "en-AU", 167 | "login_url": "https://login.ford.com", 168 | "countrycode": "AUS" 169 | }, 170 | # NEED AN www.ford.com.au registered account!!! 171 | "nzl": { 172 | "app_id": REGION_APP_IDS["asia_pacific"], 173 | "locale": "en-NZ", 174 | "login_url": "https://login.ford.com", 175 | "countrycode": "NZL" 176 | }, 177 | 178 | # NEED AN www.ford.co.za registered account!!! 179 | "zaf": { 180 | "app_id": REGION_APP_IDS["africa"], 181 | "locale": "en-ZA", 182 | "login_url": "https://login.ford.com", 183 | "countrycode": "ZAF" 184 | }, 185 | 186 | # we use the 'usa' as the default region..., 187 | "rest_of_world": { 188 | "app_id": REGION_APP_IDS["north_america"], 189 | "locale": "en-US", 190 | "login_url": "https://login.ford.com", 191 | "countrycode": "USA" 192 | }, 193 | 194 | # for compatibility, we MUST KEEP the old region keys with the OLD App-IDs!!! - this really sucks! 195 | "Netherlands": {"app_id": "1E8C7794-FF5F-49BC-9596-A1E0C86C5B19", "locale": "nl-NL", "login_url": "https://login.ford.nl", "countrycode": "NLD"}, 196 | "UK&Europe": {"app_id": "1E8C7794-FF5F-49BC-9596-A1E0C86C5B19", "locale": "en-GB", "login_url": "https://login.ford.co.uk", "countrycode": "GBR"}, 197 | "Australia": {"app_id": "5C80A6BB-CF0D-4A30-BDBF-FC804B5C1A98", "locale": "en-AU", "login_url": "https://login.ford.com", "countrycode": "AUS"}, 198 | "USA": {"app_id": "71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592", "locale": "en-US", "login_url": "https://login.ford.com", "countrycode": "USA"}, 199 | "Canada": {"app_id": "71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592", "locale": "en-CA", "login_url": "https://login.ford.com", "countrycode": "USA"} 200 | } 201 | 202 | REGIONS_STRICT = REGIONS.copy() 203 | for a_key in LEGACY_REGION_KEYS: 204 | REGIONS_STRICT.pop(a_key) 205 | 206 | WINDOW_POSITIONS: Final = { 207 | "CLOSED": { 208 | "Fully_Closed": "Closed", 209 | "Fully_closed_position": "Closed", 210 | "Fully closed position": "Closed", 211 | }, 212 | "OPEN": { 213 | "Fully open position": "Open", 214 | "Fully_Open": "Open", 215 | "Btwn 10% and 60% open": "Open-Partial", 216 | }, 217 | } 218 | 219 | REMOTE_START_STATE_ACTIVE: Final = "Active" 220 | REMOTE_START_STATE_INACTIVE: Final = "Inactive" 221 | 222 | RCC_SEAT_MODE_HEAT_AND_COOL: Final = "HEAT_AND_COOL" 223 | RCC_SEAT_MODE_HEAT_ONLY: Final = "HEAT_ONLY" 224 | RCC_SEAT_MODE_NONE: Final = "NONE" 225 | RCC_SEAT_OPTIONS_FULL: Final = ["off", "heated1", "heated2", "heated3", "cooled1", "cooled2", "cooled3"] 226 | RCC_SEAT_OPTIONS_HEAT_ONLY: Final = ["off", "heated1", "heated2", "heated3"] 227 | 228 | RCC_TEMPERATURES_CELSIUS: Final = ["lo", 229 | "16", "16_5", "17", "17_5", "18", "18_5", "19", "19_5", "20", "20_5", 230 | "21", "21_5", "22", "22_5", "23", "23_5", "24", "24_5", "25", "25_5", 231 | "26", "26_5", "27", "27_5", "28", "28_5", "29", "30", 232 | "hi"] 233 | 234 | ELVEH_TARGET_CHARGE_OPTIONS: Final = ["50", "60", "70", "80", "85", "90", "95", "100"] 235 | 236 | VEHICLE_LOCK_STATE_LOCKED: Final = "LOCKED" 237 | VEHICLE_LOCK_STATE_PARTLY: Final = "PARTLY_LOCKED" 238 | VEHICLE_LOCK_STATE_UNLOCKED: Final = "UNLOCKED" 239 | 240 | ZONE_LIGHTS_VALUE_ALL_ON: Final = "0" 241 | ZONE_LIGHTS_VALUE_FRONT: Final = "1" 242 | ZONE_LIGHTS_VALUE_REAR: Final = "2" 243 | ZONE_LIGHTS_VALUE_DRIVER: Final = "3" 244 | ZONE_LIGHTS_VALUE_PASSENGER: Final = "4" 245 | ZONE_LIGHTS_VALUE_OFF: Final = "off" 246 | ZONE_LIGHTS_OPTIONS: Final = [ZONE_LIGHTS_VALUE_ALL_ON, ZONE_LIGHTS_VALUE_FRONT, ZONE_LIGHTS_VALUE_REAR, 247 | ZONE_LIGHTS_VALUE_DRIVER, ZONE_LIGHTS_VALUE_PASSENGER, ZONE_LIGHTS_VALUE_OFF] 248 | 249 | class HONK_AND_FLASH(Enum): 250 | SHORT = 1 251 | DEFAULT = 3 252 | LONG = 5 253 | 254 | XEVPLUGCHARGER_STATE_CONNECTED: Final = "CONNECTED" 255 | XEVPLUGCHARGER_STATE_DISCONNECTED: Final = "DISCONNECTED" 256 | XEVPLUGCHARGER_STATE_CHARGING: Final = "CHARGING" # this is from evcc code - I have not seen this in my data yet 257 | XEVPLUGCHARGER_STATE_CHARGINGAC: Final = "CHARGINGAC" # this is from evcc code - I have not seen this in my data yet 258 | XEVPLUGCHARGER_STATES: Final = [XEVPLUGCHARGER_STATE_CONNECTED, XEVPLUGCHARGER_STATE_DISCONNECTED, 259 | XEVPLUGCHARGER_STATE_CHARGING, XEVPLUGCHARGER_STATE_CHARGINGAC] 260 | 261 | XEVBATTERYCHARGEDISPLAY_STATE_NOT_READY: Final = "NOT_READY" 262 | XEVBATTERYCHARGEDISPLAY_STATE_SCHEDULED: Final = "SCHEDULED" 263 | XEVBATTERYCHARGEDISPLAY_STATE_PAUSED: Final = "PAUSED" 264 | XEVBATTERYCHARGEDISPLAY_STATE_IN_PROGRESS: Final = "IN_PROGRESS" 265 | XEVBATTERYCHARGEDISPLAY_STATE_STOPPED: Final = "STOPPED" 266 | XEVBATTERYCHARGEDISPLAY_STATE_FAULT: Final = "FAULT" 267 | XEVBATTERYCHARGEDISPLAY_STATION_NOT_DETECTED: Final = "STATION_NOT_DETECTED" 268 | 269 | XEVBATTERYCHARGEDISPLAY_STATES: Final = [XEVBATTERYCHARGEDISPLAY_STATE_NOT_READY, XEVBATTERYCHARGEDISPLAY_STATE_SCHEDULED, 270 | XEVBATTERYCHARGEDISPLAY_STATE_PAUSED, XEVBATTERYCHARGEDISPLAY_STATE_IN_PROGRESS, 271 | XEVBATTERYCHARGEDISPLAY_STATE_STOPPED, XEVBATTERYCHARGEDISPLAY_STATE_FAULT, 272 | XEVBATTERYCHARGEDISPLAY_STATION_NOT_DETECTED] 273 | 274 | DAYS_MAP = { 275 | "MONDAY": 0, 276 | "TUESDAY": 1, 277 | "WEDNESDAY":2, 278 | "THURSDAY": 3, 279 | "FRIDAY": 4, 280 | "SATURDAY": 5, 281 | "SUNDAY": 6, 282 | } 283 | 284 | TRANSLATIONS: Final = { 285 | "de":{ 286 | "account": "Konto", 287 | "deu": "Deutschland", 288 | "fra": "Frankreich", 289 | "nld": "Niederlande", 290 | "ita": "Italien", 291 | "esp": "Spanien", 292 | "gbr": "Vereinigtes Königreich Großbritannien und Irland", 293 | "aus": "Australien", 294 | "nzl": "Neuseeland", 295 | "zaf": "Südafrika", 296 | "can": "Kanada", 297 | "mex": "Mexiko", 298 | "usa": "Die Vereinigten Staaten von Amerika", 299 | "bra": "Brasilien", 300 | "arg": "Argentinien", 301 | "rest_of_europe": "Andere europäische Länder", 302 | "rest_of_world": "Rest der Welt", 303 | "lincoln_usa": "Vereinigten Staaten von Amerika", 304 | "USA": "USA (LEGACY)", "Canada":"Kanada (LEGACY)", "Australia":"Australien (LEGACY)", "UK&Europe":"UK&Europa (LEGACY)", "Netherlands":"Niederlande (LEGACY)" 305 | }, 306 | "en": { 307 | "account": "Account", 308 | "deu": "Germany", 309 | "fra": "France", 310 | "nld": "Netherlands", 311 | "ita": "Italy", 312 | "esp": "Spain", 313 | "gbr": "United Kingdom of Great Britain and Northern Ireland", 314 | "aus": "Australia", 315 | "nzl": "New Zealand", 316 | "zaf": "South Africa", 317 | "can": "Canada", 318 | "mex": "Mexico", 319 | "usa": "The United States of America", 320 | "bra": "Brazil", 321 | "arg": "Argentina", 322 | "rest_of_europe": "Other European Countries", 323 | "rest_of_world": "Rest of the World", 324 | "lincoln_usa": "United States of America", 325 | "USA": "USA (LEGACY)", "Canada":"Canada (LEGACY)", "Australia":"Australia (LEGACY)", "UK&Europe":"UK&Europe (LEGACY)", "Netherlands":"Netherlands (LEGACY)" 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | # **Changelog** 2 | 3 | ## Version 2025.5.9 - HA-Switch/DeviceTracker/Lock implementation refactored 4 | Same procedure as yesterday (2025.5.8) — this time for DeviceTracker, Switch and Lock entities. 5 | 6 | While testing the 'Ignition' switch, I realized that the 'Ignition' switch changing the ignition state. Instead, the 'remote start state' is changes — that make plenty of sense when you look a bit deeper into the code and realize that the switch will send a `RemoteStart` command to the vehicle. So I have renamed the switch to `Remote Start` and changed the icon (but for compatibility reasons I have kept the original entity name). 7 | 8 | 9 | ## Version 2025.5.8 - HA-Sensor implementation refactored 10 | The integration now uses the standard Home Assistant SensorEntityDescription objects to define the sensors. This allows for a more consistent and maintainable codebase, as well as better compatibility with Home Assistant's features of the future. 11 | 12 | While refactoring, I also took the opportunity to clean up the code and (IMHO) improve the overall structure of the integration. The refactoring should not change any existing functionality, but it will make it easier to add new features in the future. 13 | 14 | > [!NOTE] 15 | > The refactoring is a major change, so please report any issues you encounter. I have tested the integration with my own vehicles, but there might be some edge cases that I have not considered. 16 | > 17 | > Please don't get upset if you find a bug, I will fix it as soon as possible — for me, it's a long weekend! 18 | 19 | ### New Features: 20 | While I was working on the refactoring, I have found some additional data in the response of the backend, which was not present in the previous versions of the integration. So I have added a new Sensor and some new attributes (to existing sensors): 21 | 22 | #### New Sensor: 23 | - Seatbelt buckle/unbuckle status (when available) 24 | 25 | #### New Attributes: 26 | 27 | - Speed Sensor: acceleration, yawRate, wheelTorqueStatus 28 | - GPS Sensor: Compass direction & heading 29 | - TirePressure Sensor: tirePressureStatus 30 | - Battery Sensor: batteryStatus 31 | - Alarm Sensor: panicAlarmStatus 32 | - ELVeh Sensor: batteryEnergyRemaining 33 | 34 | ### BreakingChange — List of incompatible changes: 35 | No major change without incompatible changes — sorry for that! 36 | 37 | - Battery Sensor: Attribute 'BatteryVoltage' has been renamed to 'batteryVoltage' (starting now with lowercase) 38 | - Indicator Sensor: The attribute keys can end with the 'additionalInfo' property [appended with an '_' (underscore)]. This 'additionalInfo' looks to me like a service key or something similar that might can become handy when you want to visit a workshop. 39 | - Message Sensor: Attributes will be 'numbered' list of all messages — including the fields: 'Date', 'Type' 'Subject' and 'Content'. The keys will start with 'msgNNN_' (where NNN is the message number starting with 001) 40 | 41 | ### I need you! 42 | It would be cool if you could take some additional minutes and read: https://github.com/marq24/ha-fordpass?tab=readme-ov-file#i-need-you 43 | 44 | ## Version 2025.5.7 45 | - PEV support (Fixed): The integration supports PEV (Plug-in Electric Vehicle). It looks like that in previous releases, there was an issue when adding sensors for this vehicle type. 46 | 47 |
This means that the integration will now handle Electric Vehicle [EV] (with SOC info), PEV's (with SOC & fuel info) and vehicles with a combustion engine (fuel info), providing the appropriate sensors and attributes for each engine type. 48 | 49 | ## Version 2025.5.6 50 | - Bugfix for #22 51 | 52 | ## Version 2025.5.5 53 | - General backend-communication enhancements: The integration now should handle the situation, when the HA instance might not have a stable internet connection (and just retry to update the data with the next refresh cycle) 54 | 55 | ## Version 2025.5.4 56 | - Added additional EVData & EVCharging attributes (might not be present for all vehicles) 57 | 58 |
The in the backend response JSON some additional (custom) data fields have been found and so they will be added to the attribute list of the EVData & EVCharging Sensor entities. 59 | 60 | ## Version 2025.5.3 61 | - BreakingChange - Window Positions only return "value" (when present) 62 | 63 |
While debugging my window position information, I realized that in contract to the `doorStatus` the `windowStatus` return `UNSPECIFIED_FRONT` and `UNSPECIFIED_REAR`... 64 | 65 |
This must be handled — and while I am already at this section of the code, I decided to include only the value object per window - since the attribute name (Passenger, Driver, ReadPassenger & ReadDriver) should be enough to locate the window. 66 | 67 | ## Version 2025.5.2 68 | - Support for none existing `hoodStatus` - fixing #21 69 | 70 | ## Version 2025.5.1 71 | - For EV-Vehicles, the Integration now uses the SOC (State of Charge) as global battery level (and not the level of the 12 V battery) 72 | - clean up some configuration code 73 | - fixing log Warnings as reported in #17 and #18 74 | 75 | ## Version 2025.5.0 76 | - Speed is measured in m/s (not in km/h) - so the `native_unit_of_measurement` is now m/s 77 | 78 | ## Version 2025.4.0 79 | - Enhanced startup behavior 80 | - Sometimes (at least at HA restart) Ford API might not return initial data - this situation is now handled and logged appropriate 81 | - No other features/functions have been added 82 | 83 | ## Version 2025.3.1 84 | - Added separate Sensor for EV Plug state 85 | - 'EV Charging' returns now the real 'charging state' of the vehicle - previously the plug state [DISCONNECTED/CONNECTED] was returned 86 | - EVCC-Status can now also return status 'C' (since we are now really looking at the charging state of the vehicle) 87 | 88 | ## Version 2025.3.0 89 | - Another bugfix for `DoorStatus` (caused by bad refactoring) - Issue #8 90 | - changed version scheme - so that it will match all my (marq24) other integrations 91 | 92 | ## Version 1.77 93 | - bugfix for refactor issue (missing `states` object) (caused by bad refactoring) - Issue #7 94 | 95 | ## Version 1.76 96 | - added separate SOC Sensor for EV/PHEV vehicles 97 | - added sensor that will provide EVCC charging-state information: A,B,C [makes template sensor obsolete] 98 | - determine vehicle engine type (at startup) and add only corresponding sensors (e.g. no Fuel for EV's) 99 | - internal refactoring [introduced "tag's"] 100 | 101 | ## Version 1.75 102 | - First bugfix was required #5 103 | - Added `Local Sync` button: Request the integration to refresh it's data against Ford APIs (when you don't want to wait for the next update interval) 104 | - Added `Remote Sync` button: Request an update from the vehicle - this will train for sure the battery of your vehicle - since the internal components must be awakened - once the remote sync request have been successfully confirmed by the Ford backend, the integration will update its data after a pause of 15 seconds (same as manually press the `Local Sync`). 105 | 106 | ## Version 1.74 107 | It could happen (e.g. when the integration is not running for a while, that the TOKEN REFRESH will not work (cause of an expired refresh token) - in this case the Integration will now allow you to re-enter the initial token-request (that requires the login via the web browser (where you must capture a code via the developers tools) 108 | 109 | Additionally, the documentation have been updated (https://github.com/marq24/ha-fordpass/blob/main/doc/OBTAINING_TOKEN.md) 110 | 111 | ## Version 1.73 112 | Only internal stuff have been updated - reduced the number of token-refresh requests - and enhanced further the debug logging output more refactoring some of the internal API stuff (just to learn how the internals are working). 113 | 114 | ## Version 1.72 115 | Only internal stuff have been updated - mainly the debug logging output have been cleaned up and I have refactored some of the internal API stuff (just to learn how the internals are working). 116 | 117 | ## Version 1.71 118 | Initial fork version () 119 | 120 | ## Version 1.70 121 | - New config flow to allow for a user to generate a token in their browser then enter into the application, bypasses WAF. 122 | ## Version 1.69 123 | - Versioning issue 1.69 is 1.70 124 | ## Version 1.68 125 | - Fix for missing locale 126 | - Fix duplicate Switches dictionary 127 | - Fix DC fast charging bug 128 | ## Version 1.67 129 | - Temp fix for login issues (Uses different region login servers, may get blocked soon!) 130 | ## Version 1.66 131 | - Remove deprecated GPS source type 132 | - Added data_description translations 133 | ## Version 1.65 134 | - Add ability to use Lincoln vehicles again 135 | ## Version 1.64 136 | - Add helper text for initial login when using a mobile number 137 | - Added sensors containing all returned data from API (Disabled by default in HA) can be used for templates and other automations/research 138 | ## Version 1.63 139 | - Reworked authentication to use login.ford.com 140 | ## Version 1.62 141 | - Skipped due to emergency release of auth changes 142 | ## Version 1.61 143 | - Deepsleep status is now reported again as a sensor 144 | - Compass Direction is now an attribute under the device_tracker entity 145 | - Handle missing countdownTimer variable 146 | - Handle missing events dictionary 147 | - Temporary fix for elveh errors 148 | - Added more Trip Data to elVeh (will assess to determine if previous Trip scores can be removed) 149 | - Trip Speed Score 150 | - Trip Deceleration Score 151 | - Trip Acceleration Score 152 | - Trip Electrical Efficiency (unsure what this value is, adding it to get more data) 153 | - Fix for fuel not displaying properly for EV's (will assess to determine if duplicate values in other sensors can be removed) 154 | - Better display for Trip Duration under elVeh 155 | - elVeh kW conversions will display 0 if voltage or amperage is 0 156 | ## Version 1.60 157 | - Versioning issue 1.59 is 1.60 158 | ## Version 1.59 159 | - Add support for manual VIN entry (Lincoln cars hopefuly) - Please test this and report any errrors back! 160 | - Fix for lastRefresh sensor not returning local time 161 | - Fix for elVehCharging Estimated End Time not returning local time 162 | - Fix for elVehCharging Battery Temp debug error 163 | - elVehCharging now displays Plug Status as default 164 | - Added device class for battery 165 | - Fix for incorrect DoorStatus @sarangcr03 166 | - Added Trip Data to elVeh. 167 | - Trip Ambient Temp 168 | - Trip Outside Air Ambient Temp 169 | - Trip Duration 170 | - Trip Cabin Temp 171 | - Trip Energy Consumed (kW) 172 | - Trip Distance Traveled 173 | - Trip Efficiency 174 | - Driving Score now Trip Driving Score 175 | - Range Regen now Trip Range Regeneration 176 | ## Version 1.58 177 | - Rewrote auth function to allow for more granular debugging 178 | - Changed odometer to use native conversions in HA (pick from sensor options) 179 | - No longer displays "unavaliable" if sensor goes offline, will instead show previous data and report an error in logs 180 | - More EV features 181 | - Add hood status to door locks 182 | - Added tripFuelEconomy attribute under speed 183 | - Added "Driving Score" and Range Regeneration attributes to elVeh (Driving score was a previous feature in the FordPass app. Its basically a "score" based on the maximum brake regen gained from a trip) 184 | - Removed duplicate attributes from elVeh / elVehCharging 185 | - Returning Estimated End Time as a timestamp for elVehCharging (previously was Time To Complete) 186 | - Improved the elVehCharging display 187 | 188 | ## Version 1.57 189 | - Rewrote command function to actively poll until success or failure is returned 190 | - Fixed bug where elveh attributes wasn't showing 191 | - Fixed bug where command wouldn't check token expiry first 192 | ## Version 1.56 193 | - Fix for error when missing GPS data from vehicle 194 | - Fix for electric vehicle error 195 | - Better formatting of door statuses 196 | - Fix for missing diesel stats 197 | - Fix for initial integration error on startup 198 | - Added temperature sensors 199 | - Added extra attributes to speed sensor e.g. pedal positions and RPM 200 | - Removed GPS sensor (All stats are in device_tracker entity) 201 | - Added check if window position if supported, if not entity is not added 202 | ## Version 1.55 203 | - Skipped due to git issue 204 | ## Version 1.54 205 | - Fixed lock/unlock status (Waits 90seconds before checking command has completed) 206 | - Added back diesel sensors 207 | - Added indicator/warning sensor (Shows any faults on the vehicle) 208 | ## Version 1.53 209 | - Updated vehicle endpoint to use new Autonomics API 210 | - Added secondary Autonomic token 211 | - Remapped commands to use new "command" API endpoint 212 | - Remapped existing sensors to new json variables (Some are missinge) 213 | - Added charge status sensor (Thanks @SquidBytes) 214 | - Added new speed sensor (Will be adding more attributes to this like pedal position and torque settings soon) 215 | 216 | *Please report any bugs as a separate issue so I can keep track easier* 217 | 218 | 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. 219 | ## Version 1.52 220 | - Update for discontinued API endpoints (Update, lock, remote start) 221 | ## Version 1.51 222 | - Fix for incorrect tire pressure conversion 223 | - Handling of blank nickName when configuring car in config flow 224 | - Fix for incorrect URL reference in vehicles API 225 | ## Version 1.50 226 | - Complete refactor of all code to make it more compliant 227 | - Added new config flow to allow for choosing vehicles on setup instead of using VIN 228 | ## Version 1.49 229 | - Added German & Italian translations (@@lollo0296) 230 | ## Version 1.48 231 | - Add translations for service strings 232 | - Fix error on odometer missing config 233 | - Handle unsupported car locks 234 | - Add Units for elVeh DTE 235 | ## Version 1.47 236 | - Add poll_api service to allow for manual refreshing of data outside of poll interval (e.g. poll more when driving) 237 | - Add option to disable distance conversion when units displaying wrong in certain countries 238 | - Add device_class to "last_refresh" sensor 239 | - Add Locking/Unlocking status to lock entity 240 | - Enabled support for debugging via the UI 241 | ## Version 1.46 242 | - Fix diesel filter error 243 | ## Version 1.45 244 | - Fix window position reporting as open always 245 | ## Version 1.44 246 | - Fix incorrect window position status 247 | - Add reload integration service 248 | ## Version 1.43 249 | - Add DPF status on supported vehicles 250 | - Incorrect vehicle refresh time (@ronytomen) 251 | - Refresh integration automatticly on options changes 252 | ## Version 1.42 253 | - Fix incorrect tire pressure units (Thanks @costr for debugging) 254 | ## Version 1.41 255 | - Fix options error in HA 2023 256 | ## Version 1.40 257 | - Fix empty value bug for lighting attributes 258 | - Fix casting bug for elvehdte and batterly level 259 | - Fix empty but not null string for tire pressure 260 | - Add BAR units to options 261 | ## Version 1.39 262 | - added statistics support to odometer sensor 263 | - Fixed device_tracker not working correctly in automations 264 | ## Version 1.38 265 | - Changed the default update interval from 5 minutes to 15 minutes (Reduce Ford API Requests) 266 | - Add ability to change this interval in integration options (WARNING! setting this too low will result in your Fordpass account being locked!!!!) 267 | ## Version 1.37 268 | - Update messages icon to new format 269 | ## Version 1.36 270 | - Remove vehicle list endpoint on setup 271 | - Add dutch translation (Thanks @Bert-R) 272 | ## Version 1.35 273 | - Remove deprecated dotted module 274 | - Add coordinator context for HA 2022.7 275 | ## Version 1.34 276 | - Change oauth flow for latest Fordpass changes 277 | ## Version 1.33 278 | - Fix occasional hacs error due to git tag issue 279 | ## Version 1.32 280 | - Fix auth flow to comply with new endpoints 281 | **Warning - If you encounter auth errors please delete the token file located in the install directory or use the "delete_token" service** 282 | ## Version 1.31 283 | - Fix for multiple accounts 284 | ## Version 1.30 285 | - Fix for elvDTE error 286 | ## Version 1.29 287 | - Disabled guard mode 288 | - Fixed elvDTE units 289 | - set Vin check on install to warning only (Lincoln cars don't show in ford database) 290 | ## Version 1.28 291 | - Added vin check on setup (Will check if given VIN is linked to the credentials) 292 | ## Version 1.27 293 | - Fix fuel level error 294 | - Add code for Vin debugging 295 | ## Version 1.25 296 | - Updated user agent 297 | - Added messages sensor to show current messages in fordpass 298 | 299 | ## Version 1.24 300 | - Change device_state_attributes to extra_state_attributes (HA 2020.12.1) 301 | - Changed session timeout to cope with timeouts in fordpass API (Helps prevent 403 error's) 302 | 303 | ## Version 1.23 304 | **Breaking Change** 305 | 306 | When installing this new version please go to "integrations" and click configure on Fordpass and choose your preferred units. Not doing this will result in an error!! 307 | 308 | - Fixed tyre pressure status when sensor missing or broke 309 | - Add DistanceToEmpty Imperial Conversion (Thanks @JacobWasFramed ) 310 | - Seperated pressure and distance measurement unit selection (Thanks @JacobWasFramed) 311 | ## Version 1.22 312 | - Fix for custom config locations on certain HA installs 313 | 314 | ## Version 1.21 315 | - Error handling for null fuel and elVehDTE attributes. Thanks @wietseschmitt 316 | 317 | ## Version 1.20 318 | - Fixed incorrect reporting of guardmode switch status 319 | 320 | ## Version 1.19 321 | - Added null guard status handling (effects some vehicles) 322 | 323 | ## Version 1.18 324 | - Fix Guard mode error (Missing data array) 325 | 326 | ## Version 1.17 327 | - Added VIN option to UI 328 | - Added Guard mode switch (Need people to test as don't have access to a guard mode enabled vehicle) 329 | - Added extra sensors (Credit @tonesto7) 330 | - Zonelighting (Supported models only) 331 | - Deep sleep status 332 | - Remote start status 333 | - Firmware update status 334 | - Added partial opening status for windows (Credit @tonesto7) 335 | - Added logic to only add supported sensors (Still in Beta) 336 | 337 | 338 | ## Version 1.16 339 | - Fixed json error when adding multiple cars 340 | - Added "vin" option to "refresh" service to allow for refreshing of individual cars 341 | - Fixed service bug calling the wrong variable 342 | - Updated manifest for latest HA requirements 343 | 344 | ## Version 1.15 345 | - Added Version attribute to manifest.json 346 | 347 | ## Version 1.14 348 | - Converted "lastrefresh" to home assistant local time 349 | 350 | ## Version 1.13 351 | - Fixed window status for Undefined 352 | - Tire pressure now reports based on region 353 | - Fixed 401 error for certain token refreshes 354 | - Token file has been moved to same folder as install (Can be changed by changing the token_location variable) 355 | 356 | ## Version 1.12 357 | - Fixed window status reporting as Open 358 | 359 | ## Version 1.11 360 | - Added check for "Undefined_window_position" window value 361 | - Fixed bug when TMPS value was 0 (Some cars return 0 on individual tyre pressures) 362 | 363 | ## Version 1.10 364 | - Fixed door open bug 2.0 (New position value) 365 | - Added a check to see if a vehicle supports GPS before adding the entity 366 | 367 | ## Version 1.09 368 | - Added individual TMPS Support 369 | - Fixed door open bug 370 | 371 | ## Version 1.08 372 | - Added Icons for each entity 373 | - Added "clear_tokens" service call 374 | - Added Electric Vehicle features 375 | - Fixed "Invalid" lock status 376 | 377 | ## Version 1.07 378 | - Support for multiple regions (Fixes unavaliable bug) 379 | - Token renamed to fordpass_token 380 | 381 | **In order to support regions you will need to reinstall the integration to change region** (Existing installs will default to North America) 382 | 383 | ## Version 1.06 384 | - Minor bug fix 385 | ## Version 1.05 386 | - Added device_tracker type (fordpass_tracker) 387 | - Added imperial or metric selection 388 | - Change fuel reading to % 389 | - Renamed lock entity from "lock.lock" to "lock.fordpass_doorlock" 390 | 391 | 392 | ## Version 1.04 393 | - Added window position status 394 | - Added service "fresh_status" to allow for polling the car at a set interval or event 395 | - Added Last Refreshed sensor, so you can see when the car was last polled for data 396 | - Added some more debug logging 397 | 398 | ## Version 1.03 399 | - Added door status 400 | - Added token saving 401 | - Added car poll refresh -------------------------------------------------------------------------------- /custom_components/fordpass/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "selector": { 3 | "region": { 4 | "options": { 5 | "aaa": "------ FORD Vehicles ------", 6 | "deu": "Germany", 7 | "fra": "France", 8 | "nld": "Netherlands", 9 | "ita": "Italy", 10 | "esp": "Spain", 11 | "gbr": "United Kingdom of Great Britain and Northern Ireland", 12 | "aus": "Australia [requires an 'ford.com.au/ford.co.nz' Account]", 13 | "nzl": "New Zealand [requires an 'ford.co.nz/ford.com.au' Account]", 14 | "zaf": "South Africa [requires an 'ford.co.za' Account]", 15 | "can": "Canada", 16 | "mex": "Mexico", 17 | "usa": "The United States of America", 18 | "bra": "Brazil [requires an 'ford.com.br/ford.com.ar' Account]", 19 | "arg": "Argentina [requires an 'ford.com.ar/ford.com.br' Account]", 20 | "rest_of_europe": "Other European Countries", 21 | "rest_of_world": "Rest of the World", 22 | "zzz": "------ LINCOLN Vehicles ------", 23 | "lincoln_usa": "United States of America" 24 | } 25 | }, 26 | "setup_type": { 27 | "options": { 28 | "new_account": "Add a new FordPass™/The Lincoln Way™ account or configure a new region", 29 | "add_vehicle": "Add a vehicle from an existing FordPass™/The Lincoln Way™ account/region" 30 | } 31 | }, 32 | "brand": { 33 | "options": { 34 | "ford": "Ford Vehicles - FordPass™ App", 35 | "lincoln": "Lincoln Vehicles - The Lincoln Way™ App" 36 | } 37 | } 38 | }, 39 | "config": { 40 | "abort": { 41 | "already_configured": "Account is already configured", 42 | "no_vehicles": "No vehicles on account or all are configured already", 43 | "reauth_successful": "Re-Autorization was successful", 44 | "reauth_unsuccessful": "Re-Autorization FAILED\n\nPlease check the token and try again", 45 | "no_filesystem_access": "This integration requires access to the local file system of your HomeAssistant installation to store a key for your FordPass™/The Lincoln Way™ account.\n\nTo do this, a subfolder will be created in the '.storage/' folder. However, this is currently not possible. An internal test failed. You can find details in the log of your Home Assistant instance.\n\nPlease ensure that you are running the Home Assistant installation with the correct user who has write access to the local file system.\n\nIf you are running Home Assistant in a Docker container, ensure that you are running the container with the correct user and that the container has access to the local file system.\n\nPlease check your installation and restart the setup of this integration once access to the local file system is possible.", 46 | "no_brand": "No Brand (Ford or Lincoln) could be detected" 47 | }, 48 | "error": { 49 | "cannot_connect": "Failed to connect", 50 | "invalid_auth": "Invalid Credentials", 51 | "invalid_vin": "Vin not found for given account", 52 | "invalid_mobile": "Mobile number as username must be specified if using South African Region", 53 | "invalid_token": "Token is invalid, please check you copied the correct token from the Header Location, it should start with fordapp:// (or lincolnapp://)", 54 | "unknown": "Unexpected error" 55 | }, 56 | "step": { 57 | "user": { 58 | "title": "Add new FordPass™/The Lincoln Way™ account or vehicle", 59 | "description": "Since you have already configured at least one FordPass™/The Lincoln Way™ account, you must first select whether you want to add another vehicle or a new account (or region).\n-- --\n### Important information concerning Multi-Vehicle Support\n\nWhen you have __multiple__ vehicles registered in your FordPass™/The Lincoln Way™ account, then in the FordPass™/The Lincoln Way™ App you must first select the vehicle you want to use, before you can access any data or functionality of this vehicle. __The same limitation is also true__ for this Home Assistant integration.\n\nThe main reason for this restriction is the fact, that the FordPass™ App and this integration makes use of a websocket connection to the Ford backend, which is some sort of bound to a single vehicle at a time.\n\nYou will find all details here: https://github.com/marq24/ha-fordpass?tab=readme-ov-file#multi-vehicle-support", 60 | "data": { 61 | "setup_type": "What do you want to do?" 62 | }, 63 | "data_description": { 64 | "setup_type": "If you set up a second vehicle with the same account, only one of the two may be active in HA at any one time - otherwise the access tokens will overwrite each other" 65 | } 66 | }, 67 | "select_account": { 68 | "title": "Select your FordPass™/The Lincoln Way™-Account", 69 | "description": "Please select the FordPass™/The Lincoln Way™ account from which you want to add another vehicle.", 70 | "data": { 71 | "account": "FordPass™/The Lincoln Way™ account" 72 | }, 73 | "data_description": { 74 | "account": "If you set up a second vehicle with the same account, only one of the two may be active in HA at any one time - otherwise the access tokens will overwrite each other" 75 | } 76 | }, 77 | "token": { 78 | "title": "Setup Token", 79 | "description": "The Token setup requires an external browser to get finally the access token.\r\rDetails can be found https://github.com/marq24/ha-fordpass/blob/main/doc/OBTAINING_TOKEN.md\r\rPlease follow the steps:\r1. Copy the URL below\r2. Open a Browser (with enabled developer tools) and paste the copied URL into your second browser\r3. Enter your FordPass™/The Lincoln Way™ credentials (again) and press the login button\r4. Watch the Network-tab till you see the `?code=` request\r5. Copy the full `Request-URL` from this request from the browsers developer tools and paste it in the Token field below\r6. Click OK to continue", 80 | "data": { 81 | "url": "URL: copy this into your browser", 82 | "tokenstr": "Token Request-URL: after the login process is completed in the browser, paste the full `Request-URL`" 83 | } 84 | }, 85 | "reauth_confirm": { 86 | "title": "Re-Authorization required", 87 | "description": "Your previous token is invalid - so you must provide anew one:\r\rThe Token setup requires an external browser to get finally the access token.\r\rDetails can be found https://github.com/marq24/ha-fordpass/blob/main/doc/OBTAINING_TOKEN.md\r\rPlease follow the steps:\r1. Copy the URL below\r2. Open a Browser (with enabled developer tools) and paste the copied URL into your second browser\r3. Enter your FordPass™/The Lincoln Way™ credentials (again) and press the login button (don't get fooled by the spinner will spin forever - this is 'normal')\r4. Watch the Network-tab till you see the `?code=` request\r5. Copy the full `Request-URL` from this request from the browsers developer tools and paste it in the Token field below\r6. Click OK to continue", 88 | "data": { 89 | "url": "URL: copy this into your browser", 90 | "tokenstr": "Token Request-URL: after the login process is completed in the browser, paste the full `Request-URL`" 91 | } 92 | }, 93 | "brand": { 94 | "data": { 95 | "brand": "Select your Brand" 96 | } 97 | }, 98 | "new_account_ford": { 99 | "data": { 100 | "password": "FordPass™ Password", 101 | "username": "FordPass™ Username (Email)", 102 | "region" : "FordPass™ Region" 103 | }, 104 | "data_description": { 105 | "username": "If using a mobile instead of email please enter your number (minus initial 0) also include + and the country code (e.g. +99123456789)", 106 | "region": "The region you are going to select must match the region for which you have registered your FordPass™ account. Further details at GitHub" 107 | } 108 | }, 109 | "new_account_lincoln": { 110 | "data": { 111 | "password": "The Lincoln Way™ Password", 112 | "username": "The Lincoln Way™ Username (Email)", 113 | "region" : "The Lincoln Way™ Region" 114 | }, 115 | "data_description": { 116 | "username": "If using a mobile instead of email please enter your number (minus initial 0) also include + and the country code (e.g. +99123456789)", 117 | "region": "The region you are going to select must match the region for which you have registered your The Lincoln Way™ account. Further details at GitHub" 118 | } 119 | }, 120 | "vehicle": { 121 | "title": "Select vehicle to add", 122 | "description": "Only vehicles not currently added will be shown", 123 | "data": { 124 | "vin": "VIN" 125 | } 126 | }, 127 | "vin": { 128 | "title": "Manual Vin Entry", 129 | "description": "Please enter your VIN number manually as no vehicles could be found automatically.", 130 | "data": { 131 | "vin": "Vin Number for vehicle" 132 | } 133 | } 134 | } 135 | }, 136 | "options": { 137 | "step": { 138 | "init": { 139 | "data": { 140 | "pressure_unit": "Unit of Pressure", 141 | "distance_unit": "Unit of Distance", 142 | "distance_conversion": "Disable distance conversion", 143 | "update_interval": "Interval to poll FordPass API (Seconds)", 144 | "force_remote_climate_control": "The remote climate (❄|☀) control options should always be available", 145 | "log_to_filesystem": "Log API responses to local HA filesystem" 146 | }, 147 | "data_description": { 148 | "force_remote_climate_control": "If you activate this option, the setting specified by Ford will be ignored.", 149 | "log_to_filesystem": "This option should not be activated over a longer period of time!\rFiles can be found: './storage/fordpass/data_dumps'" 150 | }, 151 | "description": "Configure fordpass options" 152 | } 153 | } 154 | }, 155 | "services": { 156 | "refresh_status": { 157 | "name": "Refresh Vehicle Status", 158 | "description": "Poll car for latest status (Takes up to 5mins to update once this function has been run!)", 159 | "fields": { 160 | "vin": { 161 | "name": "VIN", 162 | "description": "Enter a vin number to only refresh the specified vehicle (Default refreshes all added vehicles)" 163 | } 164 | } 165 | }, 166 | "clear_tokens": { 167 | "name": "Clear Tokens", 168 | "description": "Clear the stored token cache (requires an re-authentication afterwards)" 169 | }, 170 | "reload": { 171 | "name": "Reload", 172 | "description": "Reload the FordPass Integration" 173 | }, 174 | "poll_api": { 175 | "name": "Poll API", 176 | "description": "Manually poll API for data update (Warning: doing this too often could result in a ban)" 177 | } 178 | }, 179 | "title": "FordPass", 180 | 181 | "entity": { 182 | "button": { 183 | "update_data": {"name": "Local Sync"}, 184 | "request_refresh": {"name": "Remote Sync"}, 185 | "doorlock": {"name": "Lock"}, 186 | "doorunlock": {"name": "Unlock"}, 187 | "evstart": {"name": "EV Charging start"}, 188 | "evcancel": {"name": "EV Charging resume"}, 189 | "evpause": {"name": "EV Charging pause"}, 190 | "hafshort": {"name": "Honk & Flash [1 sec.]"}, 191 | "hafdefault": {"name": "Honk & Flash [3 sec.]"}, 192 | "haflong": {"name": "Honk & Flash [5 sec.]"}, 193 | "extendremotestart":{"name": "RC (❄|☀): Extend Time"}, 194 | "msgdeleteall": {"name": "Messages: Delete All"}, 195 | "msgdeletelast": {"name": "Messages: Delete Last"} 196 | }, 197 | "device_tracker": {"tracker": {"name": "Vehicle Tracker"}}, 198 | "lock": {"doorlock":{"name": "Doors"}}, 199 | "switch": { 200 | "ignition": {"name": "RC (❄|☀): Start [RemoteControl]"}, 201 | "elvehcharge": {"name": "EV Charging (Pause)"}, 202 | "guardmode": {"name": "Guard Mode"}, 203 | "autosoftwareupdates": {"name": "Automatic Software Updates"}, 204 | "rccdefrostrear": {"name": "RC (❄|☀): Rear Defrost [RemoteControl]"}, 205 | "rccdefrostfront": {"name": "RC (❄|☀): Heated Windshield [RemoteControl]"}, 206 | "rccsteeringwheel": {"name": "RC (❄|☀): Steering Wheel Heating [RemoteControl]"} 207 | }, 208 | "number": { 209 | "rcctemperature": {"name": "RC (❄|☀): Climate Temperature [RemoteControl]"}, 210 | "elvehtargetcharge": {"name": "Target charge level"}, 211 | "globaltargetsoc": {"name": "General: Target charge level"}, 212 | "globalaccurrentlimit": {"name": "AC Current Limit"}, 213 | "globaldcpowerlimit": {"name": "DC Power Limit"} 214 | }, 215 | "sensor": { 216 | "odometer": {"name": "Odometer"}, 217 | "fuel": {"name": "Fuel"}, 218 | "battery": {"name": "Battery (12V)"}, 219 | "oil": {"name": "Oil Life"}, 220 | "tirepressure": {"name": "Tire Pressure"}, 221 | "gps": {"name": "GPS JSON"}, 222 | "alarm": {"name": "Alarm Status"}, 223 | "ignitionstatus": {"name": "Status Ignition"}, 224 | "doorstatus": {"name": "Status Door"}, 225 | "windowposition": {"name": "Window Position"}, 226 | "lastrefresh": {"name": "last refresh"}, 227 | "elveh": {"name": "EV"}, 228 | "elvehplug": {"name": "EV Plug"}, 229 | "elvehcharging": {"name": "EV Charging"}, 230 | "speed": {"name": "Speed"}, 231 | "enginespeed": {"name": "Revolution"}, 232 | "gearleverposition": {"name": "Gear Lever Position"}, 233 | "indicators": {"name": "Indicators"}, 234 | "coolanttemp": {"name": "Temperature Coolant"}, 235 | "outsidetemp": {"name": "Temperature Outdoors"}, 236 | "engineoiltemp": {"name": "Temperature Engine Oil"}, 237 | "deepsleep": {"name": "Sleep Mode"}, 238 | "firmwareupginprogress": {"name": "Firmware Update In Progress"}, 239 | "remotestartstatus": {"name": "RC (❄|☀): Status Remote Start"}, 240 | "zonelighting": {"name": "Zone Lighting"}, 241 | "messages": {"name": "Messages"}, 242 | "dieselsystemstatus": {"name": "Status Diesel System"}, 243 | "exhaustfluidlevel": {"name": "AdBlue Level"}, 244 | "events": {"name": "Events"}, 245 | "metrics": {"name": "Metrics"}, 246 | "states": {"name": "States"}, 247 | "vehicles": {"name": "Vehicles"}, 248 | 249 | "soc": {"name": "State of Charge"}, 250 | "evccstatus": {"name": "EVCC status code"}, 251 | "seatbelt": {"name": "Belt Status"}, 252 | "deviceconnectivity": {"name": "Connectivity"}, 253 | 254 | "yawrate": {"name": "Yaw Rate"}, 255 | "acceleration": {"name": "Acceleration"}, 256 | "brakepedalstatus": {"name": "Status Brake Pedal"}, 257 | "braketorque": {"name": "Brake Torque"}, 258 | "acceleratorpedalposition": {"name": "Accelerator Pedal Position"}, 259 | "parkingbrakestatus": {"name": "Status Parking Brake"}, 260 | "torqueattransmission": {"name": "Torque at Transmission"}, 261 | "wheeltorquestatus": {"name": "Status Wheel Torque"}, 262 | "cabintemperature": {"name": "Cabin Temperature"}, 263 | "lastenergyconsumed": {"name": "EV Energy Consumption (last trip)"}, 264 | "energytransferlogentry": {"name": "EV Last Charging Session"}, 265 | "remotestartcountdown": {"name": "RC (❄|☀): Remaining Time"} 266 | }, 267 | "select": { 268 | "zonelighting": { 269 | "name": "Zone Lighting", 270 | "state": { 271 | "0": "ON", 272 | "1": "Front", 273 | "2": "Rear", 274 | "3": "Driver", 275 | "4": "Passenger", 276 | "off": "OFF" 277 | } 278 | }, 279 | "rcctemperature": { 280 | "name": "RC (❄|☀): Temperatur [RemoteControl]", 281 | "state": {"lo": "LOW", 282 | "16": "16.0°C", "16_5": "16.5°C", "17": "17.0°C", "17_5": "17.5°C", "18": "18.0°C", "18_5": "18.5°C", "19": "19.0°C", "19_5": "19.5°C", "20": "20.0°C", "20_5": "20.5°C", 283 | "21": "21.0°C", "21_5": "21.5°C", "22": "22.0°C", "22_5": "22.5°C", "23": "23.0°C", "23_5": "23.5°C", "24": "24.0°C", "24_5": "24.5°C", "25": "25.0°C", "25_5": "25.5°C", 284 | "26": "26.0°C", "26_5": "26.5°C", "27": "27.0°C", "27_5": "27.5°C", "28": "28.0°C", "28_5": "28.5°C", "29": "29.0°C", "29_5": "29.5°C", "30": "30.0°C", 285 | "hi": "HIGH"} 286 | }, 287 | "rcctemperaturefahrenheit": { 288 | "name": "RC (❄|☀): Temperatur [RemoteControl]", 289 | "state": {"lo": "LOW", 290 | "16": "60.8°F", "16_5": "61.7°F", "17": "62.6°F", "17_5": "63.5°F", "18": "64.4°F", "18_5": "65.3°F", "19": "66.2°F", "19_5": "67.1°F", "20": "68.0°F", "20_5": "68.9°F", 291 | "21": "69.8°F", "21_5": "70.7°F", "22": "71.6°F", "22_5": "72.5°F", "23": "73.4°F", "23_5": "74.3°F", "24": "75.2°F", "24_5": "76.1°F", "25": "77.0°F", "25_5": "77.9°F", 292 | "26": "78.8°F", "26_5": "29.7°F", "27": "80.6°F", "27_5": "81.5°F", "28": "82.4°F", "28_5": "83.3°F", "29": "84.2°F", "29_5": "85.1°F", "30": "86.0°F", 293 | "hi": "HIGH"} 294 | }, 295 | "rccseatrearleft": { 296 | "name": "RC (❄|☀): Seat rear left [RemoteControl]", 297 | "state": { "off": "Off", "cooled1": "Cooling I", "cooled2": "Cooling II", "cooled3": "Cooling III", "heated1": "Heating I", "heated2": "Heating II", "heated3": "Heating III"} 298 | }, 299 | "rccseatrearright": { 300 | "name": "RC (❄|☀): Seat rear right [RemoteControl]", 301 | "state": { "off": "Off", "cooled1": "Cooling I", "cooled2": "Cooling II", "cooled3": "Cooling III", "heated1": "Heating I", "heated2": "Heating II", "heated3": "Heating III"} 302 | }, 303 | "rccseatfrontleft": { 304 | "name": "RC (❄|☀): Seat front left [RemoteControl]", 305 | "state": { "off": "Off", "cooled1": "Cooling I", "cooled2": "Cooling II", "cooled3": "Cooling III", "heated1": "Heating I", "heated2": "Heating II", "heated3": "Heating III"} 306 | }, 307 | "rccseatfrontright": { 308 | "name": "RC (❄|☀): Seat front right [RemoteControl]", 309 | "state": { "off": "Off", "cooled1": "Cooling I", "cooled2": "Cooling II", "cooled3": "Cooling III", "heated1": "Heating I", "heated2": "Heating II", "heated3": "Heating III"} 310 | }, 311 | "elvehtargetcharge": { 312 | "name": "Target charge level", 313 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 314 | }, 315 | "elvehtargetchargealt1": { 316 | "name": "Target charge level [alt location 1]", 317 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 318 | }, 319 | "elvehtargetchargealt2": { 320 | "name": "Target charge level [alt location 2]", 321 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 322 | }, 323 | "globaltargetsoc": { 324 | "name": "General: Target charge level", 325 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 326 | } 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /custom_components/fordpass/translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "selector": { 3 | "region": { 4 | "options": { 5 | "aaa": "------ FORD Fahrzeuge ------", 6 | "deu": "Deutschland", 7 | "fra": "Frankreich", 8 | "nld": "Niederlande", 9 | "ita": "Italien", 10 | "esp": "Spanien", 11 | "gbr": "Vereinigtes Königreich Großbritannien und Irland", 12 | "aus": "Australien [benötigt einen 'ford.com.au/ford.co.nz' Konto]", 13 | "nzl": "Neuseeland [benötigt einen 'ford.co.nz/ford.com.au' Konto]", 14 | "zaf": "Südafrika [benötigt einen 'ford.co.za' Konto]", 15 | "can": "Kanada", 16 | "mex": "Mexiko", 17 | "usa": "Die Vereinigten Staaten von Amerika", 18 | "bra": "Brasilien [benötigt einen 'ford.com.br/ford.com.ar' Konto]", 19 | "arg": "Argentinien [benötigt einen 'ford.com.ar/ford.com.br' Konto]", 20 | "rest_of_europe": "Andere europäische Länder", 21 | "rest_of_world": "Rest der Welt", 22 | "zzz": "------ LINCOLN Fahrzeuge ------", 23 | "lincoln_usa": "Vereinigten Staaten von Amerika" 24 | } 25 | }, 26 | "setup_type": { 27 | "options": { 28 | "new_account": "Ein neues FordPass™/The Lincoln Way™ Konto (oder neue Region) hinzufügen", 29 | "add_vehicle": "Ein weiteres Fahrzeug aus einem bestehenden FordPass™/The Lincoln Way™ Konto/Region hinzufügen" 30 | } 31 | }, 32 | "brand": { 33 | "options": { 34 | "ford": "Ford Fahrzeuge - FordPass™ App", 35 | "lincoln": "Lincoln Fahrzeuge - The Lincoln Way™ App" 36 | } 37 | } 38 | }, 39 | "config": { 40 | "abort": { 41 | "already_configured": "Konto ist bereits konfiguriert", 42 | "no_vehicles": "Keine Fahrzeuge im Konto oder alle sind bereits konfiguriert", 43 | "reauth_successful": "Re-Autorisierung erfolgreich", 44 | "reauth_unsuccessful": "Re-Autorisierung fehlgeschlagen\n\nBitte prüf Deinen Login und versuche es erneut", 45 | "no_filesystem_access": "Diese Integration benötigt Zugriff auf das lokale Dateisystem Deiner Home Assistant Installation, um einen Schlüssel für Deinen FordPass™/The Lincoln Way™ Account speichern zu können.\n\nHierzu wird im in dem Ordner '.storage/' einen Unterordner anlegen. Dies ist jedoch derzeit nicht möglich. Ein interner Test ist fehlgeschlagen. Details hierzu findest Du im LOG Deiner Home Assistant Instanz.\n\nBitte stelle sicher, dass Du die Home Assistant Installation mit dem richtigen Benutzer ausführst, der Schreibzugriff auf das lokale Dateisystem hat.\n\nWenn Du Home Assistant in einem Docker-Container ausführst, stelle sicher, dass Du den Container mit dem richtigen Benutzer ausführst und dass der Container Zugriff auf das lokale Dateisystem hat.\n\nBitte prüfe Deine installation und starte die Einrichtung dieser Integration neu, sobald der Zugriff auf das lokale Dateisystem möglich ist.", 46 | "no_brand": "Kein Hersteller (Ford oder Lincoln) erkannt" 47 | }, 48 | "error": { 49 | "cannot_connect": "Verbindungsfehler", 50 | "invalid_auth": "Authentifizierungsfehler", 51 | "invalid_vin": "Die VIN kann nicht in deinem Konto gefunden werden", 52 | "invalid_mobile": "Bei Verwendung der südafrikanischen Region muss die Mobiltelefonnummer als Benutzername angegeben werden", 53 | "invalid_token": "Das Token ist ungültig. Bitte überprüfe, ob Du das richtige Token aus dem Header-Speicherort kopiert hast. Es sollte mit fordapp:// (oder lincolnapp://) beginnen.", 54 | "unknown": "Unbekannter Fehler" 55 | }, 56 | "step": { 57 | "user": { 58 | "title": "Neues FordPass™/The Lincoln Way™-Konto oder weiteres Fahrzeug hinzufügen", 59 | "description": "Da Du schon mindestens ein FordPass™/The Lincoln Way™-Konto konfiguriert hast, musst Du zunächst auswählen, ob Du ein weiteres Fahrzeug, oder neues Konto (oder eine neue Region) hinzufügen möchtest.\n-- --\n### Wichtige Informationen bei mehr als einem Fahrzeug\n\nWenn Du __mehrere__ Fahrzeuge in Deinem FordPass™/The Lincoln Way™-Konto registriert hast, musst Du auch in der FordPass™/The Lincoln Way™-App zunächst das gewünschte Fahrzeug auswählen, bevor Du auf die Daten oder Funktionen des Fahrzeugs zugreifen kannst. __Dieselbe Einschränkung gilt__ auch für diese Home Assistant-Integration.\n\nDer Hauptgrund für diese Einschränkung ist die Tatsache, dass die FordPass™-App und diese Integration eine WebSocket-Verbindung zum Ford-Backend nutzen, die jeweils an ein einzelnes Fahrzeug gebunden ist.\n\nAlle Details findest Du unter: https://github.com/marq24/ha-fordpass?tab=readme-ov-file#multi-vehicle-support", 60 | "data": { 61 | "setup_type": "Was möchtest Du tun?" 62 | }, 63 | "data_description": { 64 | "setup_type": "Wenn Du mit demselben Account ein zweites Fahrzeug einrichtest, darf immer nur eines der beiden im HA aktiv sein – sonst überschreiben sich die Access-Token gegenseitig" 65 | } 66 | }, 67 | "select_account": { 68 | "title": "FordPass™/The Lincoln Way™-Konto auswählen", 69 | "description": "Bitte wähle das FordPass™/The Lincoln Way™-Konto, aus dem Du ein weiteres Fahrzeug hinzufügen möchtest.", 70 | "data": { 71 | "account": "FordPass™/The Lincoln Way™ Konto" 72 | }, 73 | "data_description": { 74 | "account": "Wenn Du mit demselben Account ein zweites Fahrzeug einrichtest, darf immer nur eines der beiden im HA aktiv sein – sonst überschreiben sich die Access-Token gegenseitig" 75 | } 76 | }, 77 | "token": { 78 | "title": "Zugriffs-Token Einrichtung", 79 | "description": "Die Token-Einrichtung erfordert einen externen Browser, um den finalen Zugriffstoken von Ford zu erhalten.\r\rDetails findest Du unter https://github.com/marq24/ha-fordpass/blob/main/doc/OBTAINING_TOKEN.md\r\rBitte befolge die Schritte:\r1. Kopiere die URL aus dem oberen Feld\r2. Öffne einen weiteren Browser (mit aktivierten Entwicklertools) und füge die kopierte URL in dieses zweiter Browserfenster ein\r3. Gibt Deine FordPass™/The Lincoln Way™-Anmeldeinformationen (erneut) ein und starte den Anmeldeforgang\r4. Beobachte die Registerkarte „Netzwerk“, bis der Request „?code=“ zu sehen ist\r5. Kopiere die vollständige „Request-URL“ dieser Anfrage aus den Browser Entwicklertools und fügen diese hier unten im Token-Feld ein\r6. Klicke auf „OK“ um fortzufahren", 80 | "data": { 81 | "url": "URL: Füge diese in Deinen Browser ein", 82 | "tokenstr": "Token Request-URL: Nachdem der Anmeldevorgang im Browser abgeschlossen ist, füge die vollständige „Request-URL“ ein" 83 | } 84 | }, 85 | "reauth_confirm": { 86 | "title": "Erneute Autorisierung erforderlich", 87 | "description": "Dein bisheriges Token ist ungültig (geworden) - Du musst also ein Neues angeben:\r\rDetails findest Du unter https://github.com/marq24/ha-fordpass/blob/main/doc/OBTAINING_TOKEN.md\n\nBitte befolge die Schritte:\n1. Kopiere die URL aus dem oberen Feld\n2. Öffnen einen weiteren Browser (mit aktivierten Entwicklertools) und füge die kopierte URL in dieses zweite Browserfenster ein\n3. Gibt Deine FordPass™/The Lincoln Way™-Anmeldeinformationen (erneut) ein und starte den Anmeldeforgang (lass Dich nicht davon verwirren, dass der Spinner sich nicht aufhört zu drehen, das ist 'normal')\n4. Beobachte die Registerkarte „Netzwerk“, bis der Request „?code=“ zu sehen ist\n5. Kopiere die vollständige „Request-URL“ dieser Anfrage aus den Browser Entwicklertools und fügen diese hier unten im Token-Feld ein\n6. Klicke auf „OK“ um fortzufahren", 88 | "data": { 89 | "url": "URL: Füge diese in Deinen Browser ein", 90 | "tokenstr": "Token Request-URL: Nachdem der Anmeldevorgang im Browser abgeschlossen ist, füge die vollständige „Request-URL“ ein" 91 | } 92 | }, 93 | "brand": { 94 | "data": { 95 | "brand": "Wähle einen Hersteller aus" 96 | } 97 | }, 98 | "new_account_ford": { 99 | "data": { 100 | "password": "FordPass™ Passwort", 101 | "username": "FordPass™ Username (Email)", 102 | "region": "FordPass™ Region" 103 | }, 104 | "data_description": { 105 | "username": "Wenn Du ein Mobiltelefon statt einer E-Mail-Adresse verwendest, gib bitte Deine Nummer (ohne die anfängliche 0) sowie + und die Landesvorwahl ein (z. B. +99123456789).", 106 | "region": "Die Region, die Du auswählst, muss mit der Region übereinstimmen, für die Du dein FordPass™-Konto registriert hast. Weitere Details auf GitHub" 107 | } 108 | }, 109 | "new_account_lincoln": { 110 | "data": { 111 | "password": "The Lincoln Way™ Passwort", 112 | "username": "The Lincoln Way™ Username (Email)", 113 | "region" : "The Lincoln Way™ Region" 114 | }, 115 | "data_description": { 116 | "username": "Wenn Du ein Mobiltelefon statt einer E-Mail-Adresse verwendest, gib bitte Deine Nummer (ohne die anfängliche 0) sowie + und die Landesvorwahl ein (z. B. +99123456789).", 117 | "region": "Die Region, die Du auswählst, muss mit der Region übereinstimmen, für die Du dein The Lincoln Way™-Konto registriert hast. Weitere Details auf GitHub" 118 | } 119 | }, 120 | "vehicle": { 121 | "title": "Zum Hinzufügen ein Fahrzeug auswählen", 122 | "description": "Es werden nur Fahrzeuge angezeigt, die derzeit nicht hinzugefügt wurden", 123 | "data": { 124 | "vin": "VIN" 125 | } 126 | }, 127 | "vin": { 128 | "title": "Manuelle VIN-Eingabe", 129 | "description": "Bitte gib Deine VIN (Fahrzeug-Identifikationsnummer) manuell ein, da keine Fahrzeuge automatisch gefunden werden konnten.", 130 | "data": { 131 | "vin": "VIN Deines Fahrzeugs" 132 | } 133 | } 134 | } 135 | }, 136 | "options": { 137 | "step": { 138 | "init": { 139 | "data": { 140 | "pressure_unit": "Maßeinheit für Druck", 141 | "distance_unit": "Maßeinheit für Entfernung", 142 | "update_interval": "Aktualisierungsintervall FordPass-API (Sekunden)", 143 | "force_remote_climate_control": "Die Optionen der Klima Fernsteuerung (❄|☀) [RemoteControl] sollen immer verfügbar sein", 144 | "log_to_filesystem": "Protokolliere API-Antworten im lokalen HA Dateisystem" 145 | }, 146 | "data_description": { 147 | "force_remote_climate_control": "Wenn Du diese Option aktivierst, werden die von Ford vorgegebenen Einstellungen ignoriert.", 148 | "log_to_filesystem": "Diese Option sollte nicht über einen längeren Zeitraum aktiviert sein!\rDu findest die Dateien unter: './storage/fordpass/data_dumps'" 149 | }, 150 | "description": "Optionen konfigurieren" 151 | } 152 | } 153 | }, 154 | "services": { 155 | "refresh_status": { 156 | "name": "Fahrzeugstatus aktualisieren", 157 | "description": "Aktualisiert den Status und die Entitäten des Fahrzeugs. Der Vorgang kann bis zu 5 Minuten dauern.", 158 | "fields": { 159 | "vin": { 160 | "name": "FIN", 161 | "description": "Wenn angegeben, aktualisiert nur den Status des Fahrzeugs mit dieser FIN. Ansonsten wird den Status aller Fahrzeuge aktualisiert." 162 | } 163 | } 164 | }, 165 | "clear_tokens": { 166 | "name": "Token-Cache leeren", 167 | "description": "Leert die zwischengespeicherten Tokens (macht eine Re-Authorisierung erforderlich)" 168 | }, 169 | "reload": { 170 | "name": "Integration neu laden", 171 | "description": "Lädt die FordPass-Integration neu." 172 | }, 173 | "poll_api": { 174 | "name": "API aufrufen", 175 | "description": "Ruft eine manuelle Datenaktualisierung aus der FordPass-API auf. (Vorsicht: Häufige Aufrufe können zu einer temporären Drosselung/Sperre führen)" 176 | } 177 | }, 178 | "title": "FordPass", 179 | 180 | "entity": { 181 | "button": { 182 | "update_data": {"name": "Lokaler Sync"}, 183 | "request_refresh": {"name": "Remote Sync"}, 184 | "doorlock": {"name": "Verriegeln"}, 185 | "doorunlock": {"name": "Entriegeln"}, 186 | "evstart": {"name": "EV Laden starten"}, 187 | "evcancel": {"name": "EV Laden fortfahren"}, 188 | "evpause": {"name": "EV Laden pausieren"}, 189 | "hafshort": {"name": "Hupen & Blinken [1 Sek.]"}, 190 | "hafdefault": {"name": "Hupen & Blinken [3 Sek.]"}, 191 | "haflong": {"name": "Hupen & Blinken [5 Sek.]"}, 192 | "extendremotestart":{"name": "RC (❄|☀): Laufzeit verlängern"}, 193 | "msgdeleteall": {"name": "Nachrichten: Alle Löschen"}, 194 | "msgdeletelast": {"name": "Nachrichten: Letzte Löschen"} 195 | }, 196 | "device_tracker": {"tracker": {"name": "Fahrzeug Tracker"}}, 197 | "lock": {"doorlock":{"name": "Verriegelung"}}, 198 | "switch": { 199 | "ignition": {"name": "RC (❄|☀): Start [RemoteControl]"}, 200 | "elvehcharge": {"name": "EV Laden (Pause)"}, 201 | "guardmode": {"name": "Alarmanlage"}, 202 | "autosoftwareupdates": {"name": "Automatische Software Aktualisierungen"}, 203 | "rccdefrostrear": {"name": "RC (❄|☀): Heckscheibenheizung [RemoteControl]"}, 204 | "rccdefrostfront": {"name": "RC (❄|☀): Frontscheibenheizung [RemoteControl]"}, 205 | "rccsteeringwheel": {"name": "RC (❄|☀): Lenkradheizung [RemoteControl]"} 206 | }, 207 | "number": { 208 | "rcctemperature": {"name": "RC (❄|☀): Temperatur [RemoteControl]"}, 209 | "elvehtargetcharge": {"name": "Ziel-Ladestand EV"}, 210 | "globaltargetsoc": {"name": "Allgemein: Ziel-Ladestand EV"}, 211 | "globalaccurrentlimit": {"name": "Stromstärkenlimit (AC)"}, 212 | "globaldcpowerlimit": {"name": "Leistungslimit (DC)"} 213 | }, 214 | "sensor": { 215 | "odometer": {"name": "Kilometerstand"}, 216 | "fuel": {"name": "Tankanzeige"}, 217 | "battery": {"name": "Batterie (12V)"}, 218 | "oil": {"name": "Öl Qualität"}, 219 | "tirepressure": {"name": "Reifendruck"}, 220 | "gps": {"name": "GPS JSON"}, 221 | "alarm": {"name": "Alarmanlage"}, 222 | "ignitionstatus": {"name": "Status Zündung"}, 223 | "doorstatus": {"name": "Status Türen"}, 224 | "windowposition": {"name": "Fenster Positionen"}, 225 | "lastrefresh": {"name": "letzte Aktualisierung"}, 226 | "elveh": {"name": "EV Daten"}, 227 | "elvehplug": {"name": "EV Steckerstatus"}, 228 | "elvehcharging": {"name": "EV Ladestatus"}, 229 | "speed": {"name": "Geschwindigkeit"}, 230 | "enginespeed": {"name": "Motordrehzahl"}, 231 | "gearleverposition": {"name": "Gang"}, 232 | "indicators": {"name": "Warnmeldungen"}, 233 | "coolanttemp": {"name": "Temperatur Kühlmittel"}, 234 | "outsidetemp": {"name": "Temperatur Außen"}, 235 | "engineoiltemp": {"name": "Temperatur Motoröl"}, 236 | "deepsleep": {"name": "Tiefschlaf"}, 237 | "firmwareupginprogress": {"name": "Firmware Update wird durchfeführt"}, 238 | "remotestartstatus": {"name": "RC (❄|☀): Status Fernstart"}, 239 | "zonelighting": {"name": "Zonenbeleuchtung"}, 240 | "messages": {"name": "Nachrichten"}, 241 | "dieselsystemstatus": {"name": "Status Diesel System"}, 242 | "exhaustfluidlevel": {"name": "AdBlue-Stand"}, 243 | "events": {"name": "Ereignisse"}, 244 | "metrics": {"name": "Metriken"}, 245 | "states": {"name": "Status"}, 246 | "vehicles": {"name": "Fahrzeuge"}, 247 | 248 | "soc": {"name": "Ladestand"}, 249 | "evccstatus": {"name": "EVCC Status-Code"}, 250 | "seatbelt": {"name": "Gurt-Status"}, 251 | "deviceconnectivity": {"name": "Verbindung"}, 252 | 253 | "yawrate": {"name": "Gier und Neigung"}, 254 | "acceleration": {"name": "Beschleunigung"}, 255 | "brakepedalstatus": {"name": "Status Bremspedal"}, 256 | "braketorque": {"name": "Bremsmoment"}, 257 | "acceleratorpedalposition": {"name": "Gaspedalstellung"}, 258 | "parkingbrakestatus": {"name": "Status Feststellbremse"}, 259 | "torqueattransmission": {"name": "Drehmoment am Getriebe"}, 260 | "wheeltorquestatus": {"name": "Status Drehmoment am Rad"}, 261 | "cabintemperature": {"name": "Innenraumtemperatur"}, 262 | "lastenergyconsumed": {"name": "EV Energieverbrauch (letzte Fahrt)"}, 263 | "energytransferlogentry": {"name": "EV Letzter Ladevorgang"}, 264 | "remotestartcountdown": {"name": "RC (❄|☀): Verbleibende Zeit"} 265 | }, 266 | "select": { 267 | "zonelighting": { 268 | "name": "Zonen Beleuchtung", 269 | "state": { 270 | "0": "EIN", 271 | "1": "Vorne", 272 | "2": "Hinten", 273 | "3": "Fahrerseite", 274 | "4": "Beifahrerseite", 275 | "off": "AUS" 276 | } 277 | }, 278 | "rccseatrearleft": { 279 | "name": "RC (❄|☀): Sitz hinten links [RemoteControl]", 280 | "state": { "off": "aus", "cooled1": "Kühlen Stufe I", "cooled2": "Kühlen Stufe II", "cooled3": "Kühlen Stufe III", "heated1": "Heizen Stufe I", "heated2": "Heizen Stufe II", "heated3": "Heizen Stufe III"} 281 | }, 282 | "rccseatrearright": { 283 | "name": "RC (❄|☀): Sitz hinten rechts [RemoteControl]", 284 | "state": { "off": "aus", "cooled1": "Kühlen Stufe I", "cooled2": "Kühlen Stufe II", "cooled3": "Kühlen Stufe III", "heated1": "Heizen Stufe I", "heated2": "Heizen Stufe II", "heated3": "Heizen Stufe III"} 285 | }, 286 | "rccseatfrontleft": { 287 | "name": "RC (❄|☀): Sitz vorne links [RemoteControl]", 288 | "state": { "off": "aus", "cooled1": "Kühlen Stufe I", "cooled2": "Kühlen Stufe II", "cooled3": "Kühlen Stufe III", "heated1": "Heizen Stufe I", "heated2": "Heizen Stufe II", "heated3": "Heizen Stufe III"} 289 | }, 290 | "rccseatfrontright": { 291 | "name": "RC (❄|☀): Sitz vorne rechts [RemoteControl]", 292 | "state": { "off": "aus", "cooled1": "Kühlen Stufe I", "cooled2": "Kühlen Stufe II", "cooled3": "Kühlen Stufe III", "heated1": "Heizen Stufe I", "heated2": "Heizen Stufe II", "heated3": "Heizen Stufe III"} 293 | }, 294 | "rcctemperature": { 295 | "name": "RC (❄|☀): Temperatur [RemoteControl]", 296 | "state": {"lo": "LOW", 297 | "16": "16.0°C", "16_5": "16.5°C", "17": "17.0°C", "17_5": "17.5°C", "18": "18.0°C", "18_5": "18.5°C", "19": "19.0°C", "19_5": "19.5°C", "20": "20.0°C", "20_5": "20.5°C", 298 | "21": "21.0°C", "21_5": "21.5°C", "22": "22.0°C", "22_5": "22.5°C", "23": "23.0°C", "23_5": "23.5°C", "24": "24.0°C", "24_5": "24.5°C", "25": "25.0°C", "25_5": "25.5°C", 299 | "26": "26.0°C", "26_5": "26.5°C", "27": "27.0°C", "27_5": "27.5°C", "28": "28.0°C", "28_5": "28.5°C", "29": "29.0°C", "29_5": "29.5°C", "30": "30.0°C", 300 | "hi": "HIGH"} 301 | }, 302 | "rcctemperaturefahrenheit": { 303 | "name": "RC (❄|☀): Temperatur [RemoteControl]", 304 | "state": {"lo": "LOW", 305 | "16": "60.8°F", "16_5": "61.7°F", "17": "62.6°F", "17_5": "63.5°F", "18": "64.4°F", "18_5": "65.3°F", "19": "66.2°F", "19_5": "67.1°F", "20": "68.0°F", "20_5": "68.9°F", 306 | "21": "69.8°F", "21_5": "70.7°F", "22": "71.6°F", "22_5": "72.5°F", "23": "73.4°F", "23_5": "74.3°F", "24": "75.2°F", "24_5": "76.1°F", "25": "77.0°F", "25_5": "77.9°F", 307 | "26": "78.8°F", "26_5": "29.7°F", "27": "80.6°F", "27_5": "81.5°F", "28": "82.4°F", "28_5": "83.3°F", "29": "84.2°F", "29_5": "85.1°F", "30": "86.0°F", 308 | "hi": "HIGH"} 309 | }, 310 | "elvehtargetcharge": { 311 | "name": "Ziel-Ladestand EV", 312 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 313 | }, 314 | "elvehtargetchargealt1": { 315 | "name": "Ziel-Ladestand EV [alternativer Ort 1]", 316 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 317 | }, 318 | "elvehtargetchargealt2": { 319 | "name": "Ziel-Ladestand EV [alternativer Ort 2]", 320 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 321 | }, 322 | "globaltargetsoc": { 323 | "name": "Allgemein: Ziel-Ladestand EV", 324 | "state": { "50": "50%", "60": "60%", "70": "70%", "80": "80%", "85": "85%", "90": "90%", "95": "95%", "100": "100%"} 325 | } 326 | } 327 | } 328 | } -------------------------------------------------------------------------------- /custom_components/fordpass/config_flow.py: -------------------------------------------------------------------------------- 1 | """Config flow for FordPass integration.""" 2 | import asyncio 3 | import hashlib 4 | import logging 5 | import re 6 | from base64 import urlsafe_b64encode 7 | from collections.abc import Mapping 8 | from pathlib import Path 9 | from secrets import token_urlsafe 10 | from typing import Any, Final 11 | 12 | import aiohttp 13 | import voluptuous as vol 14 | from homeassistant import config_entries, exceptions 15 | from homeassistant.config_entries import ConfigError, ConfigFlowResult, ConfigFlow, OptionsFlow 16 | from homeassistant.const import CONF_URL, CONF_USERNAME, CONF_REGION 17 | from homeassistant.core import callback, HomeAssistant 18 | from homeassistant.helpers import selector 19 | from homeassistant.helpers.aiohttp_client import async_create_clientsession 20 | from homeassistant.helpers.storage import STORAGE_DIR 21 | 22 | from custom_components.fordpass.const import ( # pylint:disable=unused-import 23 | DOMAIN, 24 | OAUTH_ID, 25 | CLIENT_ID, 26 | REGIONS, 27 | REGION_OPTIONS_FORD, 28 | DEFAULT_REGION_FORD, 29 | REGION_OPTIONS_LINCOLN, 30 | DEFAULT_REGION_LINCOLN, 31 | 32 | CONFIG_VERSION, 33 | CONFIG_MINOR_VERSION, 34 | CONF_IS_SUPPORTED, 35 | CONF_BRAND, 36 | CONF_VIN, 37 | CONF_PRESSURE_UNIT, 38 | CONF_LOG_TO_FILESYSTEM, 39 | CONF_FORCE_REMOTE_CLIMATE_CONTROL, 40 | PRESSURE_UNITS, 41 | DEFAULT_PRESSURE_UNIT, 42 | BRAND_OPTIONS, 43 | 44 | UPDATE_INTERVAL, 45 | UPDATE_INTERVAL_DEFAULT, 46 | ) 47 | from custom_components.fordpass.fordpass_bridge import ConnectedFordPassVehicle 48 | 49 | _LOGGER = logging.getLogger(__name__) 50 | 51 | VIN_SCHEME = vol.Schema( 52 | { 53 | vol.Required(CONF_VIN, default=""): str, 54 | } 55 | ) 56 | 57 | CONF_TOKEN_STR: Final = "tokenstr" 58 | CONF_SETUP_TYPE: Final = "setup_type" 59 | CONF_ACCOUNT: Final = "account" 60 | 61 | NEW_ACCOUNT: Final = "new_account" 62 | ADD_VEHICLE: Final = "add_vehicle" 63 | 64 | class CannotConnect(exceptions.HomeAssistantError): 65 | """Error to indicate we cannot connect.""" 66 | 67 | 68 | class InvalidToken(exceptions.HomeAssistantError): 69 | """Error to indicate there is invalid token.""" 70 | 71 | 72 | class InvalidAuth(exceptions.HomeAssistantError): 73 | """Error to indicate there is invalid auth.""" 74 | 75 | 76 | class InvalidVin(exceptions.HomeAssistantError): 77 | """Error to indicate the wrong vin""" 78 | 79 | 80 | class InvalidMobile(exceptions.HomeAssistantError): 81 | """Error to no mobile specified for South African Account""" 82 | 83 | 84 | class FordPassConfigFlowHandler(ConfigFlow, domain=DOMAIN): 85 | """Handle a config flow for FordPass.""" 86 | VERSION = CONFIG_VERSION 87 | MINOR_VERSION = CONFIG_MINOR_VERSION 88 | region_key = DEFAULT_REGION_FORD 89 | username = None 90 | code_verifier = None 91 | cached_login_input = {} 92 | _accounts = None 93 | _vehicles = None 94 | _vehicle_name = None 95 | _can_not_connect_reason = None 96 | _session: aiohttp.ClientSession | None = None 97 | 98 | # @staticmethod 99 | # def base64_url_encode(data): 100 | # """Encode string to base64""" 101 | # return urlsafe_b64encode(data).rstrip(b'=') 102 | # 103 | # def generate_hash(self, code): 104 | # """Generate hash for login""" 105 | # hashengine = hashlib.sha256() 106 | # hashengine.update(code.encode('ascii')) 107 | # return self.base64_url_encode(hashengine.digest()).decode('utf-8') 108 | 109 | @staticmethod 110 | def generate_code_challenge(): 111 | # Create a code verifier with a length of 128 characters 112 | code_verifier = token_urlsafe(96) 113 | 114 | hashed_verifier = hashlib.sha256(code_verifier.encode("utf-8")) 115 | code_challenge = urlsafe_b64encode(hashed_verifier.digest()) 116 | code_challenge_without_padding = code_challenge.rstrip(b"=") 117 | return { 118 | "code_verifier": code_verifier, 119 | "code_challenge_method": "S256", 120 | "code_challenge": code_challenge_without_padding, 121 | } 122 | 123 | @callback 124 | def configured_vehicles(self, hass: HomeAssistant) -> set[str]: 125 | """Return a list of configured vehicles""" 126 | # return { 127 | # entry.data[CONF_VIN] 128 | # for entry in hass.config_entries.async_entries(DOMAIN) 129 | # } 130 | vehicles = [] 131 | for entry in hass.config_entries.async_entries(DOMAIN): 132 | if CONF_IS_SUPPORTED in entry.data: 133 | a_vin = entry.data[CONF_VIN] 134 | a_region = entry.data.get(CONF_REGION) 135 | if a_vin is not None and a_region is not None: 136 | if a_region in REGIONS: 137 | if a_vin not in vehicles: 138 | vehicles.append(a_vin) 139 | else: 140 | _LOGGER.warning(f"configured_vehicles(): UNKNOWN REGION! vin:'{a_vin}' region:'{a_region}' from: {entry}") 141 | else: 142 | if entry.data.get(CONF_REGION) in REGIONS: 143 | _LOGGER.info(f"configured_vehicles(): LEGACY REGION configuration entry {entry} found") 144 | else: 145 | _LOGGER.warning(f"configured_vehicles(): INCOMPATIBLE CONFIG ENTRY FOUND: {entry}") 146 | return vehicles 147 | 148 | @callback 149 | def configured_accounts(self, hass: HomeAssistant): 150 | """Return a dict of configured accounts and their entry data""" 151 | accounts = {} 152 | for entry in hass.config_entries.async_entries(DOMAIN): 153 | if CONF_IS_SUPPORTED in entry.data: 154 | a_username = entry.data.get(CONF_USERNAME) 155 | a_region = entry.data.get(CONF_REGION) 156 | if a_username is not None and a_region is not None: 157 | if a_region in REGIONS: 158 | a_key = f"{a_username}µ@µ{a_region}" 159 | if a_key not in accounts: 160 | accounts[a_key] = [] 161 | 162 | accounts[a_key].append({ 163 | "username": a_username, 164 | "region": a_region, 165 | "vehicle_id": entry.data.get(CONF_VIN), 166 | }) 167 | else: 168 | _LOGGER.warning(f"configured_accounts(): UNKNOWN REGION! user:'{a_username}' region:'{a_region}' from: {entry}") 169 | else: 170 | if entry.data.get(CONF_REGION) in REGIONS: 171 | _LOGGER.info(f"configured_accounts(): LEGACY REGION configuration entry {entry} found") 172 | else: 173 | _LOGGER.warning(f"configured_accounts(): INCOMPATIBLE CONFIG ENTRY FOUND: {entry}") 174 | return accounts 175 | 176 | async def validate_token(self, data, token:str, code_verifier:str): 177 | _LOGGER.debug(f"validate_token(): {data}") 178 | if self._session is None: 179 | self._session = async_create_clientsession(self.hass) 180 | 181 | bridge = ConnectedFordPassVehicle(self._session, data[CONF_USERNAME], "", data[CONF_REGION], 182 | coordinator=None, 183 | storage_path=Path(self.hass.config.config_dir).joinpath(STORAGE_DIR)) 184 | 185 | results = await bridge.generate_tokens(token, code_verifier, data[CONF_REGION]) 186 | 187 | if results: 188 | _LOGGER.debug(f"validate_token(): request Vehicles") 189 | vehicles = await bridge.req_vehicles() 190 | _LOGGER.debug(f"validate_token(): got Vehicles -> {vehicles}") 191 | return vehicles 192 | else: 193 | _LOGGER.debug(f"validate_token(): failed - {results}") 194 | self._can_not_connect_reason = bridge.login_fail_reason 195 | raise CannotConnect 196 | 197 | async def validate_token_only(self, data, token:str, code_verifier:str) -> bool: 198 | _LOGGER.debug(f"validate_token_only(): {data}") 199 | if self._session is None: 200 | self._session = async_create_clientsession(self.hass) 201 | 202 | bridge = ConnectedFordPassVehicle(self._session, data[CONF_USERNAME], "", data[CONF_REGION], 203 | coordinator=None, 204 | storage_path=Path(self.hass.config.config_dir).joinpath(STORAGE_DIR)) 205 | 206 | results = await bridge.generate_tokens(token, code_verifier, data[CONF_REGION]) 207 | 208 | if not results: 209 | _LOGGER.debug(f"validate_token_only(): failed - {results}") 210 | self._can_not_connect_reason = bridge.login_fail_reason 211 | raise CannotConnect 212 | else: 213 | return True 214 | 215 | async def get_vehicles_from_existing_account(self, hass: HomeAssistant, data): 216 | _LOGGER.debug(f"get_vehicles_from_existing_account(): {data}") 217 | if self._session is None: 218 | self._session = async_create_clientsession(self.hass) 219 | 220 | bridge = ConnectedFordPassVehicle(self._session, data[CONF_USERNAME], 221 | "", data[CONF_REGION], 222 | coordinator=None, 223 | storage_path=Path(hass.config.config_dir).joinpath(STORAGE_DIR)) 224 | _LOGGER.debug(f"get_vehicles_from_existing_account(): request Vehicles") 225 | vehicles = await bridge.req_vehicles() 226 | _LOGGER.debug(f"get_vehicles_from_existing_account(): got Vehicles -> {vehicles}") 227 | if vehicles is not None: 228 | return vehicles 229 | else: 230 | self._can_not_connect_reason = bridge.login_fail_reason 231 | raise CannotConnect 232 | 233 | async def validate_vin(self, data): 234 | _LOGGER.debug(f"validate_vin(): {data}") 235 | if self._session is None: 236 | self._session = async_create_clientsession(self.hass) 237 | 238 | bridge = ConnectedFordPassVehicle(self._session, data[CONF_USERNAME], data[CONF_VIN], data[CONF_REGION], 239 | coordinator=None, 240 | storage_path=Path(self.hass.config.config_dir).joinpath(STORAGE_DIR)) 241 | 242 | test = await bridge.req_status() 243 | _LOGGER.debug(f"GOT SOMETHING BACK? {test}") 244 | if test and test.status_code == 200: 245 | _LOGGER.debug("200 Code") 246 | return True 247 | if not test: 248 | raise InvalidVin 249 | return False 250 | 251 | 252 | async def async_step_user(self, user_input=None): 253 | """Handle the initial step.""" 254 | errors = {} 255 | 256 | # lookup if there are already configured accounts?! 257 | self._accounts = self.configured_accounts(self.hass) 258 | 259 | if user_input is not None: 260 | if user_input.get(CONF_SETUP_TYPE) == NEW_ACCOUNT: 261 | return await self.async_step_brand() 262 | elif user_input.get(CONF_SETUP_TYPE) == ADD_VEHICLE: 263 | return await self.async_step_select_account() 264 | 265 | # Show different options based on existing accounts 266 | if len(self._accounts) > 0: 267 | return self.async_show_form( 268 | step_id="user", 269 | data_schema=vol.Schema({ 270 | vol.Required(CONF_SETUP_TYPE): 271 | selector.SelectSelector( 272 | selector.SelectSelectorConfig( 273 | options=[ADD_VEHICLE, NEW_ACCOUNT], 274 | mode=selector.SelectSelectorMode.LIST, 275 | translation_key=CONF_SETUP_TYPE, 276 | ) 277 | ) 278 | }), 279 | errors=errors 280 | ) 281 | else: 282 | # No existing accounts, go directly to new account setup 283 | return await self.async_step_brand() 284 | 285 | 286 | async def async_step_select_account(self, user_input=None): 287 | """Handle adding a vehicle to an existing account.""" 288 | errors = {} 289 | 290 | if user_input is not None: 291 | parts = user_input[CONF_ACCOUNT].split("µ@µ") 292 | self.username = parts[0] 293 | self.region_key = parts[1] 294 | self.cached_login_input = { 295 | CONF_USERNAME: self.username, 296 | CONF_REGION: self.region_key, 297 | } 298 | try: 299 | info = await self.get_vehicles_from_existing_account(self.hass, data=self.cached_login_input) 300 | return await self.extract_vehicle_info_and_proceed_with_next_step(info) 301 | 302 | except CannotConnect: 303 | if self._can_not_connect_reason is not None: 304 | errors["base"] = f"cannot_connect - '{self._can_not_connect_reason}'" 305 | else: 306 | errors["base"] = "cannot_connect - UNKNOWN REASON" 307 | except Exception as ex: 308 | _LOGGER.error(f"Error validating existing account: {ex}") 309 | errors["base"] = "unknown" 310 | 311 | # Create account selection options (when there are multiple accounts...) 312 | if len(self._accounts) > 1: 313 | account_options = {} 314 | for a_key, entries in self._accounts.items(): 315 | configured_vehicles = len(entries) 316 | parts = a_key.split("µ@µ") 317 | account_options[a_key] = f"{parts[0]} [{parts[1].upper()}]" 318 | 319 | return self.async_show_form( 320 | step_id="select_account", 321 | data_schema=vol.Schema({ 322 | vol.Required(CONF_ACCOUNT): vol.In(account_options) 323 | }), 324 | errors=errors 325 | ) 326 | else: 327 | # when there is only one account configured, we can directly jump into the vehicle selection 328 | # Get the first account key and split it to get username and region... 329 | parts = next(iter(self._accounts)).split("µ@µ") 330 | self.username = parts[0] 331 | self.region_key = parts[1] 332 | self.cached_login_input = { 333 | CONF_USERNAME: self.username, 334 | CONF_REGION: self.region_key, 335 | } 336 | info = await self.get_vehicles_from_existing_account(self.hass, data=self.cached_login_input) 337 | return await self.extract_vehicle_info_and_proceed_with_next_step(info) 338 | 339 | 340 | async def async_step_brand(self, user_input=None): 341 | errors = {} 342 | if user_input is not None: 343 | if user_input[CONF_BRAND] == "ford": 344 | return await self.async_step_new_account_ford() 345 | else: 346 | return await self.async_step_new_account_lincoln() 347 | 348 | else: 349 | user_input = {} 350 | user_input[CONF_BRAND] = "ford" 351 | 352 | has_fs_write_access = await asyncio.get_running_loop().run_in_executor(None, 353 | ConnectedFordPassVehicle.check_general_fs_access, 354 | Path(self.hass.config.config_dir).joinpath(STORAGE_DIR)) 355 | if not has_fs_write_access: 356 | return self.async_abort(reason="no_filesystem_access") 357 | else: 358 | return self.async_show_form( 359 | step_id="brand", 360 | data_schema=vol.Schema({ 361 | vol.Required(CONF_BRAND, default="ford"): 362 | selector.SelectSelector( 363 | selector.SelectSelectorConfig( 364 | options=BRAND_OPTIONS, 365 | mode=selector.SelectSelectorMode.LIST, 366 | translation_key=CONF_BRAND, 367 | ) 368 | ) 369 | }), errors=errors 370 | ) 371 | 372 | 373 | async def async_step_new_account_ford(self, user_input=None): 374 | account_data = {"default": DEFAULT_REGION_FORD, 375 | "options": REGION_OPTIONS_FORD, 376 | "step_id": "new_account_ford"} 377 | errors = {} 378 | if user_input is not None: 379 | try: 380 | self.region_key = user_input[CONF_REGION] 381 | self.username = user_input[CONF_USERNAME] 382 | return await self.async_step_token(None) 383 | 384 | except CannotConnect as ex: 385 | _LOGGER.debug(f"async_step_new_account_ford {type(ex).__name__} - {ex}") 386 | if self._can_not_connect_reason is not None: 387 | errors["base"] = f"cannot_connect - '{self._can_not_connect_reason}'" 388 | else: 389 | errors["base"] = "cannot_connect - UNKNOWN REASON" 390 | else: 391 | user_input = {} 392 | user_input[CONF_REGION] = account_data["default"] 393 | user_input[CONF_USERNAME] = "" 394 | 395 | return self.async_show_form( 396 | step_id=account_data["step_id"], 397 | data_schema=vol.Schema({ 398 | vol.Required(CONF_USERNAME, default=""): str, 399 | vol.Required(CONF_REGION, default=account_data["default"]): 400 | selector.SelectSelector( 401 | selector.SelectSelectorConfig( 402 | options=account_data["options"], 403 | mode=selector.SelectSelectorMode.DROPDOWN, 404 | translation_key=CONF_REGION, 405 | ) 406 | ) 407 | }), errors=errors 408 | ) 409 | 410 | 411 | async def async_step_new_account_lincoln(self, user_input=None): 412 | account_data = {"default": DEFAULT_REGION_LINCOLN, 413 | "options": REGION_OPTIONS_LINCOLN, 414 | "step_id": "new_account_lincoln"} 415 | errors = {} 416 | if user_input is not None: 417 | try: 418 | self.region_key = user_input[CONF_REGION] 419 | self.username = user_input[CONF_USERNAME] 420 | return await self.async_step_token(None) 421 | 422 | except CannotConnect as ex: 423 | _LOGGER.debug(f"async_step_new_account_lincoln {type(ex).__name__} - {ex}") 424 | if self._can_not_connect_reason is not None: 425 | errors["base"] = f"cannot_connect - '{self._can_not_connect_reason}'" 426 | else: 427 | errors["base"] = "cannot_connect - UNKNOWN REASON" 428 | else: 429 | user_input = {} 430 | user_input[CONF_REGION] = account_data["default"] 431 | user_input[CONF_USERNAME] = "" 432 | 433 | return self.async_show_form( 434 | step_id=account_data["step_id"], 435 | data_schema=vol.Schema({ 436 | vol.Required(CONF_USERNAME, default=""): str, 437 | vol.Required(CONF_REGION, default=account_data["default"]): 438 | selector.SelectSelector( 439 | selector.SelectSelectorConfig( 440 | options=account_data["options"], 441 | mode=selector.SelectSelectorMode.DROPDOWN, 442 | translation_key=CONF_REGION, 443 | ) 444 | ) 445 | }), errors=errors 446 | ) 447 | 448 | 449 | async def async_step_token(self, user_input=None): 450 | errors = {} 451 | 452 | if user_input is not None: 453 | try: 454 | token_fragment = user_input[CONF_TOKEN_STR] 455 | # we should not save our user-captured 'code' url... 456 | del user_input[CONF_TOKEN_STR] 457 | 458 | if self.check_token(token_fragment, self.region_key): 459 | # we don't need our generated URL either... 460 | del user_input[CONF_URL] 461 | 462 | user_input[CONF_REGION] = self.region_key 463 | user_input[CONF_USERNAME] = self.username 464 | _LOGGER.debug(f"user_input {user_input}") 465 | 466 | info = await self.validate_token(user_input, token_fragment, self.code_verifier) 467 | self.cached_login_input = user_input 468 | 469 | return await self.extract_vehicle_info_and_proceed_with_next_step(info) 470 | 471 | else: 472 | errors["base"] = "invalid_token" 473 | 474 | except CannotConnect as ex: 475 | _LOGGER.debug(f"async_step_token {ex}") 476 | if self._can_not_connect_reason is not None: 477 | errors["base"] = f"cannot_connect - '{self._can_not_connect_reason}'" 478 | else: 479 | errors["base"] = "cannot_connect - UNKNOWN REASON" 480 | 481 | if self.region_key is not None: 482 | _LOGGER.debug(f"self.region_key {self.region_key}") 483 | return self.async_show_form( 484 | step_id="token", data_schema=vol.Schema( 485 | { 486 | vol.Optional(CONF_URL, default=self.generate_url(self.region_key)): str, 487 | vol.Required(CONF_TOKEN_STR): str, 488 | } 489 | ), errors=errors 490 | ) 491 | else: 492 | _LOGGER.error("No region_key set - FATAL ERROR") 493 | raise ConfigError(f"No region_key set - FATAL ERROR") 494 | 495 | 496 | async def extract_vehicle_info_and_proceed_with_next_step(self, info): 497 | if info is not None and "userVehicles" in info and "vehicleDetails" in info["userVehicles"]: 498 | self._vehicles = info["userVehicles"]["vehicleDetails"] 499 | self._vehicle_name = {} 500 | if "vehicleProfile" in info: 501 | for a_vehicle in info["vehicleProfile"]: 502 | if "VIN" in a_vehicle and "year" in a_vehicle and "model" in a_vehicle: 503 | self._vehicle_name[a_vehicle["VIN"]] = f"{a_vehicle['year']} {a_vehicle['model']}" 504 | 505 | _LOGGER.debug(f"Extracted vehicle names: {self._vehicle_name}") 506 | return await self.async_step_vehicle() 507 | else: 508 | _LOGGER.debug(f"NO VEHICLES FOUND in info {info}") 509 | self._vehicles = None 510 | return await self.async_step_vin() 511 | 512 | @staticmethod 513 | def check_token(token, region_key): 514 | _LOGGER.debug(f"check_token(): selected REGIONS object: {REGIONS[region_key]}") 515 | 516 | redirect_schema = "fordapp" 517 | if "redirect_schema" in REGIONS[region_key]: 518 | redirect_schema = REGIONS[region_key]["redirect_schema"] 519 | 520 | if f"{redirect_schema}://userauthorized/?code=" in token: 521 | return True 522 | return False 523 | 524 | def generate_url(self, region_key): 525 | if region_key not in REGIONS: 526 | _LOGGER.error(f"generate_url(): Invalid region_key: {region_key}") 527 | region_key = DEFAULT_REGION_FORD 528 | _LOGGER.debug(f"generate_url(): selected REGIONS object: {REGIONS[region_key]}") 529 | 530 | cc_object = self.generate_code_challenge() 531 | code_challenge = cc_object["code_challenge"].decode("utf-8") 532 | code_challenge_method = cc_object["code_challenge_method"] 533 | self.code_verifier = cc_object["code_verifier"] 534 | 535 | # LINCOLN 536 | # https://login.lincoln.com/4566605f-43a7-400a-946e-89cc9fdb0bd7/B2C_1A_SignInSignUp_Lincoln_en-US/oauth2/v2.0/authorize?redirect_uri=lincolnapp%3A%2F%2Fuserauthorized&response_type=code&scope=09852200-05fd-41f6-8c21-d36d3497dc64%20openid&max_age=3600&login_hint=eyJyZWFsbSI6ICJjbG91ZElkZW50aXR5UmVhbG0ifQ%3D%3D&code_challenge=K2WtKFhDWmbkkx__9U9b4LhI1z_QvEGb6VvZ1RGX45I&code_challenge_method=S256&client_id=09852200-05fd-41f6-8c21-d36d3497dc64&language_code=en-US&ford_application_id=45133B88-0671-4AAF-B8D1-99E684ED4E45&country_code=USA 537 | 538 | sign_up = "B2C_1A_SignInSignUp_" 539 | if "sign_up_addon" in REGIONS[region_key]: 540 | sign_up = f"{sign_up}{REGIONS[region_key]['sign_up_addon']}" 541 | 542 | redirect_schema = "fordapp" 543 | if "redirect_schema" in REGIONS[region_key]: 544 | redirect_schema = REGIONS[region_key]["redirect_schema"] 545 | 546 | url = f"{REGIONS[region_key]['login_url']}/{OAUTH_ID}/{sign_up}{REGIONS[region_key]['locale']}/oauth2/v2.0/authorize?redirect_uri={redirect_schema}://userauthorized&response_type=code&max_age=3600&code_challenge={code_challenge}&code_challenge_method={code_challenge_method}&scope=%20{CLIENT_ID}%20openid&client_id={CLIENT_ID}&ui_locales={REGIONS[region_key]['locale']}&language_code={REGIONS[region_key]['locale']}&ford_application_id={REGIONS[region_key]['app_id']}&country_code={REGIONS[region_key]['countrycode']}" 547 | return url 548 | 549 | @staticmethod 550 | def valid_number(phone_number): 551 | pattern = re.compile(r'^([+]\d{2})?\d{10}$', re.IGNORECASE) 552 | pattern2 = re.compile(r'^([+]\d{2})?\d{9}$', re.IGNORECASE) 553 | return pattern.match(phone_number) is not None or pattern2.match(phone_number) is not None 554 | 555 | 556 | async def async_step_vin(self, user_input=None): 557 | """Handle manual VIN entry""" 558 | errors = {} 559 | if user_input is not None: 560 | _LOGGER.debug(f"cached_login_input: {self.cached_login_input} vin_input: {user_input}") 561 | 562 | # add the vin to the cached_login_input (so we store this in the config entry) 563 | self.cached_login_input[CONF_VIN] = user_input[CONF_VIN] 564 | vehicle = None 565 | try: 566 | vehicle = await self.validate_vin(self.cached_login_input) 567 | except InvalidVin: 568 | errors["base"] = "invalid_vin" 569 | except Exception: 570 | errors["base"] = "unknown" 571 | 572 | if vehicle: 573 | self.cached_login_input[CONF_IS_SUPPORTED] = True 574 | # create the config entry without the vehicle type/name... 575 | return self.async_create_entry(title=f"VIN: {user_input[CONF_VIN]}", data=self.cached_login_input) 576 | 577 | _LOGGER.debug(f"{self.cached_login_input}") 578 | return self.async_show_form(step_id="vin", data_schema=VIN_SCHEME, errors=errors) 579 | 580 | 581 | async def async_step_vehicle(self, user_input=None): 582 | if user_input is not None: 583 | _LOGGER.debug("Checking Vehicle is accessible") 584 | self.cached_login_input[CONF_VIN] = user_input[CONF_VIN] 585 | _LOGGER.debug(f"{self.cached_login_input}") 586 | 587 | if user_input[CONF_VIN] in self._vehicle_name: 588 | a_title = f"{self._vehicle_name[user_input[CONF_VIN]]} [VIN: {user_input[CONF_VIN]}]" 589 | else: 590 | a_title = f"VIN: {user_input[CONF_VIN]}" 591 | 592 | self.cached_login_input[CONF_IS_SUPPORTED] = True 593 | return self.async_create_entry(title=a_title, data=self.cached_login_input) 594 | 595 | _LOGGER.debug(f"async_step_vehicle(): with vehicles: {self._vehicles}") 596 | 597 | already_configured_vins = self.configured_vehicles(self.hass) 598 | _LOGGER.debug(f"async_step_vehicle(): configured VINs: {already_configured_vins}") 599 | 600 | available_vehicles = {} 601 | for a_vehicle in self._vehicles: 602 | _LOGGER.debug(f"async_step_vehicle(): a vehicle from backend response: {a_vehicle}") 603 | a_veh_vin = a_vehicle["VIN"] 604 | if a_veh_vin not in already_configured_vins: 605 | if a_veh_vin in self._vehicle_name: 606 | available_vehicles[a_veh_vin] = f"{self._vehicle_name[a_veh_vin]} - {a_veh_vin}" 607 | elif "nickName" in a_vehicle: 608 | self._vehicle_name[a_veh_vin] = a_vehicle["nickName"] 609 | available_vehicles[a_veh_vin] = f"{a_vehicle['nickName']} - {a_veh_vin}" 610 | else: 611 | available_vehicles[a_veh_vin] = f"'({a_veh_vin})" 612 | 613 | if not available_vehicles: 614 | _LOGGER.debug("async_step_vehicle(): No Vehicles (or all already configured)?") 615 | return self.async_abort(reason="no_vehicles") 616 | 617 | return self.async_show_form( 618 | step_id="vehicle", 619 | data_schema=vol.Schema( 620 | {vol.Required(CONF_VIN): vol.In(available_vehicles)} 621 | ), 622 | errors={} 623 | ) 624 | 625 | 626 | async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> ConfigFlowResult: 627 | """Handle flow upon an API authentication error.""" 628 | return await self.async_step_reauth_confirm() 629 | 630 | async def async_step_reauth_confirm(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: 631 | """Dialog that informs the user that reauth is required.""" 632 | 633 | errors: dict[str, str] = {} 634 | reauth_entry = self._get_reauth_entry() 635 | assert reauth_entry is not None 636 | 637 | if user_input is not None: 638 | try: 639 | token_fragment = user_input[CONF_TOKEN_STR] 640 | # we should not save our user-captured 'code' url... 641 | del user_input[CONF_TOKEN_STR] 642 | 643 | if self.check_token(token_fragment, reauth_entry.data[CONF_REGION]): 644 | # we don't need our generated URL either... 645 | del user_input[CONF_URL] 646 | 647 | # ok we have already the username and region, this must be stored 648 | # in the config entry, so we can get it from there... 649 | user_input[CONF_REGION] = reauth_entry.data[CONF_REGION] 650 | user_input[CONF_USERNAME] = reauth_entry.data[CONF_USERNAME] 651 | _LOGGER.debug(f"async_step_reauth_token: user_input -> {user_input}") 652 | 653 | info = await self.validate_token_only(user_input, token_fragment, self.code_verifier) 654 | if info: 655 | # do we want to check, if the VIN is still accessible?! 656 | # for now, we just will reload the config entry... 657 | await self.hass.config_entries.async_reload(reauth_entry.entry_id) 658 | return self.async_abort(reason="reauth_successful") 659 | else: 660 | # what we need to do, if user did not re-authenticate successfully? 661 | _LOGGER.warning(f"Re-Authorization failed - fordpass integration can't provide data for VIN: {reauth_entry.data[CONF_VIN]}") 662 | return self.async_abort(reason="reauth_unsuccessful") 663 | else: 664 | errors["base"] = "invalid_token" 665 | 666 | except CannotConnect as ex: 667 | _LOGGER.debug(f"async_step_reauth_token {ex}") 668 | if self._can_not_connect_reason is not None: 669 | errors["base"] = f"cannot_connect - '{self._can_not_connect_reason}'" 670 | else: 671 | errors["base"] = "cannot_connect - UNKNOWN REASON" 672 | 673 | # then we generate again the fordpass-login-url and show it to the 674 | # user... 675 | return self.async_show_form( 676 | step_id="reauth_confirm", 677 | data_schema=vol.Schema({ 678 | vol.Optional(CONF_URL, default=self.generate_url(reauth_entry.data[CONF_REGION])): str, 679 | vol.Required(CONF_TOKEN_STR): str, 680 | }), 681 | errors=errors 682 | ) 683 | 684 | @staticmethod 685 | @callback 686 | def async_get_options_flow(config_entry): 687 | """Get the options' flow for this handler.""" 688 | return FordPassOptionsFlowHandler(config_entry) 689 | 690 | 691 | class FordPassOptionsFlowHandler(OptionsFlow): 692 | def __init__(self, config_entry: config_entries.ConfigEntry): 693 | """Initialize options flow.""" 694 | if len(dict(config_entry.options)) == 0: 695 | self._options = dict(config_entry.data) 696 | else: 697 | self._options = dict(config_entry.options) 698 | 699 | async def async_step_init(self, user_input=None): 700 | if user_input is not None: 701 | return self.async_create_entry(title="", data=user_input) 702 | 703 | options = {vol.Optional(CONF_PRESSURE_UNIT, default=self._options.get(CONF_PRESSURE_UNIT, DEFAULT_PRESSURE_UNIT),): vol.In(PRESSURE_UNITS), 704 | vol.Optional(CONF_FORCE_REMOTE_CLIMATE_CONTROL, default=self._options.get(CONF_FORCE_REMOTE_CLIMATE_CONTROL, False),): bool, 705 | vol.Optional(CONF_LOG_TO_FILESYSTEM, default=self._options.get(CONF_LOG_TO_FILESYSTEM, False),): bool, 706 | vol.Optional(UPDATE_INTERVAL, default=self._options.get(UPDATE_INTERVAL, UPDATE_INTERVAL_DEFAULT),): int} 707 | return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FordPass Home Assistant Integration 2025 (EV/PHEV/Petrol/Diesel) 2 | ## Supporting all Ford vehicles with FordPass™ & Lincoln vehicles with The Lincoln Way™ connectivity 3 | 4 | 9 | 10 | 19 | 20 | 36 | 37 | [![hacs_badge][hacsbadge]][hacs] [![github][ghsbadge]][ghs] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] [![buymecoffee2][buymecoffeebadge2]][buymecoffee2] [![PayPal][paypalbadge]][paypal] [![hainstall][hainstallbadge]][hainstall] 38 | 39 | > [!WARNING] 40 | > ## General Disclaimer 41 | > **This integration is not officially supported by Ford, and as such, using this integration could have unexpected/unwanted results.** 42 | > 43 | > Please be aware that I am developing this integration to the best of my knowledge and belief, but can't give a guarantee. Therefore, use this integration **at your own risk**! [ _I am not affiliated with Ford in any way._] 44 | > 45 | > ## FordPass™/The Lincoln Way™ Account Disclaimer 46 | > **The use of this HA integration could lead to a (temporary) lock of your FordPass™/The Lincoln Way™ account.** 47 | > Since this integration is not officially supported by Ford, using it could result in your being locked out! from your account. 48 | > 49 | > **It's recommended** to use/create a **separate FordPass™/The Lincoln Way™ account** for this integration ([see the 'step-by-step' procedure further below](https://github.com/marq24/ha-fordpass?tab=readme-ov-file#use-of-a-separate-fordpassthe-lincoln-way-account-is-recommended)). 50 | 51 | --- 52 | 53 | > [!NOTE] 54 | > Even if this integration was initially forked from [@itchannel and @SquidBytes](https://github.com/itchannel/fordpass-ha), the current version is a complete rewrite and is __not compatible__ with the __original FordPass integration__. 55 | > 56 | > I have been continuously working on improvements of the integration in the past month, especially on the compatibility with the latest Home Assistant versions and apply clean code standards. I hope this effort will make it much easier for other developers to understand how the communication with the Ford backend works and how to extend/adjust the integration in the future. 57 | > 58 | > This is a __cloud push integration__, which means that the data is pushed from Ford backend systems to Home Assistant via a websocket connection — so you receive data as it changes. __No polling__ (in a certain interval) __is required anymore__. 59 | > 60 | > It would be quite gentle if you could consider supporting the development of this integration by any kind of contribution — TIA 61 | 62 | --- 63 | 64 | > [!NOTE] 65 | > My main motivation comes from the fact that I own a Ford Mustang Mach-E 2023, and I wanted to have a Home Assistant integration that just works with my car. I will focus on the features that are available for electrical vehicles, but of course I will try not to mess up the features for petrol or diesel vehicles. 66 | 67 | 68 | 69 | --- 70 | 71 | > [!IMPORTANT] 72 | > ## Unusual Integration Setup 73 | > Status Quo in spring/summer/end 2025: This integration requires an unusual setup process to be able to access the data of your vehicle. This is because Ford has changed (once again) the access policies to the required backend APIs (and revoked the access to the APIs for individual developers). 74 | > 75 | > The current implementation is based on API calls the original FordPass™/The Lincoln Way™ App (for Android & iOS) performs, and it's some sort of reverse engineered. 76 | > 77 | > This approach implies that when Ford is going to change something in their none-public/undocumented API, it's quite likely that the integration will break instantly. 78 | > 79 | > __It's impossible to predict__ when this will happen, but __I will try__ to keep the integration up-to-date and working __as long as possible__, since I drive a Ford myself. 80 | > 81 | > ## Fetch & Store FordPass™/The Lincoln Way™ Access Token 82 | > During the integration setup, you will be guided through the process to obtain an access token for your vehicle in the context of your FordPass™/The Lincoln Way™ account. 83 | > 84 | > This should be a _one-time process_, and the access token will be stored in a file outside the custom integration directory (This is to prevent the access token from being deleted during updates of the integration itself). As already explaind, I can't give any guarantee that process will work in the future. 85 | > 86 | > The overall setup process is described in short in the [Installation section](https://github.com/marq24/ha-fordpass?tab=readme-ov-file#installation-instructions-3-steps) below, and in detail in the [linked documentation](./doc/OBTAINING_TOKEN.md). 87 | 88 | --- 89 | 90 | ## Requirements 91 | 1. Your car must have the latest onboard modem functionality and have been registered/authorized with the FordPass™/The Lincoln Way™ application.

92 | 2. You need a Home Assistant instance (v2024.12 or higher) with the [HACS](https://hacs.xyz/docs/use/#getting-started-with-hacs) custom integration installed.

93 | 3. You __must have removed any other previously installed FordPass integration from your Home Assistant instance__, especially the original FordPass integration from @itchannel and @SquidBytes, __before you can use this fork of the integration__. [See also the '_incompatibility_' information](https://github.com/marq24/ha-fordpass?tab=readme-ov-file#this-fork-is-not-compatible-with-the-original-fordpass-integration-from-itchannel-and-squidbytes). 94 | 95 | > [!IMPORTANT] 96 | > This is a HACS custom integration — not a Home Assistant Add-on. Don't try to add this repository as an add-on in Home Assistant. 97 | > 98 | > The IMHO simplest way to install this integration is via the two buttons below ('_OPEN HACS REPOSITORY ON MY HA_' and '_ADD INTEGRATION TO MY HA_'). 99 | 100 | 101 | ## Installation Instructions (3 Steps) 102 | ### Step 1. HACS add the Integration 103 | 104 | [![Open your Home Assistant instance and adding repository to HACS.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=marq24&repository=ha-fordpass&category=integration) 105 | 106 | 1. In HA HACS, you need to add a new custom repository (via the 'three dots' menu in the top right corner). 107 | 2. Enter https://github.com/marq24/ha-fordpass as the repository URL (and select the type `Integration`). 108 | 3. After adding the new repository, you can search for `fordpass` in the search bar. 109 | 4. Important there is already a default HACS fordpass integration — Please make sure to select the 'correct' one with the description: _FordPass integration for Home Assistant - support for Ford & Lincoln vehicles [optimized for EV's & EVCC]_. 110 | 5. Install the 'correct' (aka 'this') fordpass integration (v2025.9.0 or higher). 111 | 6. Restart HA. 112 | 113 | ### Step 2. Setup the Integration 114 | 115 | [![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=fordpass) 116 | 117 | 7. After the restart go to `Settings` -> `Devices & Services` area 118 | 8. Add the new integration `FordPass` and follow the instructions:
119 | You will need to provide: 120 | - Your __FordPass™/The Lincoln Way™ Email__/Account 121 | - __Select a FordPass™/The Lincoln Way™ Region__ (that is currently supported by the integration) 122 | 123 | > [!IMPORTANT] 124 | > The region you are going to select __must match__ the region for which you have __registered your FordPass™/The Lincoln Way™__ account. 125 | > 126 | > While for some countries there is a cross-region support in place (like for European countries and North America), there are other regions where an account registered in a specific country __can't__ be used in another region. E.g. an Ford account registered with the Ford domain in Australia (ford.com.au) can not be used with the USA domain (ford.com). 127 | > 128 | > So if your country is not listed in the integration, and you follow the recommendation to register a sperate account to be used with the integration, then [__register this second account at the ford.com domain__](https://www.ford.com/#$userCreateAccount), since this ensures that you can use the Integration with the 'Rest of the World' Region setting. 129 | 130 | ### Step 3. The hard part — the **Token Setup** 131 | The actual token request requires an external browser to get finally the FordPass™/The Lincoln Way™ access token. [Yes this is for sure quite unusual process when setting up a HA integration, but it's the only way to get the token right now] 132 | 133 | Please follow the steps: 134 | 1. Copy the URL listed in the first input field 135 | 2. Open a new/separate browser (with enabled developer tools) and paste the copied URL it into this second browser instance (you might like to use a private/incognito window for this) 136 | 3. In this second browser: Enter your FordPass™/The Lincoln Way™ credentials (again) and press the login button 137 | 4. Watch the developer tools Network-tab till you see the `?code=` request (this request will fail, but this error is not important). This `?code=` request contains the required access token as a URL parameter. 138 | 5. Copy the full `Request-URL` from this `?code=` request from the browser's developer tools and paste it in the HA integration setup Token field [you must copy the complete URL - so ist must start with `fordapp://userauthorized/?code= ... ` (or `lincolnapp://userauthorized/?code= ... `)] 139 | 140 | More details (how to deal with the browser developer tools) to get your token can be found in the [additional 'obtaining token document'](./doc/OBTAINING_TOKEN.md). 141 | 142 | 143 | ## Usage with EVCC 144 | 145 | [All information, how to use this integration as provider for Ford EV data can be found in a separate section in this repository.](./doc/EVCC.md) 146 | 147 | 161 | 162 | 163 | ## Charge Control of your EV 164 | 165 | Charge control of your EV is experimental. The Integration can (try to) start a charging session via the Ford backend, but this will only work with supporting charging stations (wallboxes) - e.g., Ford Charge Station Pro (FCSP), and only if the vehicle is plugged in. If the vehicle is not plugged in, the button will be disabled. 166 | 167 | So do not expect that the charging control will work with all charging stations. Personally, I use [evcc.io to control the charging](https://evcc.io/) of my Ford Mustang Mach-E. 168 | 169 | 170 | ## Use of a separate FordPass™/The Lincoln Way™ account is recommended 171 | 172 | > [!TIP] 173 | > It's recommended to use a separate FordPass™/The Lincoln Way™ account for this integration. This is to prevent any issues with the FordPass™/The Lincoln Way™ account being locked due to the polling of the API. 174 | 175 | Here is a short procedure how to create and connect a second account (for FordPass™): 176 | 177 | 1. Create a new FordPass™ account (via the regular Ford website) with a different email address (and confirm this account by eMail). All this can be done via a regular web browser.
__It's important, that you can access this eMail account from your mobile phone where the FordPass™ App is installed__ (we need this in step 6). 178 | 2. On a mobile Device: Open the FordPass™ app (logged in with your original account), then you can select `Settings` from the main screen (at the bottom there are three options: `Connected Services >`, `Location >` & `Settings >`) 179 | 3. On the next screen select `Vehicle Access` (from the options: `Phone As A Key >`, `Software updates >` & `Vehicle Access >`) 180 | 4. Select `Invite Driver(s) Invite` and then enter the next screen the eMail address of the new account you created in step 1. 181 | 5. Now you can log out with your main account from the FordPass™ app and log-in again with the new account (created in step 1). 182 | 6. Wait till the invitation eMail arrives and accept the invitation with the button at the bottom of eMail.
__This step must be performed on the mobile device where the FordPass™ app is installed!__ (since only on a mobile device with installed FordPass™ you can open the acceptance-link of this eMail) 183 | 7. Finally, you should now have connected your car to the new FordPass™ account. 184 | 8. You can now log out again of the FordPass™ app with your second account and re-login with your original FordPass™ account. 185 | 9. You can double-check with a regular browser, that the car is now accessible with the new account by web. 186 | 187 | If accepting the invitation doesn't work or results in a blank sceeen in the Ford app, try adding the vehicle by VIN to the new account first then accepting the invite. 188 | 189 | ## Multi-Vehicle Support 190 | 191 | When you have __multiple__ vehicles registered in your FordPass™/The Lincoln Way™ account, then in the FordPass™/The Lincoln Way™ App you must first select the vehicle you want to use, before you can access any data or functionality of this vehicle. __The same limitation is also true__ for this Home Assistant integration. 192 | 193 | The main reason for this restriction is the fact, that the FordPass™ App and this integration makes use of a websocket connection to the Ford backend, which is some sort of bound to a single vehicle at a time. 194 | 195 | So you have three options to use multiple vehicles in Home Assistant with this integration: 196 | 1. **Use multiple FordPass™ accounts**: You can create a separate FordPass™ account for each of your vehicles and then add each account as a separate integration in Home Assistant. This way you can use multiple vehicles in Home Assistant that does not have any influence on each other [my personal recommendation]. 197 | 198 | 2. **Use different Regions**: If you have multiple vehicles, you can create for each of the vehicles a separate Region (and create a new access token per Region). 199 | 200 | 3. **Have _only one_ vehicle _active_ in Home Assistant**: If you have multiple vehicles in your FordPass™ account, you can activate only use one of the vehicles at a time in Home Assistant. This means that you must first deactivate the current active vehicle in HA (deactivate the device) and then activate the new vehicle you want to use. This approach is quite similar to the way how FordPass™ App deals with multiple vehicles in your FordPass™ account, but probably that's not what you want. 201 | 202 | 203 | ## Services 204 | 205 | ### Clear Tokens 206 | If you are experiencing any sign in issues, please try clearing your tokens using the "clear_tokens" service call. 207 | 208 | ### Poll API (local refresh) — also available as button in the UI 209 | This service allows you to sync the data of the integration (read via the websocket) with the Ford backends by manually polling all data. This can become Handy if you want to ensure that HA data is in sync with the Ford backend data. 210 | 211 | ### Request Update (remote refresh) — also available as button in the UI 212 | This service will contact the modem in the vehicle and request to sync data between the vehicle and the ford backends. **Please note that this will have an impact on the battery of your vehicle.** 213 | 214 | 215 | > [!Note] 216 | > ### Not every Ford is the same 217 | > Sounds a bit strange — but the EV Explorer or EV Capri (European models) are based on a platform from Volkswagen. As a consequence, not every sensor will be available for these vehicles, since the Ford backend does not provide the corresponding data for these vehicles [e.g. 12V battery, RC seats, or the target charge level]. 218 | 219 | 220 | ## Sensors 221 | **Sensors may change as the integration is being developed** 222 | ~~Supports Multiple Regions~~ 223 | 224 | | Sensor Name | Petrol/Diesel | (P)HEV |  (B)EV  | 225 | |:-----------------------------------|:-------------:|:------:|:-----------------:| 226 | | Odometer | ✔ | ✔ | ✔ | 227 | | Battery (12V) | ✔ | ✔ | ✔ | 228 | | Oil | ✔ | ✔ | ✔ | 229 | | Tire Pressure | ✔ | ✔ | ✔ | 230 | | GPS/Location Data (JSON) | ✔ | ✔ | ✔ | 231 | | Alarm Status | ✔ | ✔ | ✔ | 232 | | Status Ignition | ✔ | ✔ | ✔ | 233 | | Status Door | ✔ | ✔ | ✔ | 234 | | Window Position | ✔ | ✔ | ✔ | 235 | | last refresh (timestamp) | ✔ | ✔ | ✔ | 236 | | Speed | ✔ | ✔ | ✔ | 237 | | Gear Lever Position | ✔ | ✔ | ✔ | 238 | | Indicators/Warnings | ✔ | ✔ | ✔ | 239 | | Temperature Coolant | ✔ | ✔ | ✔ | 240 | | Temperature Outdoors | ✔ | ✔ | ✔ | 241 | | RC: Status Remote Start[^1][^2] | ✔ | ✔ | ✔ | 242 | | RC: Remaining Duration[^1][^2] | ✔ | ✔ | ✔ | 243 | | FordPass Messages | ✔ | ✔ | ✔ | 244 | | Belt Status | ✔ | ✔ | ✔ | 245 | | (Deep)Sleep Mode | ✔ | ✔ | ✔ | 246 | | Revolution / Engine Speed | ✔ | ✔ | ? | 247 | | Fuel Level (can be > 100%) | ✔ | ✔ | | 248 | | Temperature Engine Oil | ✔ | ✔ | | 249 | | Status Diesel System | ✔ | ✔ | | 250 | | AdBlue Level | ✔ | ✔ | | 251 | | EV-Data collection | | ✔ | ✔ | 252 | | EV Plug Status | | ✔ | ✔ | 253 | | EV Charging information | | ✔ | ✔ | 254 | | State of Charge (SOC) | | ? | ✔ | 255 | | EV Energy Consumption (last trip) | | ? | ✔ | 256 | | EV Last Charging Session | | ? | ✔ | 257 | | EVCC status code ('A', 'B' or 'C') | | ? | ✔ | 258 | | Yaw Rate | ✔ | ✔ | ✔ | 259 | | Acceleration (X-Axis | ✔ | ✔ | ✔ | 260 | | Status Brake Pedal | ✔ | ✔ | ✔ | 261 | | Brake Torque | ✔ | ✔ | ✔ | 262 | | Accelerator Pedal Position (%) | ✔ | ✔ | ✔ | 263 | | Status Parking Brake | ✔ | ✔ | ✔ | 264 | | Torque at Transmission | ✔ | ✔ | ✔ | 265 | | Status Wheel Torque | ✔ | ✔ | ✔ | 266 | | Cabin Temperature | ✔ | ✔ | ✔ | 267 | 268 | Many sensors provide more detail information as attributes of sensors. These attributes are available by expanding the panel at the bottom of sensor view (marked by green border). 269 | 270 | ![image](./images/012.png) 271 | 272 | You can find more details about the individual sensors when accessing your HA via `http://[your-ha-ip-here]/developer-tools/state` and then selecting the individual sensor from the dropdown list, then you can see all the attributes of the sensor. 273 | 274 | Based on these attributes you can create your own template sensors or automations in Home Assistant. 275 | 276 | 277 | ## Buttons / Switches / Other 278 | 279 | | Type | Sensor Name | Petrol/Diesel | (P)HEV/BEV | 280 | |:--------------------|:----------------------------------------------------|:-------------:|:----------:| 281 | | Button | Remote Sync (Car with Ford backend) | ✔ | ✔ | 282 | | Button | Local Sync (Ford backend with HA) | ✔ | ✔ | 283 | | Lock | Lock/Unlock Vehicle[^1] | ✔ | ✔ | 284 | | Switch | ~~Guard Mode (Only supported cars)~~ | | | 285 | | Button | Start charging[^4] | | ✔ | 286 | | Switch | PAUSE/UNPAUSE charging[^5] | | ✔ | 287 | | Switch | Auto SoftwareUpdates[^1] | ✔ | ✔ | 288 | | DeviceTracker | Vehicle Tracker[^1] | ✔ | ✔ | 289 | | Select | Zone Lighting (experimental)[^1] | ✔ | ✔ | 290 | | Switch | RC: Start (❄/☀)[^1][^2] | ✔ | ✔ | 291 | | Button | RC: Extend Time[^1][^2] | ✔ | ✔ | 292 | | Select (was Number) | RC: Climate Temperature (❄/☀)[^1][^2] | ✔ | ✔ | 293 | | Switch | RC: Steering Wheel Heating[^1][^2] | ✔ | ✔ | 294 | | Select | RC: Seat (❄/☀) front/rear & left/right[^1][^2][^3] | ✔ | ✔ | 295 | | Switch | RC: Rear Defrost[^1][^2] | ✔ | ✔ | 296 | | Switch | RC: Windshield Heating[^1][^2] | ✔ | ✔ | 297 | | Select | Target charge level(s)[^6] | | ✔ | 298 | 299 | [^1]: Must be supported by the vehicle. If not supported, the entity will not be available in the UI. 300 | [^2]: _RC_ stands for 'Remote Control'. 301 | [^3]: There are four controls — one for each seat. Depending on your vehicle configuration you can select 'Heating Level I-III' and 'Cooling Level I-III' for each seat individually. Please note that not all vehicles support the full set of featured (e.g., only heating) and/or that there might be only the front seats available. 302 | [^4]: The 'Start charging' button will only work with supporting charging stations (wallboxes) - e.g., Ford Charge Station Pro (FCSP), and only if the vehicle is plugged in. If the vehicle is not plugged in, the button will be disabled. 303 | [^5]: Once the charging process has been started, the switch allows you to pause and unpause the charging process. It's not possible to actually start a charging session via this switch — you must use the _EV Start_-button for this! The switch will be toggled to `ON` when the vehicle is plugged in and the wallbox has started to charge the car. When you toggle the switch `OFF`, the charging process will be paused, and when you toggle it `ON` again, the charging process will resume. 304 | [^6]: In FordPass™ App you can create _Target Charge Locations_ — Based on the previous DC charging locations (this functionality was also new for me).
305 | This integration will create up to three select entities — one for the first three of these locations. You can select the target charge level for each of these locations [some sort of strange option list: 50%, 60%, 70%, 80%, 85%, 90%, 95% & 100%]. The target charge level will be used when you start a charging session, e.g., via the _EV Start_-button. 306 | If you don't have any target charge locations configured in FordPass™, then this entity will not be available in Home Assistant.
307 | The entities for the second and third 'charge locations select'-entities are disabled by default, but you can enable them in the integration. 308 | 309 | 310 | ## Want to report an issue? 311 | 312 | Please use the [GitHub Issues](https://github.com/marq24/ha-fordpass/issues) for reporting any issues you encounter with this integration. Please be so kind before creating a new issues, check the closed ones if your problem has been already reported (& solved). 313 | 314 | To speed up the support process, you might like to already prepare and provide DEBUG log output. In the case of a technical issue, I would need this DEBUG log output to be able to help/fix the issue. There is a short [tutorial/guide 'How to provide DEBUG log' here](https://github.com/marq24/ha-senec-v3/blob/main/docs/HA_DEBUG.md) — please take the time to quickly go through it. 315 | 316 | For this integration, you need to add: 317 | ``` 318 | logger: 319 | default: warning 320 | logs: 321 | custom_components.fordpass: debug 322 | ``` 323 | 324 | ### Additional considerations before reporting an issue 325 | 326 | If you miss entities or functionality, please check if there is any data available in the FordPass™/The Lincoln Way™ App. If there is no data available in the FordPass™/The Lincoln Way™ App, then there might be good reasons, why there is no data available for this integration either. Please be aware that not all vehicles support all features, so it's possible that some entities are not available for your vehicle. 327 | 328 | You can enable the __Log API responses to local HA filesystem__ in the integration configuration. This will log all API responses to the local HA filesystem, which can be helpful for any data debugging purposes. The log files will be stored in the `.storage/fordpass/data_dumps` directory of your Home Assistant installation. 329 | 330 | ![image](./images/011.png) 331 | 332 | When you create an issue, please consider: 333 | - the data can contain sensitive information do not post any of the files in the issue. 334 | - you can email me the files directly (please include a link to the GitHub issue). 335 | 336 | 337 | ## I need You! 338 | 339 | In the past month I have asked various Ford owners to support the development of this integration by providing access to their vehicle data. This has helped a lot to improve the integration and to ensure that it works with various Ford models (EV's, PHEV's, Petrol and Diesel vehicles). 340 | 341 | Currently, I do have (IMHO) enough different vehicles to test the integration. If this situation is going to change, I will ask again for your support - typically in the [discussion area of this repository](https://github.com/marq24/ha-fordpass/discussions). 342 | 343 | In the meantime, it would be very kind, if you would consider to support the ongoing development efforty by a [paypal donation][paypal], [buying some coffee][buymecoffee] or become [a GitHub sponsor][ghs], where the last one is my personal favourite. 344 | 345 | 352 | 353 | 354 | ## Supporting the development 355 | If you like this integration and want to support the development, please consider supporting me on [GitHub Sponsors][ghs] or [BuyMeACoffee][buymecoffee] or [PayPal][paypal]. 356 | 357 | [![GitHub Sponsors][ghsbadge]][ghs] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] [![buymecoffee2][buymecoffeebadge2]][buymecoffee2] [![PayPal][paypalbadge]][paypal] 358 | 359 | 360 | > [!WARNING] 361 | > ## This fork is **not compatible** with the original FordPass integration from @itchannel and @SquidBytes 362 | > Before you can use this fork with your vehicle, you must have removed the original FordPass integration from HA and you should delete all configuration entries. Please be aware that it's quite likely that a configuration can be disabled! 363 | > 364 | > When the integration detect a configuration entry that was not generated by this fork you might get additional warnings in your HA log. 365 | > ### Incompatible changes: 366 | > - The VIN has been added to all the entity names, to ensure that names stay unique in HA when you have multiple vehicles. 367 | > - The sensor attribute names do not contain spaces anymore to make post-processing easier. Additionally, all the attribute names are now using camelcase. This means that all attributes start with a lower-case character (don't let you fool by the HA user interface, which always will show the first character as upper-case). 368 | > - The access-token(s) is stored outside the custom integration folder 369 | > 370 | > ### Additional enhancements: 371 | > - This is now a cloud __push__ integration, which means that the data is pushed to Home Assistant via a websocket connection. This is a major improvement over the original integration, which was a cloud __pull__ integration. 372 | > - Additional Sensors for EV/PHEV vehicles 373 | > - Buttons to local/remote refresh data in HA 374 | > - Sensor to provide EVCC-Charging state [see evcc.io website for details](https://evcc.io) 375 | > - Translation of Entity names (DE/EN) 376 | > - Code cleanup and refactoring 377 | 378 | 379 | ## Change Log 380 | [See GitHub releases](https://github.com/marq24/ha-fordpass/releases) of this repository or [for older update information that was not part of this repository a separate document](./info.md) for the complete change log of this integration. 381 | 382 | 383 | ## Credits 384 | - [@crowedavid](https://github.com/crowedavid) — David, who is great support here in the community and has provided a lot of feedback and ideas for improvements. Also, he has provided various HA automations and template sensors for this integration. Thanks a lot for your support David! 385 | - https://github.com/itchannel/fordpass-ha — Original fordpass integration by @itchannel and @SquidBytes 386 | 387 | 388 | ### Credits (of the original integration) 389 | - https://github.com/SquidBytes — EV updates and documentation 390 | - https://github.com/clarkd — Initial Home Assistant automation idea and Python code (Lock/Unlock) 391 | - https://github.com/pinballnewf — Figuring out the application ID issue 392 | - https://github.com/degrashopper — Fixing 401 error for certain installs 393 | - https://github.com/tonesto7 — Extra window statuses and sensors 394 | - https://github.com/JacobWasFramed — Updated unit conversions 395 | - https://github.com/heehoo59 — French Translation 396 | 397 | 398 | [hacs]: https://hacs.xyz 399 | [hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge&logo=homeassistantcommunitystore&logoColor=ccc 400 | 401 | [ghs]: https://github.com/sponsors/marq24 402 | [ghsbadge]: https://img.shields.io/github/sponsors/marq24?style=for-the-badge&logo=github&logoColor=ccc&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fmarq24&label=Sponsors 403 | 404 | [buymecoffee]: https://www.buymeacoffee.com/marquardt24 405 | [buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a-coffee-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc 406 | 407 | [buymecoffee2]: https://buymeacoffee.com/marquardt24/membership 408 | [buymecoffeebadge2]: https://img.shields.io/badge/sponsor%20📅-coffee-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc 409 | 410 | 411 | 412 | [paypal]: https://paypal.me/marq24 413 | [paypalbadge]: https://img.shields.io/badge/paypal-me-blue.svg?style=for-the-badge&logo=paypal&logoColor=ccc 414 | 415 | [hainstall]: https://my.home-assistant.io/redirect/config_flow_start/?domain=fordpass 416 | [hainstallbadge]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&logo=home-assistant&logoColor=ccc&label=usage&suffix=%20installs&cacheSeconds=15600&url=https://analytics.home-assistant.io/custom_integrations.json&query=$.fordpass.total 417 | --------------------------------------------------------------------------------