├── .gitignore ├── .gitattributes ├── example.png ├── kia_uvo.png ├── kia_uvo2.png ├── setup.cfg ├── .devcontainer ├── configuration.yaml ├── devcontainer.json └── README.md ├── hacs.json ├── custom_components └── kia_uvo │ ├── manifest.json │ ├── const.py │ ├── strings.json │ ├── translations │ └── en.json │ ├── services.yaml │ ├── config_flow.py │ ├── binary_sensor.py │ └── __init__.py ├── .github ├── workflows │ ├── cron.yaml │ ├── pull.yml │ ├── push.yml │ └── semantic_release.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── issue.md ├── .vscode └── tasks.json ├── LICENSE ├── info.md ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcomartin/kia_uvo/HEAD/example.png -------------------------------------------------------------------------------- /kia_uvo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcomartin/kia_uvo/HEAD/kia_uvo.png -------------------------------------------------------------------------------- /kia_uvo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcomartin/kia_uvo/HEAD/kia_uvo2.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [semantic_release] 2 | version_variable=custom_components/kia_uvo/const.py:__version__ -------------------------------------------------------------------------------- /.devcontainer/configuration.yaml: -------------------------------------------------------------------------------- 1 | default_config: 2 | logger: 3 | default: error 4 | logs: 5 | custom_components.kia_uvo: debug -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kia Uvo", 3 | "hacs": "0.24.0", 4 | "domains": [ 5 | "binary_sensor" 6 | ], 7 | "iot_class": "Cloud Polling", 8 | "zip_release": true, 9 | "filename": "kia_uvo.zip", 10 | "homeassistant": "0.108.0" 11 | } -------------------------------------------------------------------------------- /custom_components/kia_uvo/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "kia_uvo", 3 | "name": "Kia Uvo", 4 | "config_flow": true, 5 | "documentation": "https://www.home-assistant.io/integrations/kia_uvo", 6 | "requirements": [ 7 | "KiaUvo==0.14" 8 | ], 9 | "dependencies": [], 10 | "codeowners": [ 11 | "@wcomartin" 12 | ] 13 | } -------------------------------------------------------------------------------- /custom_components/kia_uvo/const.py: -------------------------------------------------------------------------------- 1 | """Constants for the Kia Uvo integration.""" 2 | import logging 3 | 4 | __version__ = "0.1.9" 5 | 6 | DOMAIN = "kia_uvo" 7 | LOGGER = logging.getLogger(__package__) 8 | 9 | CONF_VEHICLE_ID = "vehicle_id" 10 | VEHICLE_DATA = "vehicle_data" 11 | VEHICLE_ACCOUNT = "vehicle_account" 12 | VEHICLE_LISTENER = "vehicle_listener" 13 | FORCE_VEHICLE_LISTENER = "force_vehicle_listener" 14 | 15 | TOPIC_UPDATE = f"kiauvo_update_{0}" 16 | -------------------------------------------------------------------------------- /.github/workflows/cron.yaml: -------------------------------------------------------------------------------- 1 | name: Cron actions 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | validate: 9 | runs-on: "ubuntu-latest" 10 | name: Validate 11 | steps: 12 | - uses: "actions/checkout@v2" 13 | 14 | - name: HACS validation 15 | uses: "hacs/integration/action@master" 16 | with: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | CATEGORY: "integration" 19 | 20 | - name: Hassfest validation 21 | uses: "home-assistant/actions/hassfest@master" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/workflows/pull.yml: -------------------------------------------------------------------------------- 1 | name: Pull actions 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | validate: 8 | runs-on: "ubuntu-latest" 9 | name: Validate 10 | steps: 11 | - uses: "actions/checkout@v2" 12 | 13 | - name: HACS validation 14 | uses: "hacs/integration/action@master" 15 | with: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | CATEGORY: "integration" 18 | 19 | - name: Hassfest validation 20 | uses: "home-assistant/actions/hassfest@master" 21 | 22 | style: 23 | runs-on: "ubuntu-latest" 24 | name: Check style formatting 25 | steps: 26 | - uses: "actions/checkout@v2" 27 | - uses: "actions/setup-python@v1" 28 | with: 29 | python-version: "3.x" 30 | - run: python3 -m pip install black 31 | - run: black . -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Push actions 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | name: Validate 13 | steps: 14 | - uses: "actions/checkout@v2" 15 | 16 | - name: HACS validation 17 | uses: "hacs/integration/action@master" 18 | with: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | CATEGORY: "integration" 21 | 22 | - name: Hassfest validation 23 | uses: "home-assistant/actions/hassfest@master" 24 | 25 | style: 26 | runs-on: "ubuntu-latest" 27 | name: Check style formatting 28 | steps: 29 | - uses: "actions/checkout@v2" 30 | - uses: "actions/setup-python@v1" 31 | with: 32 | python-version: "3.x" 33 | - run: python3 -m pip install black 34 | - run: black . -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run Home Assistant on port 9123", 6 | "type": "shell", 7 | "command": "container start", 8 | "problemMatcher": [] 9 | }, 10 | { 11 | "label": "Run Home Assistant configuration against /config", 12 | "type": "shell", 13 | "command": "container check", 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "label": "Upgrade Home Assistant to latest dev", 18 | "type": "shell", 19 | "command": "container install", 20 | "problemMatcher": [] 21 | }, 22 | { 23 | "label": "Install a spesific version of Home Assistant", 24 | "type": "shell", 25 | "command": "container set-version", 26 | "problemMatcher": [] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/vscode-remote/devcontainer.json for format details. 2 | { 3 | "image": "ludeeus/container:integration", 4 | "context": "..", 5 | "appPort": [ 6 | "9123:8123" 7 | ], 8 | "postCreateCommand": "container install", 9 | "runArgs": [ 10 | "-v", 11 | "${env:HOME}${env:USERPROFILE}/.ssh:/tmp/.ssh" 12 | ], 13 | "extensions": [ 14 | "ms-python.python", 15 | "github.vscode-pull-request-github", 16 | "tabnine.tabnine-vscode" 17 | ], 18 | "settings": { 19 | "files.eol": "\n", 20 | "editor.tabSize": 4, 21 | "terminal.integrated.shell.linux": "/bin/bash", 22 | "python.pythonPath": "/usr/bin/python3", 23 | "python.linting.pylintEnabled": true, 24 | "python.linting.enabled": true, 25 | "python.formatting.provider": "black", 26 | "editor.formatOnPaste": false, 27 | "editor.formatOnSave": true, 28 | "editor.formatOnType": true, 29 | "files.trimTrailingWhitespace": true 30 | } 31 | } -------------------------------------------------------------------------------- /custom_components/kia_uvo/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Kia Uvo", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "description": "Set up your Kia to integrate with Home Assistant.", 7 | "data": { 8 | "username": "Username", 9 | "password": "Password" 10 | }, 11 | "title": "Kia Uvo: Authentication" 12 | }, 13 | "vehicle": { 14 | "description": "Please choose the vehicle.", 15 | "data": { 16 | "vehicle_id": "Vehicle", 17 | "pin": "Pin" 18 | }, 19 | "title": "Kia Uvo: Choose Vehicle" 20 | } 21 | }, 22 | "error": { 23 | "cannot_connect": "Failed to connect, please try again", 24 | "invalid_auth": "Invalid authentication", 25 | "invalid_pin": "Invalid Pin", 26 | "unknown": "Unexpected error" 27 | }, 28 | "abort": { 29 | "already_configured": "Device is already configured" 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /custom_components/kia_uvo/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Kia Uvo", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "description": "Set up your Kia to integrate with Home Assistant.", 7 | "data": { 8 | "username": "Username", 9 | "password": "Password" 10 | }, 11 | "title": "Kia Uvo: Authentication" 12 | }, 13 | "vehicle": { 14 | "description": "Please choose the vehicle.", 15 | "data": { 16 | "vehicle_id": "Vehicle", 17 | "pin": "Pin" 18 | }, 19 | "title": "Kia Uvo: Choose Vehicle" 20 | } 21 | }, 22 | "error": { 23 | "cannot_connect": "Failed to connect, please try again", 24 | "invalid_auth": "Invalid authentication", 25 | "invalid_pin": "Invalid Pin", 26 | "unknown": "Unexpected error" 27 | }, 28 | "abort": { 29 | "already_configured": "Device is already configured" 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | 16 | 17 | ## Version of the custom_component 18 | 21 | 22 | ## Configuration 23 | 24 | ```yaml 25 | 26 | Add your logs here. 27 | 28 | ``` 29 | 30 | ## Describe the bug 31 | A clear and concise description of what the bug is. 32 | 33 | 34 | ## Debug log 35 | 36 | 37 | 38 | ```text 39 | 40 | Add your logs here. 41 | 42 | ``` -------------------------------------------------------------------------------- /custom_components/kia_uvo/services.yaml: -------------------------------------------------------------------------------- 1 | lock: 2 | description: Lock the vehicle 3 | fields: 4 | vehicle_id: 5 | description: ID of the vehicle to lock 6 | example: "" 7 | 8 | unlock: 9 | description: Lock the vehicle 10 | fields: 11 | vehicle_id: 12 | description: ID of the vehicle to lock 13 | example: "" 14 | 15 | start_engine: 16 | description: Start the vehicle 17 | fields: 18 | vehicle_id: 19 | description: ID of the vehicle to start 20 | example: "" 21 | preset: 22 | description: The engine start preset you would like to use 23 | example: "Summer" 24 | 25 | stop_engine: 26 | description: Stop the vehicle 27 | fields: 28 | vehicle_id: 29 | description: ID of the vehicle to stop 30 | example: "" 31 | 32 | force_update_vehicle: 33 | description: Force Kia to fetch data from vehicle. This has a limited usages in a given time currently unknown, the integration is currently set to do this automatically every hour. 34 | fields: 35 | vehicle_id: 36 | description: ID of the vehicle to fetch data from 37 | example: "" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joakim Sørensen @ludeeus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/semantic_release.yml: -------------------------------------------------------------------------------- 1 | name: semantic_release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | with: 15 | ref: master 16 | - name: Set up Python 3.7 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: 3.7 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install python-semantic-release 24 | - name: Setup Git 25 | run: | 26 | git config --global user.name "semantic-release" 27 | git config --global user.email "semantic-release@GitHub" 28 | - name: Publish with semantic-release 29 | env: 30 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 31 | run: | 32 | DEBUG='*' semantic-release publish 33 | # Pack the kia_uvo dir as a zip and upload to the release 34 | - name: Zip kia_uvo dir 35 | run: | 36 | cd /home/runner/work/kia_uvo/kia_uvo/custom_components/kia_uvo 37 | zip kia_uvo.zip -r ./ 38 | - name: Set release variable 39 | run: | 40 | echo "::set-env name=release_version::`git describe --abbrev=0`" 41 | - name: Sleep 42 | # add delay so upload does not kill the release notes from semantic-release 43 | run: | 44 | sleep 5 45 | - name: Upload zip to release 46 | uses: svenstaro/upload-release-action@v1-release 47 | with: 48 | repo_token: ${{ secrets.GH_TOKEN }} 49 | file: /home/runner/work/kia_uvo/kia_uvo/custom_components/kia_uvo/kia_uvo.zip 50 | asset_name: kia_uvo.zip 51 | tag: ${{ env.release_version }} 52 | overwrite: true 53 | -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | [![GitHub Release][releases-shield]][releases] 2 | [![GitHub Activity][commits-shield]][commits] 3 | ![License][license-shield] 4 | 5 | [![hacs][hacsbadge]][hacs] 6 | [![Project Maintenance][maintenance-shield]][maintenance-homepage] 7 | [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] 8 | 9 | _Component to integrate with [kia uvo][kia_uvo]._ 10 | 11 | Allows the user to see the last known state of the door sensors, locks and has a entity containing all the data returned from kia, It also exposes services to lock/unlock the vehicle 12 | 13 | **This component will set up the following platforms.** 14 | 15 | Platform | Description 16 | -- | -- 17 | `binary_sensor` | Show something `True` or `False`. 18 | 19 | ![kia_uvo][kiauvoimg] 20 | 21 | {% if not installed %} 22 | ## Installation 23 | 24 | 1. Click install. 25 | 1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Kia Uvo". 26 | 27 | {% endif %} 28 | 29 | 30 | ## Configuration is done in the UI 31 | 32 | 33 | 34 | *** 35 | 36 | [kia_uvo]: https://github.com/wcomartin/kia_uvo 37 | [maintenance-homepage]: http://williamcomartin.com 38 | [buymecoffee]: https://www.buymeacoffee.com/wcomartin 39 | [buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge 40 | [commits-shield]: https://img.shields.io/github/commit-activity/y/wcomartin/kia_uvo.svg?style=for-the-badge 41 | [commits]: https://github.com/wcomartin/kia_uvo/commits/master 42 | [hacs]: https://github.com/custom-components/hacs 43 | [hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge 44 | [kiauvoimg]: https://upload.wikimedia.org/wikipedia/en/0/0d/Kia_UVO_Logo.png 45 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge 46 | [forum]: https://community.home-assistant.io/ 47 | [license-shield]: https://img.shields.io/github/license/wcomartin/kia_uvo.svg?style=for-the-badge 48 | [maintenance-shield]: https://img.shields.io/badge/maintainer-William%20Comartin-blue.svg?style=for-the-badge 49 | [releases-shield]: https://img.shields.io/github/release/wcomartin/kia_uvo.svg?style=for-the-badge 50 | [releases]: https://github.com/wcomartin/kia_uvo/releases -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | Contributing to this project should be as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | 10 | ## Github is used for everything 11 | 12 | Github is used to host code, to track issues and feature requests, as well as accept pull requests. 13 | 14 | Pull requests are the best way to propose changes to the codebase. 15 | 16 | 1. Fork the repo and create your branch from `master`. 17 | 2. If you've changed something, update the documentation. 18 | 3. Make sure your code lints (using black). 19 | 4. Test you contribution. 20 | 5. Issue that pull request! 21 | 22 | ## Any contributions you make will be under the MIT Software License 23 | 24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 | ## Report bugs using Github's [issues](../../issues) 27 | 28 | GitHub issues are used to track public bugs. 29 | Report a bug by [opening a new issue](../../issues/new/choose); it's that easy! 30 | 31 | ## Write bug reports with detail, background, and sample code 32 | 33 | **Great Bug Reports** tend to have: 34 | 35 | - A quick summary and/or background 36 | - Steps to reproduce 37 | - Be specific! 38 | - Give sample code if you can. 39 | - What you expected would happen 40 | - What actually happens 41 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 42 | 43 | People *love* thorough bug reports. I'm not even kidding. 44 | 45 | ## Use a Consistent Coding Style 46 | 47 | Use [black](https://github.com/ambv/black) to make sure the code follows the style. 48 | 49 | ## Test your code modification 50 | 51 | This custom component is based on [blueprint template](https://github.com/custom-components/blueprint). 52 | 53 | It comes with development environement in a container, easy to launch 54 | if you use Visual Studio Code. With this container you will have a stand alone 55 | Home Assistant instance running and already configured with the included 56 | [`.devcontainer/configuration.yaml`](https://github.com/oncleben31/ha-pool_pump/blob/master/.devcontainer/configuration.yaml) 57 | file. 58 | 59 | ## License 60 | 61 | By contributing, you agree that your contributions will be licensed under its MIT License. 62 | -------------------------------------------------------------------------------- /.devcontainer/README.md: -------------------------------------------------------------------------------- 1 | ## Developing with Visual Studio Code + devcontainer 2 | 3 | The easiest way to get started with custom integration development is to use Visual Studio Code with devcontainers. This approach will create a preconfigured development environment with all the tools you need. 4 | 5 | In the container you will have a dedicated Home Assistant core instance running with your custom compnent code. You can configure this instance by updating the `./devcontainer/configuration.yaml` file. 6 | 7 | **Prerequisites** 8 | 9 | - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 10 | - Docker 11 | - For Linux, macOS, or Windows 10 Pro/Enterprise/Education use the [current release version of Docker](https://docs.docker.com/install/) 12 | - Windows 10 Home requires [WSL 2](https://docs.microsoft.com/windows/wsl/wsl2-install) and the current Edge version of Docker Desktop (see instructions [here](https://docs.docker.com/docker-for-windows/wsl-tech-preview/)). This can also be used for Windows Pro/Enterprise/Education. 13 | - [Visual Studio code](https://code.visualstudio.com/) 14 | - [Remote - Containers (VSC Extension)][extension-link] 15 | 16 | [More info about requirements and devcontainer in general](https://code.visualstudio.com/docs/remote/containers#_getting-started) 17 | 18 | [extension-link]: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers 19 | 20 | **Getting started:** 21 | 22 | 1. Fork the repository. 23 | 2. Clone the repository to your computer. 24 | 3. Open the repository using Visual Studio code. 25 | 26 | When you open this repository with Visual Studio code you are asked to "Reopen in Container", this will start the build of the container. 27 | 28 | _If you don't see this notification, open the command palette and select `Remote-Containers: Reopen Folder in Container`._ 29 | 30 | ### Tasks 31 | 32 | The devcontainter comes with some useful tasks to help you with development, you can start these tasks by opening the command palette and select `Tasks: Run Task` then select the task you want to run. 33 | 34 | When a task is currently running (like `Run Home Assistant on port 9123` for the docs), it can be restarted by opening the command palette and selecting `Tasks: Restart Running Task`, then select the task you want to restart. 35 | 36 | The available tasks are: 37 | 38 | Task | Description 39 | -- | -- 40 | Run Home Assistant on port 9123 | Launch Home assistant with your custom compnent code and the configuration defined in `.devcontainer/configuration.yaml`. 41 | Run Home Assistant configuration against /config | Check the configuration. 42 | Upgrade Home Assistant to latest dev | Upgrade the Home Assitant core version in the container to the latest version of the `dev` branch. 43 | Install a spesific version of Home Assistant | Install a specific version of Home Assistant core in the container. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kia Uvo 2 | 3 | [![GitHub Release][releases-shield]][releases] 4 | [![GitHub Activity][commits-shield]][commits] 5 | [![License][license-shield]](LICENSE) 6 | 7 | [![hacs][hacsbadge]][hacs] 8 | [![Project Maintenance][maintenance-shield]][maintenance-homepage] 9 | [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] 10 | 11 | [![Lokalise][lokalise-shield]][lokalise] 12 | 13 | 14 | # Work in Progress: Currently only works in Canada 15 | 16 | 17 | _Component to integrate with [kia uvo][kia_uvo]._ 18 | 19 | Allows the user to see the last known state of the door sensors, locks and has a entity containing all the data returned from kia, It also exposes services to lock/unlock the vehicle 20 | 21 | **This component will set up the following platforms.** 22 | 23 | Platform | Description 24 | -- | -- 25 | `binary_sensor` | Show something `True` or `False`. 26 | 27 | ![kia_uvo][kiauvoimg] 28 | 29 | ## Installation 30 | 31 | 1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). 32 | 2. If you do not have a `custom_components` directory (folder) there, you need to create it. 33 | 3. In the `custom_components` directory (folder) create a new folder called `kia_uvo`. 34 | 4. Download _all_ the files from the `custom_components/kia_uvo/` directory (folder) in this repository. 35 | 5. Place the files you downloaded in the new directory (folder) you created. 36 | 6. Restart Home Assistant 37 | 7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Kia Uvo" 38 | 39 | Using your HA configuration directory (folder) as a starting point you should now also have this: 40 | 41 | ```text 42 | custom_components/kia_uvo/translations/en.json 43 | custom_components/kia_uvo/__init__.py 44 | custom_components/kia_uvo/binary_sensor.py 45 | custom_components/kia_uvo/config_flow.py 46 | custom_components/kia_uvo/const.py 47 | custom_components/kia_uvo/manifest.json 48 | custom_components/kia_uvo/strings.json 49 | custom_components/kia_uvo/services.yaml 50 | ``` 51 | 52 | ## Configuration is done in the UI 53 | 54 | 55 | 56 | ## Contributions are welcome! 57 | 58 | If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md) 59 | 60 | *** 61 | 62 | [kia_uvo]: https://github.com/wcomartin/kia_uvo 63 | [maintenance-homepage]: http://williamcomartin.com 64 | [buymecoffee]: https://www.buymeacoffee.com/wcomartin 65 | [buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge 66 | [commits-shield]: https://img.shields.io/github/commit-activity/y/wcomartin/kia_uvo.svg?style=for-the-badge 67 | [commits]: https://github.com/wcomartin/kia_uvo/commits/master 68 | [hacs]: https://github.com/custom-components/hacs 69 | [hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge 70 | [kiauvoimg]: https://upload.wikimedia.org/wikipedia/en/0/0d/Kia_UVO_Logo.png 71 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge 72 | [forum]: https://community.home-assistant.io/ 73 | [license-shield]: https://img.shields.io/github/license/wcomartin/kia_uvo.svg?style=for-the-badge 74 | [maintenance-shield]: https://img.shields.io/badge/maintainer-William%20Comartin-blue.svg?style=for-the-badge 75 | [releases-shield]: https://img.shields.io/github/release/wcomartin/kia_uvo.svg?style=for-the-badge 76 | [releases]: https://github.com/wcomartin/kia_uvo/releases 77 | [lokalise]: https://app.lokalise.com/project/952864945eb373f8910863.60939812 78 | [lokalise-shield]: https://img.shields.io/badge/Lokalise-Kia%20Uvo-green.svg?style=for-the-badge 79 | -------------------------------------------------------------------------------- /custom_components/kia_uvo/config_flow.py: -------------------------------------------------------------------------------- 1 | """Config flow for Kia Uvo integration.""" 2 | from typing import Optional 3 | import logging 4 | 5 | import voluptuous as vol 6 | 7 | from homeassistant import core, config_entries, exceptions 8 | from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_PIN 9 | from homeassistant.helpers.typing import ConfigType 10 | 11 | from .const import DOMAIN, CONF_VEHICLE_ID 12 | 13 | from KiaUvo import KiaUvo, InvalidAuthException, NoVehicleException 14 | 15 | _LOGGER = logging.getLogger(__name__) 16 | 17 | 18 | class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 19 | """Handle a config flow for Kia Uvo.""" 20 | 21 | VERSION = 1 22 | CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL 23 | 24 | def __init__(self): 25 | """Initialize flow.""" 26 | self.auth_info = None 27 | self.vehicle_info = None 28 | self.kia = None 29 | self.vehicles = [] 30 | 31 | async def async_step_user(self, user_input: Optional[ConfigType] = None): 32 | """Handle the initial step.""" 33 | errors = {} 34 | if user_input is not None: 35 | try: 36 | self.auth_info = await self.validate_authentication(user_input) 37 | return await self.async_step_vehicle() 38 | except CannotConnect: 39 | errors["base"] = "cannot_connect" 40 | except InvalidAuth: 41 | errors["base"] = "invalid_auth" 42 | except Exception: # pylint: disable=broad-except 43 | _LOGGER.exception("Unexpected exception") 44 | errors["base"] = "unknown" 45 | 46 | return self.async_show_form( 47 | step_id="user", 48 | data_schema=vol.Schema({ 49 | vol.Required(CONF_USERNAME): str, 50 | vol.Required(CONF_PASSWORD): str 51 | }), 52 | errors=errors, 53 | ) 54 | 55 | async def async_step_vehicle(self, user_input=None): 56 | 57 | errors = {} 58 | if user_input is not None: 59 | try: 60 | self.vehicle_info = await self.validate_vehicle(user_input) 61 | return await self.async_create() 62 | except InvalidPin: 63 | errors["base"] = "invalid_pin" 64 | except Exception: # pylint: disable=broad-except 65 | _LOGGER.exception("Unexpected exception") 66 | errors["base"] = "unknown" 67 | 68 | self.vehicles = await self.hass.async_add_executor_job(self.kia.get_vehicle_list) 69 | vehicle_names = [x.vehicle["nickName"] for x in self.vehicles] 70 | 71 | return self.async_show_form( 72 | step_id="vehicle", 73 | data_schema=vol.Schema({ 74 | vol.Required(CONF_VEHICLE_ID): vol.In(vehicle_names), 75 | vol.Required(CONF_PIN): str 76 | }), 77 | errors=errors 78 | ) 79 | 80 | async def validate_authentication(self, user_input): 81 | """Validate the user input allows us to connect. 82 | 83 | Data has the keys from DATA_SCHEMA with values provided by the user. 84 | """ 85 | self.kia = KiaUvo(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) 86 | await self.hass.async_add_executor_job(self.kia.login) 87 | 88 | try: 89 | await self.hass.async_add_executor_job(self.kia.verify_token) 90 | except InvalidAuthException: 91 | raise InvalidAuth 92 | 93 | # Return some info we want to store in the config entry. 94 | return user_input 95 | 96 | async def validate_vehicle(self, user_input): 97 | 98 | vehicle = next((x for x in self.vehicles if x.vehicle["nickName"] == user_input[CONF_VEHICLE_ID]), None) 99 | 100 | return {CONF_VEHICLE_ID: vehicle.vehicle["vehicleId"], CONF_PIN: user_input[CONF_PIN], "vehicle": vehicle} 101 | 102 | async def async_create(self): 103 | await self.async_set_unique_id(self.vehicle_info[CONF_VEHICLE_ID]) 104 | self._abort_if_unique_id_configured() 105 | data = { 106 | CONF_USERNAME: self.auth_info[CONF_USERNAME], 107 | CONF_PASSWORD: self.auth_info[CONF_PASSWORD], 108 | CONF_VEHICLE_ID: self.vehicle_info[CONF_VEHICLE_ID], 109 | CONF_PIN: self.vehicle_info[CONF_PIN], 110 | } 111 | title = f'Kia {self.vehicle_info["vehicle"].vehicle["modelName"]} ({self.vehicle_info["vehicle"].vehicle["nickName"]})' 112 | return self.async_create_entry(title=title, data=data) 113 | 114 | class CannotConnect(exceptions.HomeAssistantError): 115 | """Error to indicate we cannot connect.""" 116 | 117 | 118 | class InvalidAuth(exceptions.HomeAssistantError): 119 | """Error to indicate there is invalid auth.""" 120 | 121 | class InvalidPin(exceptions.HomeAssistantError): 122 | """Error to indicate the pin was incorrect""" 123 | -------------------------------------------------------------------------------- /custom_components/kia_uvo/binary_sensor.py: -------------------------------------------------------------------------------- 1 | from . import KiaUvoEntity 2 | from .const import DOMAIN, VEHICLE_DATA, VEHICLE_ACCOUNT 3 | 4 | VEHICLE_DOORS = [ 5 | ("hood", "Hood", "mdi:car"), 6 | ("trunk", "Trunk", "mdi:car-back"), 7 | ("front_left", "Front Driver", "mdi:car-door"), 8 | ("front_right", "Front Passenger", "mdi:car-door"), 9 | ("back_left", "Rear Driver", "mdi:car-door"), 10 | ("back_right", "Rear Passenger", "mdi:car-door") 11 | ] 12 | 13 | 14 | async def async_setup_entry(hass, config_entry, async_add_entities): 15 | vehicle_data = hass.data[DOMAIN][config_entry.entry_id][VEHICLE_ACCOUNT].vehicle_data 16 | 17 | sensors = [ 18 | DoorSensor(hass, config_entry, vehicle_data, door, name, icon) for door, name, icon in VEHICLE_DOORS 19 | ] 20 | 21 | async_add_entities(sensors, True) 22 | async_add_entities([LockSensor(hass, config_entry, vehicle_data)], True) 23 | async_add_entities([EngineSensor(hass, config_entry, vehicle_data)], True) 24 | async_add_entities([VehicleEntity(hass, config_entry, vehicle_data)], True) 25 | 26 | 27 | class DoorSensor(KiaUvoEntity): 28 | 29 | def __init__(self, hass, config_entry, vehicle_data, door, name, icon): 30 | super().__init__(hass, config_entry, vehicle_data) 31 | self._door = door 32 | self._name = name 33 | self._icon = icon 34 | 35 | @property 36 | def icon(self): 37 | """Return the icon.""" 38 | return "mdi:door-open" if self.is_on else "mdi:door-closed" 39 | return self._icon 40 | 41 | @property 42 | def is_on(self) -> bool: 43 | return getattr(self._vehicle_data, f'{self._door}_open') 44 | 45 | @property 46 | def state(self): 47 | return "on" if getattr(self._vehicle_data, f'{self._door}_open') else "off" 48 | 49 | @property 50 | def state_attributes(self): 51 | return { 52 | "last_updated": self._vehicle_data.last_updated 53 | } 54 | 55 | @property 56 | def device_class(self): 57 | """Return the device class.""" 58 | return "door" 59 | 60 | @property 61 | def name(self): 62 | return f'{self._vehicle_data.vehicle["nickName"]} {self._name}' 63 | 64 | @property 65 | def unique_id(self): 66 | return f'kiauvo-{self._door}-{self._vehicle_data.vehicle["vehicleId"]}' 67 | 68 | 69 | 70 | class LockSensor(KiaUvoEntity): 71 | 72 | def __init__(self, hass, config_entry, vehicle_data): 73 | super().__init__(hass, config_entry, vehicle_data) 74 | 75 | @property 76 | def icon(self): 77 | """Return the icon.""" 78 | return "mdi:lock" if self.is_on else "mdi:lock-open-variant" 79 | 80 | @property 81 | def is_on(self) -> bool: 82 | return self._vehicle_data.door_lock 83 | 84 | @property 85 | def state(self): 86 | return "off" if self._vehicle_data.door_lock else "on" 87 | 88 | @property 89 | def state_attributes(self): 90 | return { 91 | "last_updated": self._vehicle_data.last_updated 92 | } 93 | 94 | @property 95 | def device_class(self): 96 | """Return the device class.""" 97 | return "lock" 98 | 99 | @property 100 | def name(self): 101 | return f'{self._vehicle_data.vehicle["nickName"]} Door Lock' 102 | 103 | @property 104 | def unique_id(self): 105 | return f'kiauvo-door-lock-{self._vehicle_data.vehicle["vehicleId"]}' 106 | 107 | 108 | 109 | class EngineSensor(KiaUvoEntity): 110 | 111 | def __init__(self, hass, config_entry, vehicle_data): 112 | super().__init__(hass, config_entry, vehicle_data) 113 | 114 | @property 115 | def icon(self): 116 | """Return the icon.""" 117 | return "mdi:engine" if self.is_on else "mdi:engine-off" 118 | 119 | @property 120 | def is_on(self) -> bool: 121 | return self._vehicle_data.engine 122 | 123 | @property 124 | def state(self): 125 | return "on" if self._vehicle_data.engine else "off" 126 | 127 | @property 128 | def state_attributes(self): 129 | return { 130 | "last_updated": self._vehicle_data.last_updated, 131 | "presets": self._vehicle_data.engine_start_presets 132 | } 133 | 134 | @property 135 | def device_class(self): 136 | """Return the device class.""" 137 | return "power" 138 | 139 | @property 140 | def name(self): 141 | return f'{self._vehicle_data.vehicle["nickName"]} Engine' 142 | 143 | @property 144 | def unique_id(self): 145 | return f'kiauvo-engine-{self._vehicle_data.vehicle["vehicleId"]}' 146 | 147 | 148 | 149 | class VehicleEntity(KiaUvoEntity): 150 | def __init__(self, hass, config_entry, vehicle_data): 151 | super().__init__(hass, config_entry, vehicle_data) 152 | 153 | @property 154 | def state(self): 155 | return "on" 156 | 157 | @property 158 | def is_on(self) -> bool: 159 | return True 160 | 161 | @property 162 | def state_attributes(self): 163 | return { 164 | "vehicle": self._vehicle_data.vehicle, 165 | "status": self._vehicle_data.status, 166 | "maintenance": self._vehicle_data.maintenance 167 | } 168 | 169 | @property 170 | def name(self): 171 | return f'{self._vehicle_data.vehicle["nickName"]} Data' 172 | 173 | @property 174 | def unique_id(self): 175 | return f'kiauvo-all-data-{self._vehicle_data.vehicle["vehicleId"]}' 176 | 177 | 178 | -------------------------------------------------------------------------------- /custom_components/kia_uvo/__init__.py: -------------------------------------------------------------------------------- 1 | """The Kia Uvo integration.""" 2 | import asyncio 3 | from datetime import timedelta 4 | 5 | import voluptuous as vol 6 | 7 | from homeassistant import bootstrap 8 | from homeassistant.core import HomeAssistant, callback 9 | from homeassistant.config_entries import ConfigEntry 10 | from homeassistant.helpers.entity import Entity 11 | from homeassistant.helpers.dispatcher import ( 12 | async_dispatcher_connect, 13 | async_dispatcher_send, 14 | ) 15 | from homeassistant.helpers.event import async_track_time_interval 16 | from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_PIN 17 | 18 | from .const import DOMAIN, CONF_VEHICLE_ID, VEHICLE_ACCOUNT, VEHICLE_LISTENER, FORCE_VEHICLE_LISTENER, LOGGER, TOPIC_UPDATE 19 | 20 | from KiaUvo import KiaUvo, InvalidAuthException, NoVehicleException 21 | 22 | # TODO List the platforms that you want to support. 23 | # For your initial PR, limit it to 1 platform. 24 | PLATFORMS = ["binary_sensor"] 25 | 26 | DEFAULT_SCAN_INTERVAL = timedelta(minutes=1) 27 | FORCE_SCAN_INTERVAL = timedelta(minutes=60) 28 | 29 | CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) 30 | 31 | 32 | async def async_setup(hass: HomeAssistant, config): 33 | """Set up the Kia Uvo component.""" 34 | if DOMAIN not in hass.data: 35 | hass.data[DOMAIN] = {} 36 | 37 | # async def async_handle_start_engine(call): 38 | # """Handle the service call 'start_engine'""" 39 | # vehicle_id = call.data.get("vehicle_id") 40 | # preset = call.data.get("preset") 41 | # print(vehicle_id) 42 | # print(preset) 43 | # 44 | # account = next( 45 | # entry[VEHICLE_ACCOUNT] 46 | # for entry in hass.data[DOMAIN].values() 47 | # if entry[VEHICLE_ACCOUNT].data[CONF_VEHICLE_ID] == vehicle_id) 48 | # 49 | # await hass.async_add_executor_job(account.kia.login) 50 | # await hass.async_add_executor_job(account.kia.verify_token) 51 | # await hass.async_add_executor_job(account.kia.lock_vehicle) 52 | # 53 | # async def async_handle_stop_engine(call): 54 | # """Handle the service call 'stop_engine'""" 55 | # vehicle_id = call.data.get("vehicle_id") 56 | # print(vehicle_id) 57 | # 58 | # account = next( 59 | # entry[VEHICLE_ACCOUNT] 60 | # for entry in hass.data[DOMAIN].values() 61 | # if entry[VEHICLE_ACCOUNT].data[CONF_VEHICLE_ID] == vehicle_id) 62 | # 63 | # await hass.async_add_executor_job(account.kia.login) 64 | # await hass.async_add_executor_job(account.kia.verify_token) 65 | # await hass.async_add_executor_job(account.kia.stop_vehicle) 66 | 67 | async def async_handle_lock(call): 68 | """Handle the service call 'lock'""" 69 | vehicle_id = call.data.get("vehicle_id") 70 | print(vehicle_id) 71 | 72 | account = next( 73 | entry[VEHICLE_ACCOUNT] 74 | for entry in hass.data[DOMAIN].values() 75 | if entry[VEHICLE_ACCOUNT].data[CONF_VEHICLE_ID] == vehicle_id) 76 | 77 | await hass.async_add_executor_job(account.kia.login) 78 | await hass.async_add_executor_job(account.kia.verify_token) 79 | await hass.async_add_executor_job(account.kia.lock_vehicle) 80 | 81 | async def async_handle_unlock(call): 82 | """Handle the service call 'unlock'""" 83 | vehicle_id = call.data.get("vehicle_id") 84 | print(vehicle_id) 85 | 86 | account = next( 87 | entry[VEHICLE_ACCOUNT] 88 | for entry in hass.data[DOMAIN].values() 89 | if entry[VEHICLE_ACCOUNT].data[CONF_VEHICLE_ID] == vehicle_id) 90 | 91 | await hass.async_add_executor_job(account.kia.login) 92 | await hass.async_add_executor_job(account.kia.verify_token) 93 | await hass.async_add_executor_job(account.kia.unlock_vehicle) 94 | 95 | async def async_handle_force_update_vehicle(call): 96 | """Handle the service call 'unlock'""" 97 | vehicle_id = call.data.get("vehicle_id") 98 | print(vehicle_id) 99 | 100 | account = next( 101 | entry[VEHICLE_ACCOUNT] 102 | for entry in hass.data[DOMAIN].values() 103 | if entry[VEHICLE_ACCOUNT].data[CONF_VEHICLE_ID] == vehicle_id) 104 | 105 | await account.async_force_vehicle_update() 106 | 107 | # hass.services.async_register(DOMAIN, "start_engine", async_handle_start_engine) 108 | # hass.services.async_register(DOMAIN, "stop_engine", async_handle_stop_engine) 109 | # 110 | hass.services.async_register(DOMAIN, "lock", async_handle_lock) 111 | hass.services.async_register(DOMAIN, "unlock", async_handle_unlock) 112 | hass.services.async_register(DOMAIN, "force_update_vehicle", async_handle_force_update_vehicle) 113 | return True 114 | 115 | 116 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 117 | """Set up Kia Uvo from a config entry.""" 118 | 119 | data = { 120 | VEHICLE_ACCOUNT: KiaUvoData(hass, entry), 121 | VEHICLE_LISTENER: {}, 122 | FORCE_VEHICLE_LISTENER: {} 123 | } 124 | 125 | await data[VEHICLE_ACCOUNT].async_update() 126 | 127 | for component in PLATFORMS: 128 | hass.async_create_task( 129 | hass.config_entries.async_forward_entry_setup(entry, component) 130 | ) 131 | 132 | async def refresh(event_time): 133 | """Refresh data from Kia UVO.""" 134 | await data[VEHICLE_ACCOUNT].async_update() 135 | 136 | async def force_vehicle_update(event_time): 137 | """Send request to vehicle for update""" 138 | await data[VEHICLE_ACCOUNT].async_force_vehicle_update() 139 | 140 | data[VEHICLE_LISTENER] = async_track_time_interval( 141 | hass, refresh, DEFAULT_SCAN_INTERVAL 142 | ) 143 | 144 | data[FORCE_VEHICLE_LISTENER] = async_track_time_interval( 145 | hass, force_vehicle_update, FORCE_SCAN_INTERVAL 146 | ) 147 | 148 | hass.data[DOMAIN][entry.entry_id] = data 149 | 150 | return True 151 | 152 | 153 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): 154 | """Unload a config entry.""" 155 | unload_ok = all( 156 | await asyncio.gather( 157 | *[ 158 | hass.config_entries.async_forward_entry_unload(entry, component) 159 | for component in PLATFORMS 160 | ] 161 | ) 162 | ) 163 | if unload_ok: 164 | hass.data[DOMAIN].pop(entry.entry_id) 165 | 166 | return unload_ok 167 | 168 | 169 | class KiaUvoEntity(Entity): 170 | 171 | def __init__(self, hass, config_entry, vehicle_data): 172 | self._hass = hass 173 | self._config_entry = config_entry 174 | self.topic_update = TOPIC_UPDATE.format(config_entry.data[CONF_VEHICLE_ID]) 175 | self._vehicle_data = vehicle_data 176 | 177 | async def async_added_to_hass(self): 178 | """Register callbacks.""" 179 | 180 | @callback 181 | def update(): 182 | """Update the state.""" 183 | self.update_from_latest_data() 184 | self.async_write_ha_state() 185 | 186 | self.async_on_remove( 187 | async_dispatcher_connect(self._hass, self.topic_update, update) 188 | ) 189 | 190 | self.update_from_latest_data() 191 | 192 | @property 193 | def available(self) -> bool: 194 | return not not self._vehicle_data 195 | 196 | @property 197 | def device_info(self): 198 | return { 199 | "identifiers": { 200 | # Serial numbers are unique identifiers within a specific domain 201 | (DOMAIN, self._vehicle_data.vehicle["vehicleId"]) 202 | }, 203 | "name": self._vehicle_data.vehicle["nickName"], 204 | "manufacturer": "Kia", 205 | "model": self._vehicle_data.vehicle["modelName"], 206 | "sw_version": self._vehicle_data.vehicle["modelYear"], 207 | "via_device": (DOMAIN, self._vehicle_data.vehicle["vehicleId"]), 208 | } 209 | 210 | @callback 211 | def update_from_latest_data(self): 212 | """Update the entity from the latest data.""" 213 | self._vehicle_data = self._hass.data[DOMAIN][self._config_entry.entry_id][VEHICLE_ACCOUNT].vehicle_data 214 | 215 | 216 | class KiaUvoData: 217 | 218 | def __init__(self, hass, config_entry): 219 | """Initialize""" 220 | self._hass = hass 221 | self._config_entry = config_entry 222 | self.data = config_entry.data 223 | self.topic_update = TOPIC_UPDATE.format(config_entry.data[CONF_VEHICLE_ID]) 224 | self.kia = KiaUvo(self.data[CONF_USERNAME], self.data[CONF_PASSWORD]) 225 | self.kia.select_vehicle(self.data[CONF_VEHICLE_ID], self.data[CONF_PIN]) 226 | self.vehicle_data = {} 227 | 228 | async def async_update(self): 229 | try: 230 | await self._hass.async_add_executor_job(self.kia.login) 231 | await self._hass.async_add_executor_job(self.kia.verify_token) 232 | 233 | self.vehicle_data = await self._hass.async_add_executor_job(self.kia.get_vehicle_status) 234 | except Exception: 235 | self.vehicle_data = {} 236 | 237 | LOGGER.debug("Received new vehicle data") 238 | async_dispatcher_send(self._hass, self.topic_update) 239 | 240 | async def async_force_vehicle_update(self): 241 | try: 242 | await self._hass.async_add_executor_job(self.kia.login) 243 | await self._hass.async_add_executor_job(self.kia.verify_token) 244 | 245 | # Force update from vehicle 246 | await self._hass.async_add_executor_job(self.kia.request_vehicle_update) 247 | 248 | self.vehicle_data = await self._hass.async_add_executor_job(self.kia.get_vehicle_status) 249 | except Exception: 250 | self.vehicle_data = {} 251 | 252 | --------------------------------------------------------------------------------