├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md └── workflows │ └── stale.yml ├── .gitignore ├── LICENSE ├── README.md ├── custom_components └── skodaconnect │ ├── __init__.py │ ├── binary_sensor.py │ ├── climate.py │ ├── config_flow.py │ ├── const.py │ ├── device_tracker.py │ ├── lock.py │ ├── manifest.json │ ├── sensor.py │ ├── services.yaml │ ├── strings.json │ ├── switch.py │ └── translations │ ├── de.json │ ├── en.json │ ├── it.json │ ├── nb.json │ ├── nl.json │ ├── nn.json │ ├── pl.json │ ├── ru.json │ ├── sk.json │ └── sv.json ├── hacs.json ├── info.md └── requirements.txt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **IMPORTANT NOTE** 11 | 12 | This integration currently only supports the old Skoda API compatible with the old Skoda Essentials App. 13 | 14 | If you are unable to use the Skoda Essentials account the integration will not work for you. This is the case for new vehicles and/or Skoda accounts. 15 | 16 | We are aware the Skoda Essentials app is being deprecated and are working on updating the integration to be compatible with the new API / MySkoda app, but this is a major piece of development work. 17 | 18 | Please do not open new issues for this. 19 | 20 | **Please search issues in https://github.com/skodaconnect/skodaconnect and https://github.com/skodaconnect/homeassistant-skodaconnect for duplicates or fixes before reporting a new issue.** 21 | 22 | **Describe the bug** 23 | A clear and concise description of what the bug is. 24 | 25 | **Is this working in MySkoda app?*** 26 | Logout and login in the MySkoda app, preferably on Android. 27 | Make sure that you have the latest app updates, sometimes there's new consent needed that will only show in latest app version. 28 | 29 | **Latest working release** 30 | If the problem started after updating to a new release, please state latest working release. 31 | 32 | **Debug logs** 33 | If applicable, add debug logs. See [README](https://github.com/skodaconnect/homeassistant-skodaconnect#enable-debug-logging) on how to enable debug logs with response debugging. 34 | 35 | **Installation:** 36 | - Home Assistant variant: [e.g. Hass OS, Docker, Core, Supervised] 37 | - Python version [e.g. 3.7, 3.8] 38 | - Python library versions ["python3 -m pip list"] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue 3 | about: When no other template fits. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **IMPORTANT NOTE** 11 | 12 | This integration currently only supports the old Skoda API compatible with the old Skoda Essentials App. 13 | 14 | If you are unable to use the Skoda Essentials account the integration will not work for you. This is the case for new vehicles and/or Skoda accounts. 15 | 16 | We are aware the Skoda Essentials app is being deprecated and are working on updating the integration to be compatible with the new API / MySkoda app, but this is a major piece of development work. 17 | 18 | Please do not open new issues for this. 19 | 20 | **Please search issues in https://github.com/skodaconnect/skodaconnect and https://github.com/skodaconnect/homeassistant-skodaconnect for duplicates or fixes before reporting a new issue.** 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | **IMPORTANT NOTE** 10 | 11 | This integration currently only supports the old Skoda API compatible with the old Skoda Essentials App. 12 | 13 | If you are unable to use the Skoda Essentials account the integration will not work for you. This is the case for new vehicles and/or Skoda accounts. 14 | 15 | We are aware the Skoda Essentials app is being deprecated and are working on updating the integration to be compatible with the new API / MySkoda app, but this is a major piece of development work. 16 | 17 | Please do not open new issues for this. 18 | 19 | **Please search issues in https://github.com/skodaconnect/skodaconnect and https://github.com/skodaconnect/homeassistant-skodaconnect for duplicates or fixes before reporting a new issue.** 20 | 21 | **Is your feature request related to a problem? Please describe.** 22 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 23 | 24 | **Describe the solution you'd like** 25 | A clear and concise description of what you want to happen. 26 | 27 | **Describe alternatives you've considered** 28 | A clear and concise description of any alternative solutions or features you've considered. 29 | 30 | **Additional context** 31 | Add any other context or screenshots about the feature request here. 32 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '26 3 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v5 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | stale-issue-message: > 25 | There hasn't been any activity on this issue recently. Due to the 26 | high number of incoming GitHub notifications, we have to clean some 27 | of the old issues, as many of them have already been resolved with 28 | the latest updates. 29 | 30 | Please make sure to update to the latest version and check if that 31 | solves the issue. Let us know if that works for you by adding a 32 | comment 👍 33 | 34 | This issue has now been marked as stale and will be closed if no 35 | further activity occurs. Thank you for your contributions. 36 | 37 | stale-pr-message: > 38 | There hasn't been any activity on this pull request recently. This 39 | pull request has been automatically marked as stale because of that 40 | and will be closed if no further activity occurs within 7 days. 41 | 42 | If you are the author of this PR, please leave a comment if you want 43 | to keep it open. Also, please rebase your PR onto the latest dev 44 | branch to ensure that it's up to date with the latest changes. 45 | 46 | Thank you for your contribution! 47 | 48 | stale-issue-label: 'stale' 49 | stale-pr-label: 'stale' 50 | exempt-pr-labels: "no-stale" 51 | exempt-issue-labels: "no-stale, enhancement" 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | custom_components/skodaconnect/__pycache__ 2 | custom_components/.DS_Store 3 | .DS_Store 4 | .venv/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Version](https://img.shields.io/github/v/release/skodaconnect/homeassistant-skodaconnect?include_prereleases) 2 | ![PyPi](https://img.shields.io/pypi/v/skodaconnect?label=latest%20library) 3 | ![Downloads](https://img.shields.io/github/downloads/skodaconnect/homeassistant-skodaconnect/total) 4 | 5 | # **DEPRECATION NOTICE** 6 | 7 | This integration only supports the 'old' Skoda API (associated with the old mobile app called _Skoda Essentials_) and will stop working when Skoda shuts down the old API. This is announced to be on December 2nd 2024 but that date has shifted already in the past. 8 | 9 | If you are unable to use the Skoda Essentials app then this integration will **not** work for you. This is reported to be the case for newer Skoda accounts. 10 | 11 | Development on this integration has essentially completely halted and it will not be updated to support the new app/API. 12 | 13 | A new integration is being developed to work with the 'new' Skoda API (associated with the new mobile app called _MySkoda_): https://github.com/skodaconnect/homeassistant-myskoda 14 | 15 | The new integration is actively developed and only a few models are (partially) supported at this time. Still we highly encourage everyone to try out the new integration and provide feedback through Github issues or [Discord](https://discord.gg/826X9jEtCh). 16 | 17 | # Skoda Connect - A Home Assistant custom component for Skoda Connect/MyŠKODA 18 | 19 | If you are new to Home Assistant, please read the [Home Assistant documentation](https://www.home-assistant.io/docs/) first. 20 | 21 | This is fork of [robinostlund/homeassistant-volkswagencarnet](https://github.com/robinostlund/homeassistant-volkswagencarnet) modified to support Skoda Connect/MySkoda through native app API (API calls directly to vwg-connect services) 22 | 23 | This integration for Home Assistant will fetch data from Skoda Connect servers related to your Skoda Connect enabled car. 24 | Skoda Connect never fetch data directly from car, the car sends updated data to VAG servers on specific events such as lock/unlock, charging events, climatisation events and when vehicle is parked. The integration will then fetch this data from the servers. 25 | When vehicle actions fails or return with no response, a force refresh might help. This will trigger a "wake up" call from VAG servers to the car. 26 | The scan_interval is how often the integration should fetch data from the servers, if there's no new data from the car then entities won't be updated. 27 | 28 | This project contains the Home Assistant custom component code. It depends on https://github.com/skodaconnect/skodaconnect which provides the Python library interacting with the Skoda API. 29 | 30 | ### Supported setups 31 | 32 | This integration will only work for your car if you have Skoda Connect/MyŠKODA functionality. Cars using other third party, semi-official, mobile apps such as the "MinSkoda" from ConnectedCars in Denmark won't work. 33 | The library used for API communication is reverse engineered from the MySkoda Android app. 34 | Initial support has been added for SmartLink and newer style API cars, such as the Enyaq iV. 35 | 36 | The car privacy settings must be set to "Share my position" for full functionality of this integration. Without this setting, if set to "Use my position", the sensors for position (device tracker), requests remaining and parking time might not work reliably or at all. Set to even stricter privacy setting will limit functionality even further. 37 | 38 | ### If you encounter problems 39 | 40 | If you encounter a problem where the integration can't be setup or if you receive an error that there's unaccepted terms or EULA, it might be because of your mobile app platform. 41 | The underlying library is built by reverse engineering the Android App behavior and thus it use the same client configurations as an Android device. If you only use the app on iPhone/iOS devices it might cause issues with this integration. 42 | 43 | Possible workarounds: 44 | - Log in using the **old** "MySkoda Essentials" iOS/Android app (see #198). It should present an EULA or new terms and conditions to be accepted. 45 | - Log in through a web browser (https://www.skoda-connect.com/) 46 | 47 | If this does not work for you and the particular problem you are facing, please open an issue and provide as detailed problem description as possible and relevant debug logs. 48 | 49 | ### What is working, all cars 50 | 51 | - Automatic discovery of enabled functions (API endpoints). 52 | - Charging plug connected 53 | - Charging plug locked 54 | - Charger connected (external power) 55 | - Battery level 56 | - Electric range 57 | - Model image, URL in 1080 resolution 58 | - Start/stop charging 59 | - Start/stop Electric climatisation, window_heater and information 60 | - Charger maximum current 61 | (1-16 tested OK for Superb iV, Enyaq limited to "Maximum"/"Reduced") 62 | - Set departure timers (switch on/off and service call to set parameters) 63 | - Odometer and service info 64 | - Lock, windows, trunk, hood, sunroof and door status 65 | - Position - gps coordinates, if vehicle is moving, time parked 66 | - Device tracker - entity is set to 'not_home' when car is moving 67 | 68 | ### Additional information/functions VW-Group API ("All" Skodas except Enyaq iV so far) 69 | 70 | - Fuel level, range, adblue level 71 | - Last trip info 72 | - start/stop auxiliary climatisation for PHEV cars 73 | - Lock and unlock car 74 | - Parking heater heating/ventilation (for non-electric cars) 75 | - Requests information - latest status, requests remaining until throttled 76 | - Trigger data refresh - this will trigger a wake up call so the car sends new data 77 | 78 | ### Additional information/functions Skoda native API (Enyaq iV so far) 79 | 80 | - Charging power (Watts) 81 | - Charging rate (km per hour) 82 | - Charging time left (minutes) 83 | - Seat heating (???) 84 | 85 | ### Under development and BETA functionality (may be buggy) 86 | 87 | - Config flow multiple vehicles from same account 88 | - SmartLink: 89 | - Fuel level 90 | - Odometer 91 | - Service inspection distance 92 | - Service inspection time 93 | - Oil service distance 94 | - Oil service time 95 | - Skoda Enyaq iV: 96 | - Start/stop charging (switch) 97 | - Set charger max current (service call) 98 | - Set charge limit (service call) 99 | 100 | ### What is NOT working 101 | 102 | - Switches doesn't immediately update "request results" and "request_in_progress". Long running requests will not show up until completed which might take up to 3-5 minutes. 103 | - Config flow convert from yaml config 104 | 105 | ### Breaking changes 106 | 107 | - Combustion heater/ventilation is now named parking heater so it's not mixed up with aux heater for PHEV 108 | - Many resources have changed names to avoid confusion in the code, some have changed from sensor to switch and vice versa. Sensors with trailing "_km" in the name has been renamed to "_distance" for better compatibility between imperial and non-imperial units. 109 | - Major code changes has been made for requests handling. 110 | - request_in_progress is now a binary sensor instead of a switch 111 | - force_data_refresh is a new switch with the same functionality as "request_in_progress" previously, it will force refresh data from car 112 | 113 | ## Installation 114 | 115 | ### Install with HACS (recomended) 116 | 117 | If you have HACS (Home Assistant Community Store) installed, just search for Skoda Connect and install it direct from HACS. 118 | HACS will keep track of updates and you can easly upgrade to the latest version when a new release is available. 119 | 120 | If you don't have it installed, check it out here: [HACS](https://community.home-assistant.io/t/custom-component-hacs) 121 | 122 | ### Manual installation 123 | 124 | Clone or copy the repository and copy the folder 'homeassistant-skodaconnect/custom_component/skodaconnect' into '[config dir]/custom_components'. 125 | Install required python library with (adjust to suit your environment): 126 | 127 | ```sh 128 | pip install skodaconnect 129 | ``` 130 | 131 | ## Enable debug logging 132 | 133 | For comprehensive debug logging you can add this to your `/configuration.yaml`: 134 | 135 | ```yaml 136 | logger: 137 | default: info 138 | logs: 139 | skodaconnect.connection: debug 140 | skodaconnect.vehicle: debug 141 | custom_components.skodaconnect: debug 142 | custom_components.skodaconnect.climate: debug 143 | custom_components.skodaconnect.lock: debug 144 | custom_components.skodaconnect.device_tracker: debug 145 | custom_components.skodaconnect.switch: debug 146 | custom_components.skodaconnect.binary_sensor: debug 147 | custom_components.skodaconnect.sensor: debug 148 | ``` 149 | 150 | - **skodaconnect.connection:** Set the debug level for the Connection class of the Skoda Connect library. This handles the GET/SET requests towards the API 151 | 152 | - **skodaconnect.vehicle:** Set the debug level for the Vehicle class of the Skoda Connect library. One object created for every vehicle in account and stores all data. 153 | 154 | - **skodaconnect.dashboard:** Set the debug level for the Dashboard class of the Skoda Connect library. A wrapper class between hass component and library. 155 | 156 | - **custom_components.skodaconnect:** Set debug level for the custom component. The communication between hass and library. 157 | 158 | - **custom_components.skodaconnect.XYZ** Sets debug level for individual entity types in the custom component. 159 | 160 | ## Configuration 161 | 162 | Configuration in configuration.yaml is now deprecated and can interfere with setup of the integration. 163 | To configure the integration, go to Configuration in the side panel of Home Assistant and then select Integrations. 164 | Click on the "ADD INTEGRATION" button in the bottom right corner and search/select skodaconnect. 165 | Follow the steps and enter the required information. Because of how the data is stored and handled in Home Assistant, there will be one integration per vehicle. 166 | Setup multiple vehicles by adding the integration multiple times. 167 | 168 | ### Configuration options 169 | 170 | The integration options can be changed after setup by clicking on the "CONFIGURE" text on the integration. 171 | The options available are: 172 | 173 | - **Poll frequency** The interval (in seconds) that the servers are polled for updated data. Several users have reported being rate limited (HTTP 429) when using 60s or lower. It is recommended to start with a value of 120s or 180s. See [#215](https://github.com/skodaconnect/homeassistant-skodaconnect/issues/215). 174 | 175 | - **S-PIN** The S-PIN for the vehicle. This is optional and is only needed for certain vehicle requests/actions (auxiliary heater, lock etc). 176 | 177 | - **Mutable** Select to allow interactions with vehicle, start climatisation etc. 178 | 179 | - **Full API debug logging** Enable full debug logging. This will print the full respones from API to homeassistant.log. Only enable for troubleshooting since it will generate a lot of logs. 180 | 181 | - **Resources to monitor** Select which resources you wish to monitor for the vehicle. 182 | 183 | - **Distance/unit conversions** Select if you want to convert distance/units. 184 | 185 | ## Automations 186 | 187 | In this example we are sending notifications to an ios device. The Android companion app does not currently support dynamic content in notifications (maps etc.) 188 | 189 | Save these automations in your automations file `/automations.yaml` 190 | 191 | ### Use input_select to change pre-heater duration 192 | 193 | First create a input_number, via editor in configuration.yaml or other included config file, or via GUI Helpers editor. 194 | It is important to set minimum value to 10, maximum to 60 and step to 10. Otherwise the service call might not work. 195 | 196 | ```yaml 197 | # configuration.yaml 198 | input_number: 199 | pheater_duration: 200 | name: "Pre-heater duration" 201 | initial: 20 202 | min: 10 203 | max: 60 204 | step: 10 205 | unit_of_measurement: min 206 | ``` 207 | 208 | Create the automation, in yaml or via GUI editor. You can find the device id by going to Settings->Devices & Services and then clicking on the device for the Skodaconnect vehicle. The device ID will show in the web browser address bar after "/config/devices/device/". The ID can also be found by using the GUI services editor under developer tools. Choose one of the skodaconnect services and choose your vehicle. Change to YAML mode and copy the device ID. 209 | 210 | ```yaml 211 | # automations.yaml 212 | - alias: Pre-heater duration 213 | description: "" 214 | trigger: 215 | - platform: state 216 | entity_id: input_number.pheater_duration 217 | condition: [] 218 | action: 219 | - service: skodaconnect.set_pheater_duration 220 | data_template: 221 | device_id: 222 | duration: "{{ (states('input_number.pheater_duration') | float ) | round(0) }}" 223 | mode: single 224 | ``` 225 | 226 | ### Charge rate guesstimate 227 | 228 | Thanks to @haraldpaulsen 229 | An estimated charge rate template sensor based on battery capacity and reported time left. 230 | Replace with the name of your vehicle and with battery capacity in Wh. 231 | 232 | ```yaml 233 | template: 234 | - sensor: 235 | - name: "Charge speed guesstimate" 236 | state: > 237 | {% if is_state('switch.skoda__charging', 'on') %} 238 | {% set battery_capacity = | int %} 239 | {% set charge = { "remaining": states('sensor.skoda__minimum_charge_level') | int - states('sensor.skoda__battery_level') | int } %} 240 | {% set timeleft = states('sensor.skoda__charging_time_left') | int %} 241 | {% set chargeleft = battery_capacity * charge.remaining / 100 %} 242 | {% set chargespeed = chargeleft / (timeleft / 60) %} 243 | {{ chargespeed | round (1) }} 244 | {% else %} 245 | 0 246 | {% endif %} 247 | unit_of_measurement: "kW" 248 | state_class: measurement 249 | ``` 250 | 251 | ### Get notification when your car is on a new place and show a map with start position and end position 252 | 253 | Note: only available for iOS devices since Android companion app does not support this yet. 254 | This might be broken since 1.0.30 when device_tracker changed behaviour. 255 | 256 | ```yaml 257 | - id: notify_skoda_position_change 258 | description: Notify when position has been changed 259 | alias: Skoda position changed notification 260 | trigger: 261 | - platform: state 262 | entity_id: device_tracker.kodiaq 263 | condition: template 264 | condition: template 265 | value_template: "{{ trigger.to_state.state != trigger.from_state.state }}" 266 | action: 267 | - service: notify.ios_my_ios_device 268 | data_template: 269 | title: "Kodiaq Position Changed" 270 | message: | 271 | 🚗 Skoda Car is now on a new place. 272 | data: 273 | url: /lovelace/car 274 | apns_headers: 275 | 'apns-collapse-id': 'car_position_state_{{ trigger.entity_id.split(".")[1] }}' 276 | push: 277 | category: map 278 | thread-id: "HA Car Status" 279 | action_data: 280 | latitude: "{{trigger.from_state.attributes.latitude}}" 281 | longitude: "{{trigger.from_state.attributes.longitude}}" 282 | second_latitude: "{{trigger.to_state.attributes.latitude}}" 283 | second_longitude: "{{trigger.to_state.attributes.longitude}}" 284 | shows_traffic: true 285 | ``` 286 | 287 | ### Announce when your car is unlocked but no one is home 288 | 289 | ```yaml 290 | - id: 'notify_skoda_car_is_unlocked' 291 | alias: Skoda is at home and unlocked 292 | trigger: 293 | - entity_id: binary_sensor.vw_carid_external_power 294 | platform: state 295 | to: 'on' 296 | for: 00:10:00 297 | condition: 298 | - condition: state 299 | entity_id: lock.kodiaq_door_locked 300 | state: unlocked 301 | - condition: state 302 | entity_id: device_tracker.kodiaq 303 | state: home 304 | - condition: time 305 | after: '07:00:00' 306 | before: '21:00:00' 307 | action: 308 | # Notification via push message to smartphone 309 | - service: notify.device 310 | data: 311 | message: "The car is unlocked!" 312 | target: 313 | - device/my_device 314 | # Notification via smart speaker (kitchen) 315 | - service: media_player.volume_set 316 | data: 317 | entity_id: media_player.kitchen 318 | volume_level: '0.6' 319 | - service: tts.google_translate_say 320 | data: 321 | entity_id: media_player.kitchen 322 | message: "My Lord, the car is unlocked. Please attend this this issue at your earliest inconvenience!" 323 | ``` 324 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Skoda Connect integration 4 | 5 | Read more at https://github.com/skodaconnect/homeassistant-skodaconnect/ 6 | """ 7 | import re 8 | import asyncio 9 | import logging 10 | from datetime import datetime, timedelta 11 | from typing import Union 12 | import voluptuous as vol 13 | 14 | from homeassistant.config_entries import ConfigEntry, SOURCE_REAUTH, SOURCE_IMPORT 15 | from homeassistant.const import ( 16 | CONF_NAME, 17 | CONF_PASSWORD, 18 | CONF_RESOURCES, 19 | CONF_SCAN_INTERVAL, 20 | CONF_USERNAME, 21 | EVENT_HOMEASSISTANT_STOP, 22 | ) 23 | from homeassistant.core import HomeAssistant 24 | from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady 25 | from homeassistant.helpers import config_validation as cv, device_registry 26 | from homeassistant.helpers.aiohttp_client import async_get_clientsession 27 | from homeassistant.helpers.dispatcher import async_dispatcher_connect 28 | from homeassistant.helpers.entity import Entity 29 | from homeassistant.helpers.icon import icon_for_battery_level 30 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed 31 | 32 | from skodaconnect import Connection 33 | from skodaconnect.vehicle import Vehicle 34 | from skodaconnect.exceptions import ( 35 | SkodaConfigException, 36 | SkodaAuthenticationException, 37 | SkodaAccountLockedException, 38 | SkodaTokenExpiredException, 39 | SkodaTokenInvalidException, 40 | SkodaException, 41 | SkodaEULAException, 42 | SkodaThrottledException, 43 | SkodaLoginFailedException, 44 | SkodaInvalidRequestException, 45 | SkodaRequestInProgressException 46 | ) 47 | 48 | from .const import ( 49 | PLATFORMS, 50 | CONF_MUTABLE, 51 | CONF_SCANDINAVIAN_MILES, 52 | CONF_SPIN, 53 | CONF_VEHICLE, 54 | CONF_TOKENS, 55 | CONF_INSTRUMENTS, 56 | CONF_DEBUG, 57 | CONF_CONVERT, 58 | CONF_NO_CONVERSION, 59 | CONF_IMPERIAL_UNITS, 60 | CONF_SAVESESSION, 61 | DATA, 62 | DATA_KEY, 63 | MIN_SCAN_INTERVAL, 64 | DEFAULT_SCAN_INTERVAL, 65 | DEFAULT_DEBUG, 66 | DOMAIN, 67 | SIGNAL_STATE_UPDATED, 68 | UNDO_UPDATE_LISTENER, 69 | REMOVE_LISTENER, 70 | UPDATE_CALLBACK, 71 | SERVICE_SET_SCHEDULE, 72 | SERVICE_SET_MAX_CURRENT, 73 | SERVICE_SET_CHARGE_LIMIT, 74 | SERVICE_SET_CLIMATER, 75 | SERVICE_SET_PHEATER_DURATION, 76 | ) 77 | 78 | SERVICE_SET_SCHEDULE_SCHEMA = vol.Schema( 79 | { 80 | vol.Required("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), 81 | vol.Required("id"): vol.In([1,2,3]), 82 | vol.Required("time"): cv.string, 83 | vol.Required("enabled"): cv.boolean, 84 | vol.Required("recurring"): cv.boolean, 85 | vol.Optional("date"): cv.string, 86 | vol.Optional("days"): cv.string, 87 | vol.Optional("temp"): vol.All(vol.Coerce(float), vol.Range(min=16, max=30)), 88 | vol.Optional("climatisation"): cv.boolean, 89 | vol.Optional("charging"): cv.boolean, 90 | vol.Optional("charge_current"): vol.Any( 91 | vol.Range(min=1, max=254), 92 | vol.In(['Maximum', 'maximum', 'Max', 'max', 'Minimum', 'minimum', 'Min', 'min', 'Reduced', 'reduced']) 93 | ), 94 | vol.Optional("charge_target"): vol.In([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]), 95 | vol.Optional("heater_source"): cv.boolean, 96 | vol.Optional("spin"): vol.All(cv.string, vol.Match(r"^[0-9]{4}$")), 97 | vol.Optional("off_peak_active"): cv.boolean, 98 | vol.Optional("off_peak_start"): cv.string, 99 | vol.Optional("off_peak_end"): cv.string, 100 | } 101 | ) 102 | SERVICE_SET_MAX_CURRENT_SCHEMA = vol.Schema( 103 | { 104 | vol.Required("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), 105 | vol.Required("current"): vol.Any( 106 | vol.Range(min=1, max=255), 107 | vol.In(['Maximum', 'maximum', 'Max', 'max', 'Minimum', 'minimum', 'Min', 'min', 'Reduced', 'reduced']) 108 | ), 109 | } 110 | ) 111 | SERVICE_SET_CHARGE_LIMIT_SCHEMA = vol.Schema( 112 | { 113 | vol.Required("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), 114 | vol.Required("limit"): vol.In([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]), 115 | } 116 | ) 117 | SERVICE_SET_CLIMATER_SCHEMA = vol.Schema( 118 | { 119 | vol.Required("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), 120 | vol.Required("enabled", default=True): cv.boolean, 121 | vol.Optional("temp"): vol.All(vol.Coerce(float), vol.Range(min=16, max=30)), 122 | vol.Optional("battery_power"): cv.boolean, 123 | vol.Optional("aux_heater"): cv.boolean, 124 | vol.Optional("spin"): vol.All(cv.string, vol.Match(r"^[0-9]{4}$")) 125 | } 126 | ) 127 | SERVICE_SET_PHEATER_DURATION_SCHEMA = vol.Schema( 128 | { 129 | vol.Required("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), 130 | vol.Required("duration"): vol.In([10, 20, 30, 40, 50, 60]), 131 | } 132 | ) 133 | 134 | # Set max parallel updates to 2 simultaneous (1 poll and 1 request waiting) 135 | #PARALLEL_UPDATES = 2 136 | 137 | _LOGGER = logging.getLogger(__name__) 138 | 139 | 140 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 141 | """Setup Skoda Connect component from a config entry.""" 142 | hass.data.setdefault(DOMAIN, {}) 143 | 144 | if entry.options.get(CONF_SCAN_INTERVAL): 145 | update_interval = timedelta(seconds=entry.options[CONF_SCAN_INTERVAL]) 146 | else: 147 | update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL) 148 | if update_interval < timedelta(seconds=MIN_SCAN_INTERVAL): 149 | update_interval = timedelta(seconds=MIN_SCAN_INTERVAL) 150 | 151 | coordinator = SkodaCoordinator(hass, entry, update_interval) 152 | 153 | try: 154 | if not await coordinator.async_login(): 155 | await hass.config_entries.flow.async_init( 156 | DOMAIN, 157 | context={"source": SOURCE_REAUTH}, 158 | data=entry, 159 | ) 160 | return False 161 | except (SkodaAuthenticationException, SkodaAccountLockedException, SkodaLoginFailedException) as e: 162 | raise ConfigEntryAuthFailed(e) from e 163 | except Exception as e: 164 | raise ConfigEntryNotReady(e) from e 165 | 166 | # Do a first update of all vehicles 167 | await coordinator.async_refresh() 168 | if not coordinator.last_update_success: 169 | raise ConfigEntryNotReady 170 | 171 | # Get parent device 172 | try: 173 | identifiers={(DOMAIN, entry.unique_id)} 174 | registry = device_registry.async_get(hass) 175 | device = registry.async_get_device(identifiers) 176 | # Get user configured name for device 177 | name = device.name_by_user if not device.name_by_user is None else None 178 | except: 179 | name = None 180 | 181 | data = SkodaData(entry.data, name, coordinator) 182 | instruments = coordinator.data 183 | 184 | conf_instruments = entry.data.get(CONF_INSTRUMENTS, {}).copy() 185 | if entry.options.get(CONF_DEBUG, False) is True: 186 | _LOGGER.debug(f"Configured data: {entry.data}") 187 | _LOGGER.debug(f"Configured options: {entry.options}") 188 | _LOGGER.debug(f"Resources from options are: {entry.options.get(CONF_RESOURCES, [])}") 189 | _LOGGER.debug(f"All instruments (data): {conf_instruments}") 190 | new_instruments = {} 191 | 192 | def is_enabled(attr): 193 | """Return true if the user has enabled the resource.""" 194 | return attr in entry.data.get(CONF_RESOURCES, [attr]) 195 | 196 | components = set() 197 | 198 | # Check if new instruments 199 | for instrument in ( 200 | instrument 201 | for instrument in instruments 202 | if not instrument.attr in conf_instruments 203 | ): 204 | _LOGGER.info(f"Discovered new instrument {instrument.name}") 205 | new_instruments[instrument.attr] = instrument.name 206 | 207 | # Update config entry with new instruments 208 | if len(new_instruments) > 0: 209 | conf_instruments.update(new_instruments) 210 | # Prepare data to update config entry with 211 | update = { 212 | 'data': { 213 | CONF_INSTRUMENTS: dict(sorted(conf_instruments.items(), key=lambda item: item[1])) 214 | }, 215 | 'options': { 216 | CONF_RESOURCES: entry.options.get( 217 | CONF_RESOURCES, 218 | entry.data.get(CONF_RESOURCES, ['none'])) 219 | } 220 | } 221 | 222 | # Enable new instruments if "activate newly enable entitys" is active 223 | if hasattr(entry, "pref_disable_new_entities"): 224 | if not entry.pref_disable_new_entities: 225 | _LOGGER.debug(f"Enabling new instruments {new_instruments}") 226 | for item in new_instruments: 227 | update['options'][CONF_RESOURCES].append(item) 228 | 229 | _LOGGER.debug(f"Updating config entry data: {update.get('data')}") 230 | _LOGGER.debug(f"Updating config entry options: {update.get('options')}") 231 | hass.config_entries.async_update_entry( 232 | entry, 233 | data={**entry.data, **update['data']}, 234 | options={**entry.options, **update['options']} 235 | ) 236 | 237 | for instrument in ( 238 | instrument 239 | for instrument in instruments 240 | if instrument.component in PLATFORMS and is_enabled(instrument.slug_attr) 241 | ): 242 | data.instruments.add(instrument) 243 | components.add(PLATFORMS[instrument.component]) 244 | 245 | hass.data[DOMAIN][entry.entry_id] = { 246 | UPDATE_CALLBACK: update_callback, 247 | DATA: data, 248 | UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), 249 | REMOVE_LISTENER: hass.bus.async_listen_once( 250 | EVENT_HOMEASSISTANT_STOP, coordinator.async_logout 251 | ), 252 | } 253 | 254 | for component in components: 255 | coordinator.platforms.append(component) 256 | await hass.config_entries.async_forward_entry_setup(entry, component) 257 | 258 | # Service functions 259 | async def get_car(service_call): 260 | """Get VIN associated with HomeAssistant device ID.""" 261 | # Get device entry 262 | dev_id = service_call.data.get("device_id") 263 | dev_reg = device_registry.async_get(hass) 264 | dev_entry = dev_reg.async_get(dev_id) 265 | 266 | # Get vehicle VIN from device identifiers 267 | skoda_identifiers = [ 268 | identifier 269 | for identifier in dev_entry.identifiers 270 | if identifier[0] == DOMAIN 271 | ] 272 | vin_identifier = next(iter(skoda_identifiers)) 273 | vin = vin_identifier[1] 274 | 275 | # Get coordinator handling the device entry 276 | conf_entry = next(iter(dev_entry.config_entries)) 277 | try: 278 | dev_coordinator = hass.data[DOMAIN][conf_entry]['data'].coordinator 279 | except: 280 | raise SkodaConfigException('Could not find associated coordinator for given vehicle') 281 | 282 | # Return with associated Vehicle class object 283 | return dev_coordinator.connection.vehicle(vin) 284 | 285 | async def set_schedule(service_call=None): 286 | """Set departure schedule.""" 287 | try: 288 | # Prepare data 289 | id = service_call.data.get("id", 0) 290 | temp = None 291 | spin = service_call.data.get("spin", None) 292 | 293 | 294 | # Convert datetime objects to simple strings or check that strings are correctly formatted 295 | try: 296 | time = service_call.data.get("time").strftime("%H:%M") 297 | except: 298 | if re.match('^[0-9]{2}:[0-9]{2}$', service_call.data.get('time', '')): 299 | time = service_call.data.get("time", "08:00") 300 | else: 301 | raise SkodaInvalidRequestException(f"Invalid time string: {service_call.data.get('time')}") 302 | if service_call.data.get("off_peak_start", False): 303 | try: 304 | peakstart = service_call.data.get("off_peak_start").strftime("%H:%M") 305 | except: 306 | if re.match('^[0-9]{2}:[0-9]{2}$', service_call.data.get("off_peak_start", "")): 307 | peakstart = service_call.data.get("off_peak_start", "00:00") 308 | else: 309 | raise SkodaInvalidRequestException(f"Invalid value for off peak start hours: {service_call.data.get('off_peak_start')}") 310 | if service_call.data.get("off_peak_end", False): 311 | try: 312 | peakend = service_call.data.get("off_peak_end").strftime("%H:%M") 313 | except: 314 | if re.match('^[0-9]{2}:[0-9]{2}$', service_call.data.get("off_peak_end", "")): 315 | peakend = service_call.data.get("off_peak_end", "00:00") 316 | else: 317 | raise SkodaInvalidRequestException(f"Invalid value for off peak end hours: {service_call.data.get('off_peak_end')}") 318 | 319 | # Convert to parseable data 320 | schedule = { 321 | "id": service_call.data.get("id", 1), 322 | "enabled": service_call.data.get("enabled"), 323 | "recurring": service_call.data.get("recurring"), 324 | "date": service_call.data.get("date"), 325 | "time": time, 326 | "days": service_call.data.get("days", "nnnnnnn"), 327 | } 328 | # Set optional values 329 | # Heater source 330 | if service_call.data.get("heater_source", None) is not None: 331 | if service_call.data.get("heater_source", False) is True: 332 | if spin is None: 333 | raise SkodaInvalidRequestException("S-PIN is required when using auxiliary heater.") 334 | schedule["heaterSource"] = "automatic" 335 | else: 336 | schedule["heaterSource"] = "electric" 337 | # Night rate 338 | if service_call.data.get("off_peak_active", None) is not None: 339 | schedule["nightRateActive"] = service_call.data.get("off_peak_active") 340 | if service_call.data.get("off_peak_start", None) is not None: 341 | schedule["nightRateTimeStart"] = service_call.data.get("off_peak_start") 342 | if service_call.data.get("off_peak_end", None) is not None: 343 | schedule["nightRateTimeEnd"] = service_call.data.get("off_peak_end") 344 | # Climatisation and charging options 345 | if service_call.data.get("climatisation", None) is not None: 346 | schedule["operationClimatisation"] = service_call.data.get("climatisation") 347 | if service_call.data.get("charging", None) is not None: 348 | schedule["operationCharging"] = service_call.data.get("charging") 349 | if service_call.data.get("charge_target", None) is not None: 350 | schedule["targetChargeLevel"] = service_call.data.get("charge_target") 351 | if service_call.data.get("charge_current", None) is not None: 352 | schedule["chargeMaxCurrent"] = service_call.data.get("charge_current") 353 | # Global optional options 354 | if service_call.data.get("temp", None) is not None: 355 | schedule["targetTemp"] = service_call.data.get("temp") 356 | 357 | # Find the correct car and execute service call 358 | car = await get_car(service_call) 359 | _LOGGER.info(f'Set departure schedule {id} with data {schedule} for car {car.vin}') 360 | state = await car.set_timer_schedule(id, schedule, spin) 361 | if state is not False: 362 | _LOGGER.debug(f"Service call 'set_schedule' executed without error") 363 | await coordinator.async_request_refresh() 364 | else: 365 | _LOGGER.warning(f"Failed to execute service call 'set_schedule' with data '{service_call}'") 366 | except (SkodaInvalidRequestException) as e: 367 | _LOGGER.warning(f"Service call 'set_schedule' failed. {e}") 368 | except Exception as e: 369 | raise 370 | 371 | async def set_charge_limit(service_call=None): 372 | """Set minimum charge limit.""" 373 | try: 374 | car = await get_car(service_call) 375 | 376 | # Get charge limit and execute service call 377 | limit = service_call.data.get("limit", 50) 378 | state = await car.set_charge_limit(limit) 379 | if state is not False: 380 | _LOGGER.debug(f"Service call 'set_charge_limit' executed without error") 381 | await coordinator.async_request_refresh() 382 | else: 383 | _LOGGER.warning(f"Failed to execute service call 'set_charge_limit' with data '{service_call}'") 384 | except (SkodaInvalidRequestException) as e: 385 | _LOGGER.warning(f"Service call 'set_charge_limit' failed {e}") 386 | except Exception as e: 387 | raise 388 | 389 | async def set_current(service_call=None): 390 | """Set departure schedule.""" 391 | try: 392 | car = await get_car(service_call) 393 | 394 | # Get charge current and execute service call 395 | current = service_call.data.get('current', None) 396 | state = await car.set_charger_current(current) 397 | if state is not False: 398 | _LOGGER.debug(f"Service call 'set_current' executed without error") 399 | await coordinator.async_request_refresh() 400 | else: 401 | _LOGGER.warning(f"Failed to execute service call 'set_current' with data '{service_call}'") 402 | except (SkodaInvalidRequestException) as e: 403 | _LOGGER.warning(f"Service call 'set_current' failed {e}") 404 | except Exception as e: 405 | raise 406 | 407 | async def set_pheater_duration(service_call=None): 408 | """Set duration for parking heater.""" 409 | try: 410 | car = await get_car(service_call) 411 | car.pheater_duration = service_call.data.get("duration", car.pheater_duration) 412 | _LOGGER.debug(f"Service call 'set_pheater_duration' executed without error") 413 | await coordinator.async_request_refresh() 414 | except (SkodaInvalidRequestException) as e: 415 | _LOGGER.warning(f"Service call 'set_pheater_duration' failed {e}") 416 | except Exception as e: 417 | raise 418 | 419 | async def set_climater(service_call=None): 420 | """Start or stop climatisation with options.""" 421 | try: 422 | car = await get_car(service_call) 423 | 424 | if service_call.data.get('enabled'): 425 | action = 'auxiliary' if service_call.data.get('aux_heater', False) else 'electric' 426 | temp = service_call.data.get('temp', None) 427 | hvpower = service_call.data.get('battery_power', None) 428 | spin = service_call.data.get('spin', None) 429 | else: 430 | action = 'off' 431 | temp = hvpower = spin = None 432 | # Execute service call 433 | if await car.set_climatisation(action, temp, hvpower, spin) is True: 434 | _LOGGER.debug("Service call 'set_climater' executed without error") 435 | await coordinator.async_request_refresh() 436 | else: 437 | _LOGGER.warning(f"Failed to execute service call 'set_climater' with data '{service_call}'") 438 | except (SkodaInvalidRequestException) as e: 439 | _LOGGER.warning(f"Service call 'set_climater' failed {e}") 440 | except Exception as e: 441 | raise 442 | 443 | # Register services 444 | hass.services.async_register( 445 | DOMAIN, 446 | SERVICE_SET_SCHEDULE, 447 | set_schedule, 448 | schema = SERVICE_SET_SCHEDULE_SCHEMA 449 | ) 450 | hass.services.async_register( 451 | DOMAIN, 452 | SERVICE_SET_MAX_CURRENT, 453 | set_current, 454 | schema = SERVICE_SET_MAX_CURRENT_SCHEMA 455 | ) 456 | hass.services.async_register( 457 | DOMAIN, 458 | SERVICE_SET_CHARGE_LIMIT, 459 | set_charge_limit, 460 | schema = SERVICE_SET_CHARGE_LIMIT_SCHEMA 461 | ) 462 | hass.services.async_register( 463 | DOMAIN, 464 | SERVICE_SET_CLIMATER, 465 | set_climater, 466 | schema = SERVICE_SET_CLIMATER_SCHEMA 467 | ) 468 | hass.services.async_register( 469 | DOMAIN, 470 | SERVICE_SET_PHEATER_DURATION, 471 | set_pheater_duration, 472 | schema = SERVICE_SET_PHEATER_DURATION_SCHEMA 473 | ) 474 | return True 475 | 476 | def update_callback(hass, coordinator): 477 | _LOGGER.debug("CALLBACK!") 478 | hass.async_create_task( 479 | coordinator.async_request_refresh() 480 | ) 481 | 482 | async def async_setup(hass: HomeAssistant, config: dict): 483 | """Set up the component from configuration.yaml.""" 484 | hass.data.setdefault(DOMAIN, {}) 485 | 486 | if hass.config_entries.async_entries(DOMAIN): 487 | return True 488 | 489 | if DOMAIN in config: 490 | _LOGGER.info("Found existing Skoda Connect configuration.") 491 | hass.async_create_task( 492 | hass.config_entries.flow.async_init( 493 | DOMAIN, 494 | context={"source": SOURCE_IMPORT}, 495 | data=config[DOMAIN], 496 | ) 497 | ) 498 | 499 | return True 500 | 501 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): 502 | """Unload a config entry.""" 503 | _LOGGER.debug("Unloading services") 504 | hass.services.async_remove(DOMAIN, SERVICE_SET_SCHEDULE) 505 | hass.services.async_remove(DOMAIN, SERVICE_SET_MAX_CURRENT) 506 | hass.services.async_remove(DOMAIN, SERVICE_SET_CHARGE_LIMIT) 507 | hass.services.async_remove(DOMAIN, SERVICE_SET_CLIMATER) 508 | hass.services.async_remove(DOMAIN, SERVICE_SET_PHEATER_DURATION) 509 | return await async_unload_coordinator(hass, entry) 510 | 511 | async def async_unload_coordinator(hass: HomeAssistant, entry: ConfigEntry): 512 | """Unload auth token based entry.""" 513 | _LOGGER.debug("Unloading update listener") 514 | hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() 515 | hass.data[DOMAIN][entry.entry_id].pop(UNDO_UPDATE_LISTENER, None) 516 | 517 | _LOGGER.debug("Unloading coordinator") 518 | coordinator = hass.data[DOMAIN][entry.entry_id][DATA].coordinator 519 | await coordinator.async_logout() 520 | _LOGGER.debug("Waiting for shutdown to complete") 521 | unloaded = all( 522 | await asyncio.gather( 523 | *[ 524 | hass.config_entries.async_forward_entry_unload(entry, platform) 525 | for platform in PLATFORMS 526 | if platform in coordinator.platforms 527 | ] 528 | ) 529 | ) 530 | if unloaded: 531 | _LOGGER.debug("Unloading entry") 532 | del hass.data[DOMAIN][entry.entry_id] 533 | 534 | if not hass.data[DOMAIN]: 535 | _LOGGER.debug("Unloading data") 536 | del hass.data[DOMAIN] 537 | 538 | return unloaded 539 | 540 | async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): 541 | """Handle options update.""" 542 | # Do a lazy reload of integration when configuration changed 543 | await hass.config_entries.async_reload(entry.entry_id) 544 | 545 | def get_convert_conf(entry: ConfigEntry): 546 | return CONF_SCANDINAVIAN_MILES if entry.options.get( 547 | CONF_SCANDINAVIAN_MILES, 548 | entry.data.get( 549 | CONF_SCANDINAVIAN_MILES, 550 | False 551 | ) 552 | ) else CONF_NO_CONVERSION 553 | 554 | async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry): 555 | """Migrate configuration from old version to new.""" 556 | _LOGGER.debug(f'Migrating from version {entry.version}') 557 | 558 | # Migrate data from version 1, pre 1.0.57 559 | if entry.version == 1: 560 | # Make a copy of old config 561 | new_data = {**entry.data} 562 | new_options = {**entry.options} 563 | 564 | # Convert from minutes to seconds for poll interval 565 | minutes = entry.options.get("update_interval", 1) 566 | seconds = minutes*60 567 | new_data.pop("update_interval", None) 568 | new_data[CONF_SCAN_INTERVAL] = seconds 569 | # Default "save session" to false 570 | new_options[CONF_SAVESESSION] = False 571 | 572 | # Save "new" config 573 | entry.data = {**new_data} 574 | entry.options = {**new_options} 575 | entry.version = 3 576 | hass.config_entries.async_update_entry(entry, data=new_data, options=new_options) 577 | 578 | # Migrate data from version 2, pre version 1.2 579 | if entry.version == 2: 580 | # Make a copy of old config 581 | new_data = {**entry.data} 582 | new_options = {**entry.options} 583 | 584 | # Default "save session" to false 585 | new_options[CONF_SAVESESSION] = False 586 | new_data[CONF_TOKENS] = {} 587 | 588 | # Save "new" config 589 | #entry.options = {**new_options} 590 | #entry.data = {**new_data} 591 | entry.version = 3 592 | hass.config_entries.async_update_entry(entry, data=new_data, options=new_options) 593 | 594 | _LOGGER.info("Migration to version %s successful", entry.version) 595 | return True 596 | 597 | 598 | class SkodaData: 599 | """Hold component state.""" 600 | 601 | def __init__(self, config, name=None, coordinator=None): 602 | """Initialize the component state.""" 603 | self.vehicles = set() 604 | self.instruments = set() 605 | self.config = config.get(DOMAIN, config) 606 | self.name = name 607 | self.coordinator = coordinator 608 | 609 | def instrument(self, vin, component, attr): 610 | """Return corresponding instrument.""" 611 | return next( 612 | ( 613 | instrument 614 | for instrument in ( 615 | self.coordinator.data 616 | if self.coordinator is not None 617 | else self.instruments 618 | ) 619 | if instrument.vehicle.vin == vin 620 | and instrument.component == component 621 | and instrument.attr == attr 622 | ), 623 | None, 624 | ) 625 | 626 | def vehicle_name(self, vehicle): 627 | """Provide a friendly name for a vehicle.""" 628 | try: 629 | # Return name if configured by user 630 | if isinstance(self.name, str): 631 | if len(self.name) > 0: 632 | return self.name 633 | except: 634 | pass 635 | 636 | # Default name to nickname if supported, else vin number 637 | try: 638 | if vehicle.is_nickname_supported: 639 | return vehicle.nickname 640 | elif vehicle.vin: 641 | return vehicle.vin 642 | except: 643 | _LOGGER.info(f"Name set to blank") 644 | return "" 645 | 646 | 647 | class SkodaEntity(Entity): 648 | """Base class for all Skoda entities.""" 649 | 650 | def __init__(self, data, vin, component, attribute, callback=None): 651 | """Initialize the entity.""" 652 | 653 | def update_callbacks(): 654 | if callback is not None: 655 | callback(self.hass, data.coordinator) 656 | 657 | self.data = data 658 | self.vin = vin 659 | self.component = component 660 | self.attribute = attribute 661 | self.coordinator = data.coordinator 662 | self.instrument.callback = update_callbacks 663 | self.callback = callback 664 | 665 | async def async_update(self) -> None: 666 | """Update the entity. 667 | 668 | Only used by the generic entity update service. 669 | """ 670 | 671 | # Ignore manual update requests if the entity is disabled 672 | if not self.enabled: 673 | return 674 | 675 | await self.coordinator.async_request_refresh() 676 | 677 | async def async_added_to_hass(self): 678 | """Register update dispatcher.""" 679 | if self.coordinator is not None: 680 | self.async_on_remove( 681 | self.coordinator.async_add_listener(self.async_write_ha_state) 682 | ) 683 | else: 684 | self.async_on_remove( 685 | async_dispatcher_connect( 686 | self.hass, SIGNAL_STATE_UPDATED, self.async_write_ha_state 687 | ) 688 | ) 689 | 690 | @property 691 | def instrument(self): 692 | """Return corresponding instrument.""" 693 | return self.data.instrument(self.vin, self.component, self.attribute) 694 | 695 | @property 696 | def icon(self): 697 | """Return the icon.""" 698 | if self.instrument.attr in ["battery_level", "charging"]: 699 | return icon_for_battery_level( 700 | battery_level=self.instrument.state, charging=self.vehicle.charging 701 | ) 702 | if self.instrument.attr in ["plug_autounlock"]: 703 | if self.instrument.state == True: 704 | return "mdi:battery-lock-open" 705 | else: 706 | return "mdi:battery-lock" 707 | return self.instrument.icon 708 | 709 | @property 710 | def vehicle(self): 711 | """Return vehicle.""" 712 | return self.instrument.vehicle 713 | 714 | @property 715 | def _entity_name(self): 716 | return self.instrument.name 717 | 718 | @property 719 | def _vehicle_name(self): 720 | return self.data.vehicle_name(self.vehicle) 721 | 722 | @property 723 | def name(self): 724 | """Return full name of the entity.""" 725 | return f"{self._vehicle_name} {self._entity_name}" 726 | 727 | @property 728 | def should_poll(self): 729 | """Return the polling state.""" 730 | return False 731 | 732 | @property 733 | def assumed_state(self): 734 | """Return true if unable to access real state of entity.""" 735 | return True 736 | 737 | @property 738 | def extra_state_attributes(self): 739 | """Return extra state attributes.""" 740 | attributes = dict( 741 | self.instrument.attributes, 742 | model=f"{self.vehicle.model}/{self.vehicle.model_year}", 743 | ) 744 | 745 | # Return model image as picture attribute for position entity 746 | if "position" in self.attribute: 747 | # Try to use small thumbnail first hand, else fallback to fullsize 748 | if self.vehicle.is_model_image_small_supported: 749 | attributes["entity_picture"] = self.vehicle.model_image_small 750 | elif self.vehicle.is_model_image_large_supported: 751 | attributes["entity_picture"] = self.vehicle.model_image_large 752 | 753 | return attributes 754 | 755 | @property 756 | def device_info(self): 757 | """Return the device_info of the device.""" 758 | return { 759 | "identifiers": {(DOMAIN, self.vin)}, 760 | "name": self._vehicle_name, 761 | "manufacturer": "Skoda", 762 | "model": self.vehicle.model, 763 | "sw_version": self.vehicle.model_year, 764 | } 765 | 766 | @property 767 | def available(self): 768 | """Return if sensor is available.""" 769 | if self.data.coordinator is not None: 770 | return self.data.coordinator.last_update_success 771 | return True 772 | 773 | @property 774 | def unique_id(self) -> str: 775 | """Return a unique ID.""" 776 | return f"{self.vin}-{self.component}-{self.attribute}" 777 | 778 | 779 | class SkodaCoordinator(DataUpdateCoordinator): 780 | """Class to manage fetching data from the API.""" 781 | 782 | def __init__(self, hass: HomeAssistant, entry, update_interval: timedelta): 783 | self.vin = entry.data[CONF_VEHICLE].upper() 784 | self.hass = hass 785 | self.entry = entry 786 | self.platforms = [] 787 | self.report_last_updated = None 788 | self.connection = Connection( 789 | session=async_get_clientsession(hass), 790 | username=self.entry.data[CONF_USERNAME], 791 | password=self.entry.data[CONF_PASSWORD], 792 | fulldebug=self.entry.options.get(CONF_DEBUG, self.entry.data.get(CONF_DEBUG, DEFAULT_DEBUG)), 793 | ) 794 | 795 | super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval) 796 | 797 | async def _async_update_data(self): 798 | """Update data via library.""" 799 | vehicle = await self.update() 800 | 801 | if not vehicle: 802 | raise UpdateFailed("No vehicles found.") 803 | 804 | # Backward compatibility 805 | default_convert_conf = get_convert_conf(self.entry) 806 | 807 | convert_conf = self.entry.options.get( 808 | CONF_CONVERT, 809 | self.entry.data.get( 810 | CONF_CONVERT, 811 | default_convert_conf 812 | ) 813 | ) 814 | 815 | dashboard = vehicle.dashboard( 816 | mutable=self.entry.options.get(CONF_MUTABLE), 817 | spin=self.entry.options.get(CONF_SPIN), 818 | miles=convert_conf == CONF_IMPERIAL_UNITS, 819 | scandinavian_miles=convert_conf == CONF_SCANDINAVIAN_MILES, 820 | ) 821 | 822 | return dashboard.instruments 823 | 824 | async def async_logout(self, event=None): 825 | """Logout from Skoda Connect""" 826 | _LOGGER.debug("Shutdown Skoda Connect") 827 | try: 828 | entry_options = self.entry.options.copy() 829 | entry_data = self.entry.data.copy() 830 | # Unload update listener 831 | try: 832 | if UNDO_UPDATE_LISTENER in self.hass.data[DOMAIN][self.entry.entry_id]: 833 | _LOGGER.debug(f"Unloading update listener") 834 | self.hass.data[DOMAIN][self.entry.entry_id][UNDO_UPDATE_LISTENER]() 835 | except Exception as e: 836 | _LOGGER.debug(f"Unloading update listener") 837 | # If config is only reloaded this will throw an exception 838 | pass 839 | 840 | if entry_options.get(CONF_SAVESESSION, False): 841 | try: 842 | # Save session is true, save tokens for all clients 843 | tokens = await self.connection.save_tokens() 844 | if tokens is not False: 845 | _LOGGER.debug('Successfully fetched tokens to save') 846 | entry_data[CONF_TOKENS] = tokens 847 | _LOGGER.debug("Saving tokens to config registry") 848 | self.hass.config_entries.async_update_entry( 849 | self.entry, 850 | data=entry_data, 851 | options=entry_options 852 | ) 853 | _LOGGER.debug("Save complete.") 854 | else: 855 | _LOGGER.debug(f'Save tokens failed.') 856 | except Exception as e: 857 | raise SkodaException(f'Save tokens failed: {e}') 858 | else: 859 | try: 860 | # Save session is false, remove any saved tokens 861 | entry_data[CONF_TOKENS] = {} 862 | _LOGGER.debug("Removing tokens from config registry") 863 | self.hass.config_entries.async_update_entry( 864 | self.entry, 865 | data=entry_data, 866 | options=entry_options 867 | ) 868 | # Revoke tokens 869 | _LOGGER.debug("Terminate connection") 870 | await self.connection.terminate() 871 | except Exception as e: 872 | raise SkodaException(f'Revocation of tokens failed: {e}') 873 | # Clear connection session 874 | _LOGGER.debug('Unloading library connection') 875 | self.connection = None 876 | except (SkodaException) as e: 877 | _LOGGER.error(e) 878 | return False 879 | except Exception as ex: 880 | _LOGGER.error('Errors occured during shutdown.') 881 | return False 882 | return True 883 | 884 | async def async_login(self): 885 | """Login to Skoda Connect""" 886 | # Check if we can login 887 | try: 888 | restore = False 889 | if self.entry.options.get(CONF_SAVESESSION, False): 890 | if len(self.entry.data.get(CONF_TOKENS, {})) != 0: 891 | if await self.connection.restore_tokens(self.entry.data.get(CONF_TOKENS, None)): 892 | restore = True 893 | if restore is False: 894 | if await self.connection.doLogin() is False: 895 | _LOGGER.warning( 896 | "Could not login to Skoda Connect, please check your credentials and verify that the service is working" 897 | ) 898 | return False 899 | # Get associated vehicles before we continue 900 | await self.connection.get_vehicles() 901 | return True 902 | except (SkodaAccountLockedException, SkodaAuthenticationException) as e: 903 | # Raise auth failed error in config flow 904 | raise ConfigEntryAuthFailed(e) from e 905 | except: 906 | raise 907 | 908 | async def update(self) -> Union[bool, Vehicle]: 909 | """Update status from Skoda Connect""" 910 | 911 | # Update vehicle data 912 | _LOGGER.debug("Updating data from Skoda Connect") 913 | try: 914 | # Get Vehicle object matching VIN number 915 | vehicle = self.connection.vehicle(self.vin) 916 | if await vehicle.update(): 917 | return vehicle 918 | else: 919 | _LOGGER.warning("Could not query update from Skoda Connect") 920 | return False 921 | except Exception as error: 922 | _LOGGER.warning(f"An error occured while requesting update from Skoda Connect: {error}") 923 | return False 924 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for Skoda Connect. 3 | """ 4 | import logging 5 | 6 | from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity 7 | from homeassistant.const import CONF_RESOURCES 8 | 9 | from . import UPDATE_CALLBACK, DATA, DATA_KEY, DOMAIN, SkodaEntity 10 | 11 | _LOGGER = logging.getLogger(__name__) 12 | 13 | 14 | async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): 15 | """Set up the Skoda binary sensors.""" 16 | if discovery_info is None: 17 | return 18 | async_add_entities([SkodaBinarySensor(hass.data[DATA_KEY], *discovery_info)]) 19 | 20 | 21 | async def async_setup_entry(hass, entry, async_add_devices): 22 | data = hass.data[DOMAIN][entry.entry_id][DATA] 23 | coordinator = data.coordinator 24 | if coordinator.data is not None: 25 | if CONF_RESOURCES in entry.options: 26 | resources = entry.options[CONF_RESOURCES] 27 | else: 28 | resources = entry.data[CONF_RESOURCES] 29 | 30 | async_add_devices( 31 | SkodaBinarySensor( 32 | data, instrument.vehicle_name, instrument.component, instrument.attr, hass.data[DOMAIN][entry.entry_id][UPDATE_CALLBACK] 33 | ) 34 | for instrument in ( 35 | instrument 36 | for instrument in data.instruments 37 | if instrument.component == "binary_sensor" and instrument.attr in resources 38 | ) 39 | ) 40 | 41 | return True 42 | 43 | 44 | class SkodaBinarySensor(SkodaEntity, BinarySensorEntity): 45 | """Representation of a Skoda Binary Sensor """ 46 | 47 | @property 48 | def is_on(self): 49 | """Return True if the binary sensor is on.""" 50 | # Invert state for lock/window/door to get HA to display correctly 51 | if self.instrument.device_class in ['lock', 'door', 'window']: 52 | return not self.instrument.is_on 53 | return self.instrument.is_on 54 | 55 | @property 56 | def device_class(self): 57 | """Return the class of this sensor, from DEVICE_CLASSES.""" 58 | if self.instrument.device_class in DEVICE_CLASSES: 59 | return self.instrument.device_class 60 | return None 61 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/climate.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for Skoda Connect Platform 3 | """ 4 | import logging 5 | 6 | from homeassistant.components.climate import ClimateEntity 7 | from homeassistant.components.climate.const import ( 8 | HVAC_MODE_COOL, 9 | HVAC_MODE_HEAT, 10 | HVAC_MODE_OFF, 11 | SUPPORT_TARGET_TEMPERATURE, 12 | ) 13 | from homeassistant.const import ( 14 | ATTR_TEMPERATURE, 15 | STATE_UNKNOWN, 16 | TEMP_CELSIUS, 17 | TEMP_FAHRENHEIT, 18 | CONF_RESOURCES 19 | ) 20 | 21 | SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF] 22 | 23 | from . import DATA, DATA_KEY, DOMAIN, SkodaEntity 24 | 25 | _LOGGER = logging.getLogger(__name__) 26 | 27 | 28 | async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): 29 | """ Setup the Skoda climate.""" 30 | if discovery_info is None: 31 | return 32 | async_add_entities([SkodaClimate(hass.data[DATA_KEY], *discovery_info)]) 33 | 34 | 35 | async def async_setup_entry(hass, entry, async_add_devices): 36 | data = hass.data[DOMAIN][entry.entry_id][DATA] 37 | coordinator = data.coordinator 38 | if coordinator.data is not None: 39 | if CONF_RESOURCES in entry.options: 40 | resources = entry.options[CONF_RESOURCES] 41 | else: 42 | resources = entry.data[CONF_RESOURCES] 43 | 44 | async_add_devices( 45 | SkodaClimate( 46 | data, instrument.vehicle_name, instrument.component, instrument.attr 47 | ) 48 | for instrument in ( 49 | instrument 50 | for instrument in data.instruments 51 | if instrument.component == "climate" and instrument.attr in resources 52 | ) 53 | ) 54 | 55 | return True 56 | 57 | 58 | class SkodaClimate(SkodaEntity, ClimateEntity): 59 | """Representation of a Skoda Connect Climate.""" 60 | 61 | @property 62 | def supported_features(self): 63 | """Return the list of supported features.""" 64 | return SUPPORT_TARGET_TEMPERATURE 65 | 66 | @property 67 | def hvac_mode(self): 68 | """Return hvac operation ie. heat, cool mode. 69 | Need to be one of HVAC_MODE_*. 70 | """ 71 | if not self.instrument.hvac_mode: 72 | return HVAC_MODE_OFF 73 | 74 | hvac_modes = { 75 | "HEATING": HVAC_MODE_HEAT, 76 | "COOLING": HVAC_MODE_COOL, 77 | } 78 | return hvac_modes.get(self.instrument.hvac_mode, HVAC_MODE_OFF) 79 | 80 | @property 81 | def hvac_modes(self): 82 | """Return the list of available hvac operation modes. 83 | Need to be a subset of HVAC_MODES. 84 | """ 85 | return SUPPORT_HVAC 86 | 87 | @property 88 | def temperature_unit(self): 89 | """Return the unit of measurement.""" 90 | return TEMP_CELSIUS 91 | 92 | @property 93 | def target_temperature(self): 94 | """Return the temperature we try to reach.""" 95 | if self.instrument.target_temperature: 96 | return float(self.instrument.target_temperature) 97 | else: 98 | return STATE_UNKNOWN 99 | 100 | async def async_set_temperature(self, **kwargs): 101 | """Set new target temperatures.""" 102 | temperature = kwargs.get(ATTR_TEMPERATURE) 103 | if temperature: 104 | await self.instrument.set_temperature(temperature) 105 | 106 | async def async_set_hvac_mode(self, hvac_mode): 107 | """Set new target hvac mode.""" 108 | if hvac_mode == HVAC_MODE_OFF: 109 | await self.instrument.set_hvac_mode(False) 110 | elif hvac_mode == HVAC_MODE_HEAT: 111 | await self.instrument.set_hvac_mode(True) 112 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/config_flow.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import homeassistant.helpers.config_validation as cv 3 | import logging 4 | import voluptuous as vol 5 | from homeassistant import config_entries 6 | from homeassistant.config_entries import ConfigEntry 7 | from homeassistant.const import ( 8 | CONF_PASSWORD, 9 | CONF_RESOURCES, 10 | CONF_USERNAME, 11 | CONF_SCAN_INTERVAL, 12 | ) 13 | from homeassistant.core import callback 14 | from homeassistant.helpers.aiohttp_client import async_get_clientsession 15 | 16 | from skodaconnect import Connection 17 | from .const import ( 18 | CONF_CONVERT, 19 | CONF_NO_CONVERSION, 20 | CONF_DEBUG, 21 | CONVERT_DICT, 22 | CONF_MUTABLE, 23 | CONF_SPIN, 24 | CONF_VEHICLE, 25 | CONF_INSTRUMENTS, 26 | CONF_SAVESESSION, 27 | CONF_TOKENS, 28 | MIN_SCAN_INTERVAL, 29 | DEFAULT_SCAN_INTERVAL, 30 | DOMAIN, 31 | DEFAULT_DEBUG, 32 | ) 33 | 34 | _LOGGER = logging.getLogger(__name__) 35 | 36 | 37 | class SkodaConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 38 | VERSION = 3 39 | task_login: asyncio.Task | None = None 40 | task_finish: asyncio.Task | None = None 41 | task_get_vehicles: asyncio.Task | None = None 42 | entry = None 43 | 44 | def __init__(self): 45 | """Initialize.""" 46 | self._entry = None 47 | self._init_info = {} 48 | self._data = {} 49 | self._options = {} 50 | self._errors = {} 51 | self._connection = None 52 | self._session = None 53 | 54 | async def async_step_user(self, user_input=None): 55 | if user_input is not None: 56 | self.task_login = None 57 | self.task_update = None 58 | self.task_finish = None 59 | self._errors = {} 60 | self._data = { 61 | CONF_USERNAME: user_input[CONF_USERNAME], 62 | CONF_PASSWORD: user_input[CONF_PASSWORD], 63 | CONF_INSTRUMENTS: {}, 64 | CONF_TOKENS: {}, 65 | CONF_VEHICLE: None, 66 | } 67 | # Set default options 68 | self._options = { 69 | CONF_CONVERT: CONF_NO_CONVERSION, 70 | CONF_MUTABLE: True, 71 | CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL, 72 | CONF_DEBUG: False, 73 | CONF_SPIN: None, 74 | CONF_SAVESESSION: False, 75 | CONF_RESOURCES: [], 76 | } 77 | 78 | _LOGGER.debug("Creating connection to Skoda Connect") 79 | self._connection = Connection( 80 | session=async_get_clientsession(self.hass), 81 | username=self._data[CONF_USERNAME], 82 | password=self._data[CONF_PASSWORD], 83 | fulldebug=False, 84 | ) 85 | 86 | return await self.async_step_login() 87 | 88 | return self.async_show_form( 89 | step_id="user", 90 | data_schema=vol.Schema( 91 | { 92 | vol.Required(CONF_USERNAME): cv.string, 93 | vol.Required(CONF_PASSWORD): cv.string, 94 | } 95 | ), 96 | errors=self._errors, 97 | ) 98 | 99 | # noinspection PyBroadException 100 | async def _async_task_login(self): 101 | try: 102 | result = await self._connection.doLogin() 103 | except Exception as e: 104 | _LOGGER.error(f"Login failed with error: {e}") 105 | result = False 106 | 107 | if result is False: 108 | self._errors["base"] = "cannot_connect" 109 | 110 | self.hass.async_create_task( 111 | self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) 112 | ) 113 | 114 | async def _async_task_get_vehicles(self): 115 | try: 116 | result = await self._connection.get_vehicles() 117 | except Exception as e: 118 | _LOGGER.error(f"Fetch vehicles failed with error: {e}") 119 | self._errors["base"] = "cannot_connect" 120 | 121 | if result is False: 122 | self._errors["base"] = "cannot_connect" 123 | 124 | self.hass.async_create_task( 125 | self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) 126 | ) 127 | 128 | async def async_step_vehicle(self, user_input=None): 129 | if user_input is not None: 130 | self._data[CONF_VEHICLE] = user_input[CONF_VEHICLE] 131 | self._options[CONF_SPIN] = user_input[CONF_SPIN] 132 | self._options[CONF_SAVESESSION] = user_input[CONF_SAVESESSION] 133 | self._options[CONF_MUTABLE] = user_input[CONF_MUTABLE] 134 | return await self.async_step_monitoring() 135 | 136 | vin_numbers = self._init_info["CONF_VEHICLES"].keys() 137 | return self.async_show_form( 138 | step_id="vehicle", 139 | data_schema=vol.Schema( 140 | { 141 | vol.Required(CONF_VEHICLE, default=next(iter(vin_numbers))): vol.In( 142 | vin_numbers 143 | ), 144 | vol.Optional(CONF_SPIN, default=""): cv.string, 145 | vol.Optional(CONF_SAVESESSION, default=False): cv.boolean, 146 | vol.Required(CONF_MUTABLE, default=True): cv.boolean, 147 | } 148 | ), 149 | errors=self._errors, 150 | ) 151 | 152 | async def async_step_monitoring(self, user_input=None): 153 | if user_input is not None: 154 | self._options[CONF_RESOURCES] = user_input[CONF_RESOURCES] 155 | self._options[CONF_CONVERT] = user_input[CONF_CONVERT] 156 | self._options[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL] 157 | self._options[CONF_DEBUG] = user_input[CONF_DEBUG] 158 | 159 | await self.async_set_unique_id(self._data[CONF_VEHICLE]) 160 | self._abort_if_unique_id_configured() 161 | 162 | return self.async_create_entry( 163 | title=self._data[CONF_VEHICLE], data=self._data, options=self._options 164 | ) 165 | 166 | instruments = self._init_info["CONF_VEHICLES"][self._data[CONF_VEHICLE]] 167 | instruments_dict = { 168 | instrument.attr: instrument.name for instrument in instruments 169 | } 170 | self._data[CONF_INSTRUMENTS] = dict( 171 | sorted(instruments_dict.items(), key=lambda item: item[1]) 172 | ) 173 | 174 | return self.async_show_form( 175 | step_id="monitoring", 176 | errors=self._errors, 177 | data_schema=vol.Schema( 178 | { 179 | vol.Required( 180 | CONF_RESOURCES, 181 | default=list(self._data[CONF_INSTRUMENTS].keys()), 182 | ): cv.multi_select(self._data[CONF_INSTRUMENTS]), 183 | vol.Required(CONF_CONVERT, default=CONF_NO_CONVERSION): vol.In( 184 | CONVERT_DICT 185 | ), 186 | vol.Required( 187 | CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL 188 | ): vol.All( 189 | vol.Coerce(int), vol.Range(min=MIN_SCAN_INTERVAL, max=900) 190 | ), 191 | vol.Required(CONF_DEBUG, default=False): cv.boolean, 192 | } 193 | ), 194 | ) 195 | 196 | async def async_step_login(self, user_input=None): 197 | """Authentication and login.""" 198 | if not self.task_login: 199 | self.task_login = self.hass.async_create_task(self._async_task_login()) 200 | 201 | return self.async_show_progress( 202 | step_id="login", 203 | progress_action="task_login", 204 | progress_task=self.task_login, 205 | ) 206 | 207 | # noinspection PyBroadException 208 | try: 209 | await self.task_login 210 | except Exception: 211 | return self.async_abort(reason="Failed to connect to Skoda Connect") 212 | 213 | if self._errors: 214 | return self.async_show_progress_done(next_step_id="user") 215 | 216 | # return self.async_show_progress_done(next_step_id="vehicle") 217 | return await self.async_step_get_vehicles() 218 | 219 | async def async_step_get_vehicles(self, user_input=None): 220 | """Get vehicles step.""" 221 | if not self.task_get_vehicles: 222 | self.task_get_vehicles = self.hass.async_create_task( 223 | self._async_task_get_vehicles() 224 | ) 225 | 226 | return self.async_show_progress( 227 | step_id="get_vehicles", 228 | progress_action="task_get_vehicles", 229 | progress_task=self.task_get_vehicles, 230 | ) 231 | 232 | # noinspection PyBroadException 233 | try: 234 | await self.task_get_vehicles 235 | except Exception: 236 | return self.async_abort( 237 | reason="An error occured when trying to fetch vehicles associated with account." 238 | ) 239 | 240 | if self._errors: 241 | return self.async_show_progress_done(next_step_id="user") 242 | 243 | for vehicle in self._connection.vehicles: 244 | _LOGGER.info(f"Found data for VIN: {vehicle.vin} from Skoda Connect") 245 | if len(self._connection.vehicles) == 0: 246 | return self.async_abort( 247 | reason="Could not find any vehicles associated with account!" 248 | ) 249 | 250 | self._init_info["CONF_VEHICLES"] = { 251 | vehicle.vin: vehicle.dashboard().instruments 252 | for vehicle in self._connection.vehicles 253 | } 254 | return self.async_show_progress_done(next_step_id="vehicle") 255 | 256 | async def async_step_reauth(self, entry) -> dict: 257 | """Handle initiation of re-authentication with Skoda Connect.""" 258 | self.entry = entry 259 | return await self.async_step_reauth_confirm() 260 | 261 | async def async_step_reauth_confirm(self, user_input: dict = None) -> dict: 262 | """Handle re-authentication with Skoda Connect.""" 263 | errors: dict = {} 264 | 265 | if user_input is not None: 266 | _LOGGER.debug("Creating connection to Skoda Connect") 267 | self._connection = Connection( 268 | session=async_get_clientsession(self.hass), 269 | username=user_input[CONF_USERNAME], 270 | password=user_input[CONF_PASSWORD], 271 | fulldebug=self.entry.options.get( 272 | CONF_DEBUG, self.entry.data.get(CONF_DEBUG, DEFAULT_DEBUG) 273 | ), 274 | ) 275 | 276 | # noinspection PyBroadException 277 | try: 278 | if not await self._connection.doLogin(): 279 | _LOGGER.debug( 280 | "Unable to login to Skoda Connect. Need to accept a new EULA/T&C? Try logging in to the portal: https://www.skoda-connect.com/" 281 | ) 282 | errors["base"] = "cannot_connect" 283 | else: 284 | data = self.entry.data.copy() 285 | self.hass.config_entries.async_update_entry( 286 | self.entry, 287 | data={ 288 | **data, 289 | CONF_USERNAME: user_input[CONF_USERNAME], 290 | CONF_PASSWORD: user_input[CONF_PASSWORD], 291 | }, 292 | ) 293 | self.hass.async_create_task( 294 | self.hass.config_entries.async_reload(self.entry.entry_id) 295 | ) 296 | 297 | return self.async_abort(reason="reauth_successful") 298 | except Exception as e: 299 | _LOGGER.error("Failed to login due to error: %s", str(e)) 300 | return self.async_abort(reason="Failed to connect to Connect") 301 | 302 | return self.async_show_form( 303 | step_id="reauth_confirm", 304 | data_schema=vol.Schema( 305 | { 306 | vol.Required( 307 | CONF_USERNAME, default=self.entry.data[CONF_USERNAME] 308 | ): str, 309 | vol.Required(CONF_PASSWORD): str, 310 | } 311 | ), 312 | errors=errors, 313 | ) 314 | 315 | # Configuration.yaml import 316 | async def async_step_import(self, yaml): 317 | """Import existing configuration from YAML config.""" 318 | # Set default data and options 319 | self._data = { 320 | CONF_USERNAME: None, 321 | CONF_PASSWORD: None, 322 | CONF_INSTRUMENTS: {}, 323 | CONF_VEHICLE: None, 324 | } 325 | self._options = { 326 | CONF_CONVERT: CONF_NO_CONVERSION, 327 | CONF_MUTABLE: True, 328 | CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL, 329 | CONF_DEBUG: False, 330 | CONF_SPIN: None, 331 | CONF_SAVESESSION: False, 332 | CONF_RESOURCES: [], 333 | } 334 | self._init_info = {} 335 | 336 | # Check if integration is already configured 337 | if self._async_current_entries(): 338 | _LOGGER.info( 339 | f"Integration is already setup, please remove yaml configuration as it is deprecated" 340 | ) 341 | 342 | # Validate and convert yaml config 343 | if all(entry in yaml for entry in ("username", "password")): 344 | self._data[CONF_USERNAME] = yaml["username"] 345 | self._data[CONF_PASSWORD] = yaml["password"] 346 | else: 347 | return False 348 | if "spin" in yaml: 349 | self._options[CONF_SPIN] = yaml["spin"] 350 | if "scandinavian_miles" in yaml: 351 | if yaml["scandinavian_miles"]: 352 | self._options[CONF_CONVERT] = "scandinavian_miles" 353 | if "scan_interval" in yaml: 354 | seconds = 60 355 | minutes = 0 356 | if "seconds" in yaml["scan_interval"]: 357 | seconds = int(yaml["scan_interval"]["seconds"]) 358 | if "minutes" in yaml["scan_interval"]: 359 | minutes = int(yaml["scan_interval"]["minutes"]) 360 | self._options[CONF_SCAN_INTERVAL] = seconds + (minutes * 60) 361 | if "name" in yaml: 362 | vin = next(iter(yaml["name"])) 363 | self._data[CONF_VEHICLE] = vin.upper() 364 | if "response_debug" in yaml: 365 | if yaml["response_debug"]: 366 | self._options[CONF_DEBUG] = True 367 | 368 | # Try to login and fetch vehicles 369 | self._connection = Connection( 370 | session=async_get_clientsession(self.hass), 371 | username=self._data[CONF_USERNAME], 372 | password=self._data[CONF_PASSWORD], 373 | fulldebug=False, 374 | ) 375 | try: 376 | await self._connection.doLogin() 377 | await self._connection.get_vehicles() 378 | except: 379 | raise 380 | 381 | if len(self._connection.vehicles) == 0: 382 | return self.async_abort( 383 | reason="Skoda Connect account didn't return any vehicles" 384 | ) 385 | self._init_info["CONF_VEHICLES"] = { 386 | vehicle.vin: vehicle.dashboard().instruments 387 | for vehicle in self._connection.vehicles 388 | } 389 | 390 | if self._data[CONF_VEHICLE] is None: 391 | self._data[CONF_VEHICLE] = next(iter(self._init_info["CONF_VEHICLES"])) 392 | elif self._data[CONF_VEHICLE] not in self._init_info["CONF_VEHICLES"]: 393 | self._data[CONF_VEHICLE] = next(iter(self._init_info["CONF_VEHICLES"])) 394 | 395 | await self.async_set_unique_id(self._data[CONF_VEHICLE]) 396 | self._abort_if_unique_id_configured() 397 | 398 | instruments = self._init_info["CONF_VEHICLES"][self._data[CONF_VEHICLE]] 399 | self._data[CONF_INSTRUMENTS] = { 400 | instrument.attr: instrument.name for instrument in instruments 401 | } 402 | 403 | if "resources" in yaml: 404 | for resource in yaml["resources"]: 405 | if resource in self._data[CONF_INSTRUMENTS]: 406 | self._options[CONF_RESOURCES].append(resource) 407 | 408 | return self.async_create_entry( 409 | title=self._data[CONF_VEHICLE], data=self._data, options=self._options 410 | ) 411 | 412 | @staticmethod 413 | @callback 414 | def async_get_options_flow(config_entry): 415 | """Get the options flow for this handler.""" 416 | return SkodaConnectOptionsFlowHandler(config_entry) 417 | 418 | 419 | class SkodaConnectOptionsFlowHandler(config_entries.OptionsFlow): 420 | """Handle SkodaConnect options.""" 421 | 422 | def __init__(self, config_entry: ConfigEntry): 423 | """Initialize domain options flow.""" 424 | super().__init__() 425 | 426 | self._config_entry = config_entry 427 | 428 | async def async_step_init(self, user_input=None): 429 | """Manage the options.""" 430 | 431 | return await self.async_step_user() 432 | 433 | async def async_step_user(self, user_input=None): 434 | """Manage the options.""" 435 | if user_input is not None: 436 | # Remove some options from "data", theese are to be stored in options 437 | data = self._config_entry.data.copy() 438 | if "spin" in data and user_input.get(CONF_SPIN, "") != "": 439 | data.pop("spin", None) 440 | if "resources" in data: 441 | data.pop("resources", None) 442 | self.hass.config_entries.async_update_entry( 443 | self._config_entry, data={**data} 444 | ) 445 | 446 | options = self._config_entry.options.copy() 447 | options[CONF_SCAN_INTERVAL] = user_input.get(CONF_SCAN_INTERVAL, 1) 448 | options[CONF_SPIN] = user_input.get(CONF_SPIN, None) 449 | options[CONF_MUTABLE] = user_input.get(CONF_MUTABLE, True) 450 | options[CONF_SAVESESSION] = user_input.get(CONF_SAVESESSION, True) 451 | options[CONF_DEBUG] = user_input.get(CONF_DEBUG, False) 452 | options[CONF_RESOURCES] = user_input.get(CONF_RESOURCES, []) 453 | options[CONF_CONVERT] = user_input.get(CONF_CONVERT, CONF_NO_CONVERSION) 454 | return self.async_create_entry( 455 | title=self._config_entry, 456 | data={ 457 | **options, 458 | }, 459 | ) 460 | 461 | instruments = self._config_entry.data.get(CONF_INSTRUMENTS, {}) 462 | # Backwards compability 463 | convert = self._config_entry.options.get( 464 | CONF_CONVERT, self._config_entry.data.get(CONF_CONVERT, None) 465 | ) 466 | if convert == None: 467 | convert = "no_conversion" 468 | 469 | instruments_dict = dict( 470 | sorted( 471 | self._config_entry.data.get( 472 | CONF_INSTRUMENTS, self._config_entry.options.get(CONF_RESOURCES, {}) 473 | ).items(), 474 | key=lambda item: item[1], 475 | ) 476 | ) 477 | 478 | self._config_entry.data.get( 479 | CONF_INSTRUMENTS, self._config_entry.options.get(CONF_RESOURCES, {}) 480 | ) 481 | 482 | return self.async_show_form( 483 | step_id="user", 484 | data_schema=vol.Schema( 485 | { 486 | vol.Optional( 487 | CONF_SCAN_INTERVAL, 488 | default=self._config_entry.options.get( 489 | CONF_SCAN_INTERVAL, 490 | self._config_entry.data.get( 491 | CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL 492 | ), 493 | ), 494 | ): vol.All( 495 | vol.Coerce(int), vol.Range(min=MIN_SCAN_INTERVAL, max=900) 496 | ), 497 | vol.Optional( 498 | CONF_SPIN, 499 | default=self._config_entry.options.get( 500 | CONF_SPIN, self._config_entry.data.get(CONF_SPIN, "") 501 | ), 502 | ): cv.string, 503 | vol.Optional( 504 | CONF_MUTABLE, 505 | default=self._config_entry.options.get( 506 | CONF_MUTABLE, 507 | self._config_entry.data.get(CONF_MUTABLE, False), 508 | ), 509 | ): cv.boolean, 510 | vol.Optional( 511 | CONF_SAVESESSION, 512 | default=self._config_entry.options.get( 513 | CONF_SAVESESSION, 514 | self._config_entry.data.get(CONF_SAVESESSION, False), 515 | ), 516 | ): cv.boolean, 517 | vol.Optional( 518 | CONF_DEBUG, 519 | default=self._config_entry.options.get( 520 | CONF_DEBUG, self._config_entry.data.get(CONF_DEBUG, False) 521 | ), 522 | ): cv.boolean, 523 | vol.Optional( 524 | CONF_RESOURCES, 525 | default=self._config_entry.options.get( 526 | CONF_RESOURCES, 527 | self._config_entry.data.get(CONF_RESOURCES, []), 528 | ), 529 | ): cv.multi_select(instruments_dict), 530 | vol.Required(CONF_CONVERT, default=convert): vol.In(CONVERT_DICT), 531 | } 532 | ), 533 | ) 534 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/const.py: -------------------------------------------------------------------------------- 1 | DOMAIN = "skodaconnect" 2 | DATA_KEY = DOMAIN 3 | 4 | # Configuration definitions 5 | DEFAULT_DEBUG = False 6 | CONF_MUTABLE = "mutable" 7 | CONF_SPIN = "spin" 8 | CONF_SCANDINAVIAN_MILES = "scandinavian_miles" 9 | CONF_IMPERIAL_UNITS = "imperial_units" 10 | CONF_NO_CONVERSION = "no_conversion" 11 | CONF_CONVERT = "convert" 12 | CONF_VEHICLE = "vehicle" 13 | CONF_INSTRUMENTS = "instruments" 14 | CONF_DEBUG = "debug" 15 | CONF_SAVESESSION = "store_tokens" 16 | CONF_TOKENS = "tokens" 17 | 18 | # Service definitions 19 | SERVICE_SET_SCHEDULE = "set_departure_schedule" 20 | SERVICE_SET_MAX_CURRENT = "set_charger_max_current" 21 | SERVICE_SET_CHARGE_LIMIT = "set_charge_limit" 22 | SERVICE_SET_CLIMATER = "set_climater" 23 | SERVICE_SET_PHEATER_DURATION = "set_pheater_duration" 24 | 25 | UPDATE_CALLBACK = "update_callback" 26 | DATA = "data" 27 | UNDO_UPDATE_LISTENER = "undo_update_listener" 28 | REMOVE_LISTENER = "remove_listener" 29 | 30 | SIGNAL_STATE_UPDATED = f"{DOMAIN}.updated" 31 | 32 | MIN_SCAN_INTERVAL = 30 33 | DEFAULT_SCAN_INTERVAL = 120 34 | 35 | CONVERT_DICT = { 36 | CONF_NO_CONVERSION: "No conversion", 37 | CONF_IMPERIAL_UNITS: "Imperial units", 38 | CONF_SCANDINAVIAN_MILES: "km to mil", 39 | } 40 | 41 | PLATFORMS = { 42 | "sensor": "sensor", 43 | "binary_sensor": "binary_sensor", 44 | "lock": "lock", 45 | "device_tracker": "device_tracker", 46 | "switch": "switch", 47 | "climate": "climate", 48 | } 49 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/device_tracker.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for Skoda Connect Platform 3 | """ 4 | import logging 5 | 6 | from homeassistant.components.device_tracker import SourceType 7 | from homeassistant.components.device_tracker.config_entry import TrackerEntity 8 | from homeassistant.helpers.dispatcher import async_dispatcher_connect 9 | from homeassistant.util import slugify 10 | from homeassistant.const import CONF_RESOURCES 11 | 12 | from . import DATA, DATA_KEY, DOMAIN, SIGNAL_STATE_UPDATED, SkodaEntity 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | async def async_setup_entry(hass, entry, async_add_devices): 18 | data = hass.data[DOMAIN][entry.entry_id][DATA] 19 | coordinator = data.coordinator 20 | if coordinator.data is not None: 21 | if CONF_RESOURCES in entry.options: 22 | resources = entry.options[CONF_RESOURCES] 23 | else: 24 | resources = entry.data[CONF_RESOURCES] 25 | 26 | async_add_devices( 27 | SkodaDeviceTracker( 28 | data, instrument.vehicle_name, instrument.component, instrument.attr 29 | ) 30 | for instrument in ( 31 | instrument 32 | for instrument in data.instruments 33 | if instrument.component == "device_tracker" and instrument.attr in resources 34 | ) 35 | ) 36 | 37 | return True 38 | 39 | 40 | async def async_setup_scanner(hass, config, async_see, discovery_info=None): 41 | """Set up the Skoda tracker.""" 42 | if discovery_info is None: 43 | return 44 | 45 | vin, component, attr = discovery_info 46 | data = hass.data[DATA_KEY] 47 | instrument = data.instrument(vin, component, attr) 48 | 49 | async def see_vehicle(): 50 | """Handle the reporting of the vehicle position.""" 51 | host_name = data.vehicle_name(instrument.vehicle) 52 | dev_id = "{}".format(slugify(host_name)) 53 | _LOGGER.debug("Getting location of %s" % host_name) 54 | await async_see( 55 | dev_id=dev_id, 56 | host_name=host_name, 57 | source_type=SourceType.GPS, 58 | gps=instrument.state, 59 | icon="mdi:car", 60 | ) 61 | 62 | async_dispatcher_connect(hass, SIGNAL_STATE_UPDATED, see_vehicle) 63 | 64 | return True 65 | 66 | 67 | class SkodaDeviceTracker(SkodaEntity, TrackerEntity): 68 | @property 69 | def latitude(self) -> float: 70 | """Return latitude value of the device.""" 71 | return self.instrument.state[0] 72 | 73 | @property 74 | def longitude(self) -> float: 75 | """Return longitude value of the device.""" 76 | return self.instrument.state[1] 77 | 78 | @property 79 | def source_type(self): 80 | """Return the source type, eg gps or router, of the device.""" 81 | return SourceType.GPS 82 | 83 | @property 84 | def force_update(self): 85 | """All updates do not need to be written to the state machine.""" 86 | return False 87 | 88 | @property 89 | def icon(self): 90 | """Return the icon.""" 91 | return "mdi:car" 92 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/lock.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for Skoda Connect Platform 3 | """ 4 | import logging 5 | 6 | from homeassistant.components.lock import LockEntity 7 | from homeassistant.const import CONF_RESOURCES 8 | 9 | from . import DATA, DATA_KEY, DOMAIN, SkodaEntity 10 | 11 | _LOGGER = logging.getLogger(__name__) 12 | 13 | 14 | async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): 15 | """ Setup the Skoda lock """ 16 | if discovery_info is None: 17 | return 18 | 19 | async_add_entities([SkodaLock(hass.data[DATA_KEY], *discovery_info)]) 20 | 21 | 22 | async def async_setup_entry(hass, entry, async_add_devices): 23 | data = hass.data[DOMAIN][entry.entry_id][DATA] 24 | coordinator = data.coordinator 25 | if coordinator.data is not None: 26 | if CONF_RESOURCES in entry.options: 27 | resources = entry.options[CONF_RESOURCES] 28 | else: 29 | resources = entry.data[CONF_RESOURCES] 30 | 31 | async_add_devices( 32 | SkodaLock(data, instrument.vehicle_name, instrument.component, instrument.attr) 33 | for instrument in ( 34 | instrument 35 | for instrument in data.instruments 36 | if instrument.component == "lock" and instrument.attr in resources 37 | ) 38 | ) 39 | 40 | return True 41 | 42 | 43 | class SkodaLock(SkodaEntity, LockEntity): 44 | """Represents a Skoda Connect Lock.""" 45 | 46 | @property 47 | def is_locked(self): 48 | """Return true if lock is locked.""" 49 | return self.instrument.is_locked 50 | 51 | async def async_lock(self, **kwargs): 52 | """Lock the car.""" 53 | await self.instrument.lock() 54 | 55 | async def async_unlock(self, **kwargs): 56 | """Unlock the car.""" 57 | await self.instrument.unlock() 58 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "skodaconnect", 3 | "name": "Skoda Connect", 4 | "documentation": "https://github.com/skodaconnect/homeassistant-skodaconnect/blob/main/README.md", 5 | "issue_tracker": "https://github.com/skodaconnect/homeassistant-skodaconnect/issues", 6 | "integration_type": "hub", 7 | "dependencies": [], 8 | "config_flow": true, 9 | "codeowners": [ 10 | "@Farfar", 11 | "@WebSpider", 12 | "@dvx76" 13 | ], 14 | "requirements": [ 15 | "skodaconnect==1.3.11", 16 | "homeassistant>=2024.4.0" 17 | ], 18 | "version": "v1.2.13", 19 | "iot_class": "cloud_polling" 20 | } 21 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/sensor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for Skoda Connect Platform 3 | """ 4 | import logging 5 | 6 | from . import DATA_KEY, DOMAIN, SkodaEntity 7 | from .const import DATA 8 | from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity 9 | from homeassistant.const import CONF_RESOURCES 10 | 11 | _LOGGER = logging.getLogger(__name__) 12 | 13 | 14 | async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): 15 | """Set up the Skoda sensors.""" 16 | if discovery_info is None: 17 | return 18 | async_add_entities([SkodaSensor(hass.data[DATA_KEY], *discovery_info)]) 19 | 20 | 21 | async def async_setup_entry(hass, entry, async_add_devices): 22 | data = hass.data[DOMAIN][entry.entry_id][DATA] 23 | coordinator = data.coordinator 24 | if coordinator.data is not None: 25 | if CONF_RESOURCES in entry.options: 26 | resources = entry.options[CONF_RESOURCES] 27 | else: 28 | resources = entry.data[CONF_RESOURCES] 29 | 30 | async_add_devices( 31 | SkodaSensor( 32 | data, instrument.vehicle_name, instrument.component, instrument.attr 33 | ) 34 | for instrument in ( 35 | instrument 36 | for instrument in data.instruments 37 | if instrument.component == "sensor" and instrument.attr in resources 38 | ) 39 | ) 40 | 41 | return True 42 | 43 | 44 | class SkodaSensor(SkodaEntity, SensorEntity): 45 | """Representation of a Skoda Sensor.""" 46 | 47 | @property 48 | def state(self): 49 | """Return the state of the sensor.""" 50 | return self.instrument.state 51 | 52 | @property 53 | def native_unit_of_measurement(self): 54 | """Return the native unit of measurement.""" 55 | return self.instrument.unit 56 | 57 | @property 58 | def device_class(self): 59 | """Return the class of this sensor, from DEVICE_CLASSES.""" 60 | if self.instrument.device_class in DEVICE_CLASSES: 61 | return self.instrument.device_class 62 | return None 63 | 64 | @property 65 | def state_class(self): 66 | """Return the state_class for the sensor, to enable statistics""" 67 | if self.instrument.attr in [ 68 | 'battery_level', 'adblue_level', 'fuel_level', 'charging_time_left', 'charging_power', 'charge_rate', 69 | 'electric_range', 'combustion_range', 'combined_range', 'outside_temperature' 70 | ]: 71 | state_class = "measurement" 72 | elif self.instrument.attr in [ 73 | 'odometer' 74 | ]: 75 | state_class = "total" 76 | else: 77 | state_class = None 78 | return state_class 79 | 80 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/services.yaml: -------------------------------------------------------------------------------- 1 | # Describes the format for available Skoda Connect service calls 2 | --- 3 | set_charge_limit: 4 | name: Set charge limit 5 | description: > 6 | Set the limit that the charger will charge directly to when 7 | a departure timer is active. 8 | fields: 9 | device_id: 10 | name: Vehicle 11 | description: The vehicle to set charge limit for 12 | required: true 13 | selector: 14 | device: 15 | integration: skodaconnect 16 | limit: 17 | name: Limit 18 | description: The charging upper limit 19 | example: 50 20 | selector: 21 | number: 22 | min: 0 23 | max: 100 24 | step: 10 25 | unit_of_measurement: percent 26 | set_charger_max_current: 27 | name: Set charger max current 28 | description: > 29 | Set the global maximum charging current. 30 | fields: 31 | device_id: 32 | name: Vehicle 33 | description: The vehicle to set maximum current for 34 | required: true 35 | selector: 36 | device: 37 | integration: skodaconnect 38 | current: 39 | name: Current 40 | description: > 41 | Maximum current. String (Maximum or Reduced/Minimum) or int 1-255 (1-32 = Amps, 252 = Reduced, 254 = Maximum). 42 | example: 16 43 | selector: 44 | number: 45 | min: 1 46 | max: 254 47 | unit_of_measurement: Ampere 48 | set_pheater_duration: 49 | name: Set parking heater runtime 50 | description: > 51 | Set the runtime of the parking heater on supported cars. 52 | fields: 53 | device_id: 54 | name: Vehicle 55 | description: The vehicle to set parking heater duration for 56 | required: true 57 | selector: 58 | device: 59 | integration: skodaconnect 60 | duration: 61 | name: Runtime 62 | description: Runtime for heating or ventilation of the parking heater. 63 | required: true 64 | example: 20 65 | selector: 66 | number: 67 | min: 10 68 | max: 60 69 | step: 10 70 | unit_of_measurement: min 71 | set_climater: 72 | name: Set climatisation 73 | description: Start/stop climatisation with optional parameters 74 | fields: 75 | device_id: 76 | name: Vehicle 77 | description: The vehicle to set climatisation settings for 78 | required: true 79 | selector: 80 | device: 81 | integration: skodaconnect 82 | enabled: 83 | name: Activate 84 | description: Start or stop the climatisation 85 | example: true 86 | selector: 87 | boolean: 88 | temp: 89 | name: Target temperature 90 | description: The target temperature for climatisation (unselect to use vehicles stored setting) 91 | advanced: true 92 | example: 20 93 | selector: 94 | number: 95 | min: 16 96 | max: 30 97 | step: 0.5 98 | unit_of_measurement: °C 99 | battery_power: 100 | name: Battery power 101 | description: > 102 | Allow the use of battery power to run electric climatisation (unselect to use vehicles stored setting) 103 | advanced: true 104 | example: true 105 | selector: 106 | boolean: 107 | aux_heater: 108 | name: Auxiliary heater 109 | description: > 110 | Use the auxiliary heater for climatisation (disable to use electric), requires S-PIN and car with aux heater 111 | advanced: true 112 | example: false 113 | selector: 114 | boolean: 115 | spin: 116 | name: S-PIN 117 | description: > 118 | The S-PIN for the vehicle 119 | advanced: true 120 | example: 1234 121 | selector: 122 | text: 123 | set_departure_schedule: 124 | name: Set departure schedule 125 | description: > 126 | Set the departure for one of the departure schedules. 127 | fields: 128 | device_id: 129 | name: Vehicle 130 | description: "[Required] The vehicle to set departure schedule for." 131 | required: true 132 | selector: 133 | device: 134 | integration: skodaconnect 135 | id: 136 | name: ID 137 | description: "[Required] Which departure schedule to change." 138 | required: true 139 | example: "1" 140 | selector: 141 | number: 142 | min: 1 143 | max: 3 144 | mode: slider 145 | time: 146 | name: Time 147 | description: "[Required] The time, in UTC, for departure, 24h HH:MM." 148 | required: true 149 | example: "17:00" 150 | selector: 151 | text: 152 | enabled: 153 | name: Activated 154 | description: "[Required] If the departure schedule should be activated." 155 | required: true 156 | example: true 157 | selector: 158 | boolean: 159 | recurring: 160 | name: Recurring schedule 161 | description: "[Required] Wether the schedule should be recurring or one off." 162 | required: true 163 | example: false 164 | selector: 165 | boolean: 166 | date: 167 | name: Date 168 | description: "The date for departure (required for single schedule, not recurring)." 169 | example: "2021-06-31" 170 | selector: 171 | text: 172 | days: 173 | name: Days 174 | description: "Weekday mask for recurring schedule, mon-sun - (required for recurring schedule, not single)." 175 | example: "yyynnnn" 176 | selector: 177 | text: 178 | temp: 179 | name: Target temperature 180 | description: "[Optional] Target temperature for climatisation. Global setting and affects all climatisation actions and schedules." 181 | advanced: true 182 | example: 20 183 | selector: 184 | number: 185 | min: 16 186 | max: 30 187 | step: 0.5 188 | mode: slider 189 | climatisation: 190 | name: Climatisation 191 | description: "[Optional] Wether or not to enable climatisation for this departure." 192 | advanced: true 193 | example: true 194 | selector: 195 | boolean: 196 | charging: 197 | name: Charging 198 | description: "[Optional] Wether or not to enable charging for this departure." 199 | advanced: true 200 | example: true 201 | selector: 202 | boolean: 203 | charge_current: 204 | name: Current 205 | description: "[Optional] Maximum charging current for this departure. (1-254 or maximum/reduced)" 206 | advanced: true 207 | example: "Maximum" 208 | selector: 209 | text: 210 | charge_target: 211 | name: Charge Target 212 | description: "[Optional] The target charge level for departure." 213 | advanced: true 214 | example: 100 215 | selector: 216 | number: 217 | min: 0 218 | max: 100 219 | step: 10 220 | unit_of_measurement: percent 221 | heater_source: 222 | name: Allow Auxiliary Heater 223 | description: "[Optional] Enable allow use of aux heater for next departure" 224 | advanced: true 225 | example: true 226 | selector: 227 | boolean: 228 | spin: 229 | name: S-PIN 230 | description: > 231 | [Optional] Security PIN, required if enabling Auxiliary heater. 232 | advanced: true 233 | example: 1234 234 | selector: 235 | text: 236 | off_peak_active: 237 | name: Off-peak active 238 | description: "[Optional] Enable off-peak hours" 239 | advanced: true 240 | example: false 241 | selector: 242 | boolean: 243 | off_peak_start: 244 | name: Off-peak Start 245 | description: "[Optional] The time, in UTC, when off-peak hours for electric price start, 24h HH:MM." 246 | advanced: true 247 | example: "00:00" 248 | selector: 249 | text: 250 | off_peak_end: 251 | name: Off-peak End 252 | description: "[Optional] The time, in UTC, when off-peak hours for electric price end, 24h HH:MM." 253 | advanced: true 254 | example: "06:00" 255 | selector: 256 | text: 257 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect Configuration", 7 | "description": "Fill in your Skoda Connect account information.", 8 | "data": { 9 | "username": "[%key:common::config_flow::data::email%]", 10 | "password": "[%key:common::config_flow::data::password%]" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Vehicle Settings", 15 | "description": "The following vehicle(s) was found. Please select the vehicle you wish to monitor and it's settings.\n\nThe S-PIN is only required for some specific operations such as lock/unlock and operations that activates the combustion engine heating.\nYou can leave it blank.", 16 | "data": { 17 | "vehicle": "VIN Number", 18 | "spin": "S-PIN", 19 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 20 | "mutable": "Allow interactions with car (actions). Uncheck to make the car 'read only'." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Monitoring Settings", 25 | "description": "Specify additional monitoring settings.", 26 | "data": { 27 | "resources": "Resources to monitor.", 28 | "convert": "Select distance/unit conversions.", 29 | "scan_interval": "Poll frequency (seconds).", 30 | "debug": "Full API debug logging (requires debug logging enabled in configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Re-authenticate with your Skoda Connect account.\nMake sure to accept any new EULA in the Skoda Connect portal (https://www.skoda-connect.com/) before proceeding. ", 35 | "data": { 36 | "email": "[%key:common::config_flow::data::email%]", 37 | "password": "[%key:common::config_flow::data::password%]" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", 43 | "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" 44 | }, 45 | "error": { 46 | "cannot_connect": "Could not login to Skoda Connect, please check your credentials and verify that the service is working", 47 | "cannot_update": "Could not query update from Skoda Connect", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Logging in to Skoda Connect", 52 | "task_update": "Fetching vehicles" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Options for Skoda Connect", 59 | "description": "Configure update interval", 60 | "data": { 61 | "scan_interval": "Poll frequency (seconds)", 62 | "spin": "S-PIN", 63 | "mutable": "Allow interactions with car (actions). Uncheck to make the car 'read only'.", 64 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 65 | "convert": "Select distance/unit conversions.", 66 | "resources": "Resources to monitor.", 67 | "debug": "Full API debug logging (requires debug logging enabled in configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/switch.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for Skoda Connect Platform 3 | """ 4 | import logging 5 | from typing import Any, Dict, Optional 6 | import voluptuous as vol 7 | 8 | from homeassistant.helpers.entity import ToggleEntity 9 | from homeassistant.helpers import config_validation as cv, entity_platform, service 10 | from homeassistant.const import CONF_RESOURCES 11 | 12 | from . import DATA, DATA_KEY, DOMAIN, SkodaEntity, UPDATE_CALLBACK 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): 18 | """ Setup the Skoda switch.""" 19 | if discovery_info is None: 20 | return 21 | async_add_entities([SkodaSwitch(hass.data[DATA_KEY], *discovery_info)]) 22 | 23 | 24 | async def async_setup_entry(hass, entry, async_add_devices): 25 | data = hass.data[DOMAIN][entry.entry_id][DATA] 26 | coordinator = data.coordinator 27 | if coordinator.data is not None: 28 | if CONF_RESOURCES in entry.options: 29 | resources = entry.options[CONF_RESOURCES] 30 | else: 31 | resources = entry.data[CONF_RESOURCES] 32 | 33 | async_add_devices( 34 | SkodaSwitch( 35 | data, instrument.vehicle_name, instrument.component, instrument.attr, hass.data[DOMAIN][entry.entry_id][UPDATE_CALLBACK] 36 | ) 37 | for instrument in ( 38 | instrument 39 | for instrument in data.instruments 40 | if instrument.component == "switch" and instrument.attr in resources 41 | ) 42 | ) 43 | 44 | return True 45 | 46 | 47 | class SkodaSwitch(SkodaEntity, ToggleEntity): 48 | """Representation of a Skoda Connect Switch.""" 49 | 50 | @property 51 | def is_on(self): 52 | """Return true if switch is on.""" 53 | return self.instrument.state 54 | 55 | async def async_turn_on(self, **kwargs): 56 | """Turn the switch on.""" 57 | await self.instrument.turn_on() 58 | self.async_write_ha_state() 59 | 60 | async def async_turn_off(self, **kwargs): 61 | """Turn the switch off.""" 62 | await self.instrument.turn_off() 63 | self.async_write_ha_state() 64 | 65 | @property 66 | def assumed_state(self): 67 | return self.instrument.assumed_state 68 | 69 | @property 70 | def state_attributes(self) -> Optional[Dict[str, Any]]: 71 | return self.instrument.attributes 72 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect Konfiguration", 7 | "description": "Skoda Connect Kontoinformationen ausfüllen", 8 | "data": { 9 | "username": "E-Mail", 10 | "password": "Passwort" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Fahrzeug-Einstellungen", 15 | "description": "Das/die folgende(n) Fahrzeug(e) wurde(n) gefunden. Bitte wählen Sie das zu überwachende Fahrzeug und seine Einstellungen aus.\n\nDie S-PIN ist nur für einige spezifische Vorgänge erforderlich, wie z. B. Ver-/Entriegeln und Vorgänge, die die Heizung des Verbrennungsmotors aktivieren.\nSie können sie leer lassen.", 16 | "data": { 17 | "vehicle": "Fahrgestellnummer", 18 | "spin": "S-PIN", 19 | "store_tokens": "Speichern von Sitzungs-Tokens in der Konfiguration. Ermöglicht ein schnelleres Starten.", 20 | "mutable": "Interaktionen mit dem Fahrzeug zulassen (Aktionen). Deaktivieren Sie das Kontrollkästchen, um das Fahrzeug 'schreibgeschützt' zu machen." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Überwachungseinstellungen", 25 | "description": "Geben Sie zusätzliche Überwachungseinstellungen an.", 26 | "data": { 27 | "resources": "Zu überwachende Ressourcen.", 28 | "convert": "Wählen Sie Entfernungs-/Einheitsumrechnungen.", 29 | "scan_interval": "Abfragefrequenz (Sekunden).", 30 | "debug": "Vollständige API-Debug-Protokollierung (erfordert aktivierte Debug-Protokollierung in configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Authentifizieren Sie sich erneut mit Ihrem Skoda Connect-Konto.\nVergewissern Sie sich, dass Sie alle neuen EULA im Skoda Connect-Portal (https://www.skoda-connect.de/) akzeptieren, bevor Sie fortfahren.", 35 | "data": { 36 | "username": "E-Mail", 37 | "password": "Passwort" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "Auto mit dieser Fahrgestellnummer ist bereits konfiguriert", 43 | "reauth_successful": "Re-Authentifizierung war erfolgreich" 44 | }, 45 | "error": { 46 | "cannot_connect": "Sie konnten sich nicht bei Skoda Connect anmelden. Bitte überprüfen Sie Ihre Anmeldedaten und stellen Sie sicher, dass der Dienst funktioniert.", 47 | "cannot_update": "Update von Skoda Connect kann nicht abgefragt werden", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Anmelden bei Skoda Connect", 52 | "task_update": "Abholung von Fahrzeugen" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Optionen für Skoda Connect", 59 | "description": "Einstellungen konfigurieren", 60 | "data": { 61 | "scan_interval": "Abfragefrequenz (Sekunden).", 62 | "spin": "S-PIN", 63 | "mutable": "Interaktionen mit dem Fahrzeug zulassen (Aktionen). Deaktivieren Sie das Kontrollkästchen, um das Fahrzeug 'schreibgeschützt' zu machen.", 64 | "store_tokens": "Speichern von Sitzungs-Tokens in der Konfiguration. Ermöglicht ein schnelleres Starten.", 65 | "convert": "Wählen Sie Entfernungs-/Einheitsumrechnungen.", 66 | "resources": "Zu überwachende Ressourcen", 67 | "debug": "Vollständige API-Debug-Protokollierung (erfordert aktivierte Debug-Protokollierung in configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect Configuration", 7 | "description": "Fill in Skoda Connect account information", 8 | "data": { 9 | "username": "Email", 10 | "password": "Password" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Vehicle Settings", 15 | "description": "The following vehicle(s) was found. Please select the vehicle you wish to monitor and it's settings.\n\nThe S-PIN is only required for some specific operations such as lock/unlock and operations that activates the combustion engine heating.\nYou can leave it blank.", 16 | "data": { 17 | "vehicle": "VIN Number", 18 | "spin": "S-PIN", 19 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 20 | "mutable": "Allow interactions with car (actions). Uncheck to make the car 'read only'." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Monitoring Settings", 25 | "description": "Specify additional monitoring settings.", 26 | "data": { 27 | "resources": "Resources to monitor.", 28 | "convert": "Select distance/unit conversions.", 29 | "scan_interval": "Poll frequency (seconds).", 30 | "debug": "Full API debug logging (requires debug logging enabled in configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Re-authenticate with your Skoda Connect account.\nMake sure to accept any new EULA in the Skoda Connect portal (https://www.skoda-connect.com/) before proceeding. ", 35 | "data": { 36 | "username": "Email", 37 | "password": "Password" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "Car with this VIN is already configured", 43 | "reauth_successful": "Re-authentication was successful" 44 | }, 45 | "error": { 46 | "cannot_connect": "Could not login to Skoda Connect, please check your credentials and verify that the service is working", 47 | "cannot_update": "Could not query update from Skoda Connect", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Logging in to Skoda Connect", 52 | "task_update": "Fetching vehicles" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Options for Skoda Connect", 59 | "description": "Configure settings", 60 | "data": { 61 | "scan_interval": "Poll frequency (seconds)", 62 | "spin": "S-PIN", 63 | "mutable": "Allow interactions with car (actions). Uncheck to make the car 'read only'.", 64 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 65 | "convert": "Select distance/unit conversions.", 66 | "resources": "Resources to monitor.", 67 | "debug": "Full API debug logging (requires debug logging enabled in configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Configurazione Skoda Connect", 7 | "description": "Inserisci le informazioni del tuo account Skoda Connect", 8 | "data": { 9 | "username": "Email", 10 | "password": "Password" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Impostazione Veicolo", 15 | "description": "Trovati i seguenti veicoli. Perfavore scegli il veicolo che vuoi monitorare e la sua configurazione.\n\nL'S-PIN è richiesto solo per operazioni specifiche come l'Apertura/Chiusura e l'attivazione del riscaldamento\nPuoi lasciare il campo vuoto.", 16 | "data": { 17 | "vehicle": "Numero VIN", 18 | "spin": "S-PIN", 19 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 20 | "mutable": "Permetti interazione con l'auto(azioni). Deseleziona se vuoi sono monitorare." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Impostazioni monitoraggio", 25 | "description": "Specifica impostazioni aggiuntive per il monitoraggio.", 26 | "data": { 27 | "resources": "Risorse da monitorare.", 28 | "convert": "Scegli la conversione di distanza/unità.", 29 | "scan_interval": "Frequenza di aggiornamento (secondi).", 30 | "debug": "API debug logging completo (richiede che il debug logging sia abilitato in configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Ri-autenticati con il tuo account Skoda Connect.\nAccertati di aver accettato le nuove condizione (EULA) nel portale Skoda Connect (https://www.skoda-connect.com/) prima di procedere. ", 35 | "data": { 36 | "username": "Email", 37 | "password": "Password" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "L'auto con questo VIN è già configurata", 43 | "reauth_successful": "Re-autenticazione riuscita" 44 | }, 45 | "error": { 46 | "cannot_connect": "Non riesco ad accedere a Skoda Connect, perfavore controlla le tue credenziali e verifica che il servizio funzioni", 47 | "cannot_update": "Richiesta aggiornamento a Skoda Connect non riuscita", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Accesso a Skoda Connect", 52 | "task_update": "Recupero veicoli" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Opzioni per Skoda Connect", 59 | "description": "Configura impostazioni", 60 | "data": { 61 | "scan_interval": "Frequenza di aggiornamento (secondi)", 62 | "spin": "S-PIN", 63 | "mutable": "Permetti interazione con l'auto(azioni). Deseleziona se vuoi sono monitorare..", 64 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 65 | "convert": "Scegli la conversione di distanza/unità.", 66 | "resources": "Risorse da monitorare.", 67 | "debug": "API debug logging completo (richiede che il debug logging sia abilitato in configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/nb.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect konfigurasjon", 7 | "description": "Fyll ut Skoda Connect konto informasjon", 8 | "data": { 9 | "username": "E-post", 10 | "password": "Passord" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Kjøretøyinnstillinger", 15 | "description": "Følgende kjøretøy ble funnet, velg kjøretøyet du ønsker å legge til. \n\n S-PIN er kun påkrevd for noen handlinger som låse/låse opp og skru på varme. \nDu kan la den stå tom.", 16 | "data": { 17 | "vehicle": "VIN nummer", 18 | "spin": "S-PIN", 19 | "store_tokens": "Lagre sesjon nøklene i konfugurasjonen. Det tillater raskere oppstart.", 20 | "mutable": "Tillat interaksjoner med bilen (handlinger). Ikke avhuket tilsvarer 'kun lesetilgang'." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Monitoreringsinnstillinger", 25 | "description": "Spesifiser tilleggsmonitorering.", 26 | "data": { 27 | "resources": "Ressurser som skal monitoreres.", 28 | "convert": "Velg avstand/enhet konvertering.", 29 | "scan_interval": "Oppdateringsfrekvens (sekunder).", 30 | "debug": "Full API debug logging (krever debug logging skrudd på i configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Re-autentiser med din Skoda Connect konto. \n Sørg for å akseptere nye EULA retningslinjer i Skoda portalen før du fortsetter. (https://www.skoda-connect.com/) ", 35 | "data": { 36 | "username": "E-post", 37 | "password": "Passord" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "En bil med denne VIN koden er allerede konfigurert", 43 | "reauth_successful": "Re-autentiseringen var suksessfull" 44 | }, 45 | "error": { 46 | "cannot_connect": "Kunne ikke logge inn i Skoda Connect. Sjekk at du har fylt ut riktig og at tjenesten fungerer hos Skoda", 47 | "cannot_update": "Klarte ikke etterspørre oppdatering fra Skoda", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Logger inn i Skoda Connect", 52 | "task_update": "Henter kjøretøy" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Innstillinger for Skoda Connect", 59 | "description": "Konfigurer innstillinger", 60 | "data": { 61 | "scan_interval": "Oppdateringsfrekvens (sekunder).", 62 | "spin": "S-PIN", 63 | "mutable": "Tillat interaksjoner med bilen (handlinger). Ikke avhuket tilsvarer 'kun lesetilgang'.", 64 | "store_tokens": "Lagre sesjon nøklene i konfugurasjonen. Det tillater raskere oppstart.", 65 | "convert": "Velg avstand/enhet konvertering.", 66 | "resources": "Ressurser som skal monitoreres.", 67 | "debug": "Full API debug logging (krever debug logging skrudd på i configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect Configuratie", 7 | "description": "Vul account gegevens van Skoda Connect in", 8 | "data": { 9 | "username": "E-mail", 10 | "password": "Wachtwoord" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Voertuig instellingen", 15 | "description": "Het volgende voertuig/voertuigen was gevonden Kies het voertuig dat je wilt volgen en vul de instellingen in.\n\nDe S-PIN is alleen nodig voor enkele handelingen, zoals openen/sluiten en handelingen die de motorverwarming bedienen.\nJe kunt dit leeg laten.", 16 | "data": { 17 | "vehicle": "VIN Nummer", 18 | "spin": "S-PIN", 19 | "store_tokens": "Sla sessie bestanden op in configuratie. Zorgt voor lagere opstart tijd.", 20 | "mutable": "Interactie met de auto toelaten. Uitvinken maakt de auto 'alleen lezen'." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Monitoring instellingen", 25 | "description": "Geef extra monitoring instellingen op.", 26 | "data": { 27 | "resources": "Bronnen om te volgen.", 28 | "convert": "Kies afstand/eenheid conversies.", 29 | "scan_interval": "Poll snelheid (seconden).", 30 | "debug": "Volledige API debug logging (debug logging moet aan staan in configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Her-authenticeer me je Skoda Connect account.\nLet er op dat je een eventuele nieuwe EULA accepteert (https://www.skoda-connect.com/) voor je doorgaat. ", 35 | "data": { 36 | "username": "E-mail", 37 | "password": "Wachtwoord" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "Auto met deze VIN is al geconfigureerd", 43 | "reauth_successful": "Her-authenticatie geslaagd" 44 | }, 45 | "error": { 46 | "cannot_connect": "Kon niet aanmelden bij Skoda Connect, controleer je gegevens en controleer of de dienst werkt.", 47 | "cannot_update": "Kon geen nieuwe gegevens krijgen van Skoda Connect", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Aanmelden bij Skoda Connect", 52 | "task_update": "Voertuigen ophalen" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Opties voor Skoda Connect", 59 | "description": "Instellingen", 60 | "data": { 61 | "scan_interval": "Poll interval (seconden)", 62 | "spin": "S-PIN", 63 | "mutable": "Interactie met de auto toelaten. Uitvinken maakt de auto 'alleen lezen'.", 64 | "store_tokens": "Sla sessie bestanden op in configuratie. Zorgt voor lagere opstart tijd.", 65 | "convert": "Kies afstand/eenheid conversies.", 66 | "resources": "Bronnen om te volgen.", 67 | "debug": "Volledige API debug logging (debug logging moet aan staan in configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/nn.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect-konfigurasjon", 7 | "description": "Fyll ut Skoda Connect-informasjon", 8 | "data": { 9 | "username": "Brukarnamn", 10 | "password": "Passord" 11 | } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Konfiguracja Skoda Connect", 7 | "description": "Wypełnij informacje dla Skoda Connect\n\nS-PIN jest wymagany w przypadku niektórych opcji, takich jak blokowanie/odblokowywanie auta i włączanie/wyłączanie silnika", 8 | "data": { 9 | "username": "Email", 10 | "password": "Hasło" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Samochód", 15 | "description": "Znaleziono następujące pojazdy. Wybierz pojazd, który chcesz monitorować i jego ustawienia.\n\nKod S-PIN jest wymagany tylko do niektórych określonych operacji, takich jak blokowanie/odblokowywanie i operacje, które aktywują ogrzewanie silnika spalinowego.\nMożesz pozostawić to pole puste.", 16 | "data": { 17 | "vehicle": "Numer VIN", 18 | "spin": "S-PIN", 19 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 20 | "mutable": "Usuń zaznaczenie, aby samochód był „tylko do odczytu”. Jeśli je zostawisz będziesz mógł wejść w interakcję z samochodem" 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Ustawienia monitorowania", 25 | "description": "Określ dodatkowe ustawienia monitorowania.", 26 | "data": { 27 | "resources": "Zasoby do dodania", 28 | "convert": "Wybierz, jeśli chcesz dokonać konwersji jednostek odległości", 29 | "scan_interval": "Częstotliwość sondowania (sekundy).", 30 | "debug": "Pełne rejestrowanie debugowania interfejsu API (wymaga włączenia rejestrowania debugowania w pliku configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "data": { 35 | "description": "Ponownie uwierzytelnij się do swojego konta Skoda Connect. Przed kontynuowaniem należy zaakceptować każdą nową umowę EULA w portalu Skoda Connect (https://www.skoda-connect.com/).", 36 | "username": "Email", 37 | "password": "Hasło" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "Samochód z tym VIN jest już skonfigurowany", 43 | "reauth_successful": "Ponowne uwierzytelnienie powiodło się" 44 | }, 45 | "error": { 46 | "cannot_connect": "Nie można zalogować się do Skoda Connect, sprawdź wpisane dane logowania i sprawdź, czy usługa działa", 47 | "cannot_update": "Nie można zaktualizować danych Skoda Connect", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Logowanie do Skoda Connect", 52 | "task_update": "Pobieranie listy pojazdów" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Opcje Skoda Connect", 59 | "description": "Skonfiguruj opcje monitorowania pojazdu Skoda Connect.", 60 | "data": { 61 | "scan_interval": "Częstotliwość aktualizacji czujników (sekundy)", 62 | "spin": "S-PIN", 63 | "mutable": "Usuń zaznaczenie, aby samochód był „tylko do odczytu”. Jeśli je zostawisz będziesz mógł wejść w interakcję z samochodem", 64 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 65 | "convert": "Wybierz, jeśli chcesz dokonać konwersji jednostek odległości", 66 | "resources": "Zasoby do dodania", 67 | "debug": "Włącz pełne dzienniki debugowania z interfejsu API (wymaga włączenia rejestrowania debugowania w konfiguracji)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect Настройка", 7 | "description": "Заполните учётные данные Skoda Connect", 8 | "data": { 9 | "username": "Email", 10 | "password": "Password" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Настройки автомобиля", 15 | "description": "Был найден этот автомобил(и). Пожалуйста выберите автомобиль который вы хотите наблюдать и настраивать.\n\nS-PIN требуется для некоторых отдельных операций таких как блокировка/разблокировка и операций включения предварительного обогрева двигателя.\nВы можете оставить его пустым.", 16 | "data": { 17 | "vehicle": "VIN номер", 18 | "spin": "S-PIN", 19 | "store_tokens": "Сохраните токены сеанса в конфигурации. Позволяет ускорить запуск.", 20 | "mutable": "Разрешить взаимодействие с автомобилем (действия). Снимите флажок, чтобы сделать автомобиль 'только для чтения'." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Настройки наблюдения", 25 | "description": "Укажите дополнительные настройки наблюдения.", 26 | "data": { 27 | "resources": "Ресурсы для наблюдения.", 28 | "convert": "Выберите преобразование расстояний/единиц измерения.", 29 | "scan_interval": "Частота опроса (секунды).", 30 | "debug": "Полное журналирование отладки (требует включённого отладочного логирования в configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Повторно войдите в свою учетную запись Skoda Connect.\nПеред продолжением обязательно примите новое лицензионное соглашение с конечным пользователем на портале Skoda Connect (https://www.skoda-connect.com/). ", 35 | "data": { 36 | "username": "Email", 37 | "password": "Password" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "Автомобиль с этим VIN уже сконфигурирован", 43 | "reauth_successful": "Повторный вход успешен" 44 | }, 45 | "error": { 46 | "cannot_connect": "Не удалось войти в Skoda Connect, проверьте свои учетные данные и убедитесь, что служба работает", 47 | "cannot_update": "Не возможно обновить данные со Skoda Connect", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Вход в Skoda Connect", 52 | "task_update": "Обновление данных автомобиля" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Настройки для Skoda Connect", 59 | "description": "Настройка параметров", 60 | "data": { 61 | "scan_interval": "Частота обновлений (секунды)", 62 | "spin": "S-PIN", 63 | "mutable": "Разрешить взаимодействие с автомобилем (действия). Снимите флажок, чтобы сделать автомобиль 'только для чтения'.", 64 | "store_tokens": "Сохраните токены сеанса в конфигурации. Позволяет ускорить запуск.", 65 | "convert": "Выберите преобразование расстояний/единиц измерения.", 66 | "resources": "Ресурсы для наблюдения.", 67 | "debug": "Полное журналирование отладки (требует включённого отладочного логирования в configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Škoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Škoda Connect konfigurácia", 7 | "description": "Vyplňte informácie o účte Škoda Connect", 8 | "data": { 9 | "username": "Email", 10 | "password": "Heslo" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Nastavenia vozidla", 15 | "description": "Našlo sa nasledujúce vozidlo (vozidlá). Vyberte vozidlo, ktoré chcete sledovať, a jeho nastavenia.\n\nS-PIN sa vyžaduje len pri niektorých špecifických operáciách, ako je uzamknutie/odomknutie a operácie, ktoré aktivujú vyhrievanie spaľovacieho motora.\nMôžete ho nechať prázdne.", 16 | "data": { 17 | "vehicle": "VIN číslo", 18 | "spin": "S-PIN", 19 | "store_tokens": "Uložte tokeny relácie v konfigurácii. Umožňuje rýchlejšie spustenie.", 20 | "mutable": "Povoliť interakcie s autom (akcie). Zrušte začiarknutie, aby bolo auto 'iba na čítanie'." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Nastavenia monitorovania", 25 | "description": "Zadajte ďalšie nastavenia monitorovania.", 26 | "data": { 27 | "resources": "Zdroje na monitorovanie.", 28 | "convert": "Vyberte prevody vzdialenosti/jednotiek.", 29 | "scan_interval": "Frekvencia hlasovania (v sekundách).", 30 | "debug": "Úplné protokolovanie ladenia API (vyžaduje protokolovanie ladenia povolené v súbore configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Znova sa overte pomocou svojho účtu Škoda Connect.\nNezabudnite prijať akúkoľvek novú zmluvu EULA na portáli Škoda Connect (https://www.skoda-connect.com/) pred pokračovaním. ", 35 | "data": { 36 | "username": "Email", 37 | "password": "Heslo" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "Auto s týmto VIN je už nakonfigurované", 43 | "reauth_successful": "Opätovné overenie bolo úspešné" 44 | }, 45 | "error": { 46 | "cannot_connect": "Nepodarilo sa prihlásiť do Skoda Connect, skontrolujte svoje prihlasovacie údaje a overte, či služba funguje", 47 | "cannot_update": "Nepodarilo sa vyžiadať aktualizáciu zo Škoda Connect", 48 | "unknown": "[%key:common::config_flow::error::unknown%]" 49 | }, 50 | "progress": { 51 | "task_login": "Prihláste sa do Škoda Connect", 52 | "task_update": "Prinášanie vozidiel" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Možnosti pre Škoda Connect", 59 | "description": "Nakonfigurujte nastavenia", 60 | "data": { 61 | "scan_interval": "Frekvencia hlasovania (sekundy)", 62 | "spin": "S-PIN", 63 | "mutable": "Povoliť interakcie s autom (akcie). Zrušte začiarknutie, aby bolo auto 'iba na čítanie'.", 64 | "store_tokens": "Uložte tokeny relácie v konfigurácii. Umožňuje rýchlejšie spustenie.", 65 | "convert": "Vyberte prevody vzdialenosti/jednotiek.", 66 | "resources": "Zdroje na monitorovanie.", 67 | "debug": "Úplné protokolovanie ladenia API (vyžaduje protokolovanie ladenia povolené v súbore configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /custom_components/skodaconnect/translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Skoda Connect", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "title": "Skoda Connect Konfiguration", 7 | "description": "Fyll i uppgifterna för ditt Skoda Connect konto.", 8 | "data": { 9 | "username": "Användarnamn", 10 | "password": "Lösenord" 11 | } 12 | }, 13 | "vehicle": { 14 | "title": "Fordonsinställningar", 15 | "description": "Följande fordon hittades. Välj det fordon som ska övervakas och alternativ.\n\nS-PIN krävs endast för specifika operationer såsom lås/lås upp m.fl.\nDu kan lämna S-PIN blankt.", 16 | "data": { 17 | "vehicle": "Chassinummer", 18 | "spin": "S-PIN", 19 | "store_tokens": "Spara sessions tokens i konfigurationen. Kan ge snabbare uppstart.", 20 | "mutable": "Tillåt interaktioner med bilen (åtgärder). Avmarkera för att göra bilen 'skrivskyddad'." 21 | } 22 | }, 23 | "monitoring": { 24 | "title": "Övervakningsinställningar", 25 | "description": "Ange övervaknings alternativ.", 26 | "data": { 27 | "resources": "Resurser att övervaka.", 28 | "convert": "Ange enhetsomvandling.", 29 | "scan_interval": "Uppdateringsfrekvens (sekunder).", 30 | "debug": "Full API debug loggning (kräver debug loggning aktiverat i configuration.yaml)" 31 | } 32 | }, 33 | "reauth_confirm": { 34 | "description": "Återautentisera ditt Skoda Connect konto.\nSäkerställ att du accepterat eventuell ny EULA i Skoda Connect portalen (https://www.skoda-connect.com/) innan du fortsätter. ", 35 | "data": { 36 | "email": "Användarnamn", 37 | "password": "Lösenord" 38 | } 39 | } 40 | }, 41 | "abort": { 42 | "already_configured": "En bil med detta chassinummer är redan konfigurerad", 43 | "reauth_successful": "Autentisering lyckades" 44 | }, 45 | "error": { 46 | "cannot_connect": "Kunde inte logga in mot Skoda Connect, verifiera att du angett rätt användaruppgifter och att tjänsten är tillgänglig.", 47 | "cannot_update": "Kunde inte hämta data från Skoda Connect", 48 | "unknown": "Okänt fel" 49 | }, 50 | "progress": { 51 | "task_login": "Loggar in mot Skoda Connect", 52 | "task_update": "Hämtar fordon" 53 | } 54 | }, 55 | "options": { 56 | "step": { 57 | "user": { 58 | "title": "Alternativ för Skoda Connect", 59 | "description": "Alternativ för hantering av Skoda Connect.", 60 | "data": { 61 | "scan_interval": "Uppdateringsfrekvens (sekunder)", 62 | "spin": "S-PIN", 63 | "mutable": "Tillåt interaktioner med bilen (åtgärder). Avmarkera för att göra bilen 'skrivskyddad'.", 64 | "store_tokens": "Save session tokens in configuration. Allows for faster startup.", 65 | "convert": "Ange enhetsomvandling.", 66 | "resources": "Resurser att övervaka.", 67 | "debug": "Full API debug loggning (kräver debug loggning aktiverat i configuration.yaml)" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Skoda Connect", 3 | "iot_class": "Cloud Polling", 4 | "homeassistant": "2023.3.0", 5 | "hide_default_branch": true, 6 | "zip_release": false, 7 | "filename": "skodaconnect.zip" 8 | } 9 | -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | 2 | This plugin allows you to use Skoda Connect connection to your car. 3 | 4 | ## IMPORTANT NOTE 5 | 6 | This integration currently only supports the old Skoda API compatible with the old Skoda Essentials App. 7 | 8 | If you are unable to use the Skoda Essentials account the integration will not work for you. This is the case for new vehicles and/or Skoda accounts. 9 | 10 | We are aware the Skoda Essentials app is being deprecated and are working on updating the integration to be compatible with the new API / MySkoda app, but this is a major piece of development work. 11 | 12 | Please do not open new issues for this. 13 | 14 | {%- if selected_tag == "master" %} 15 | ## This is a development version! 16 | This is **only** intended for test by developers! 17 | Please not install this version! 18 | {% endif %} 19 | 20 | {%- if prerelease %} 21 | ## This is a beta version 22 | Please be careful and do NOT install this on production systems. Also make sure to take a backup/snapshot before installing. 23 | {% endif %} 24 | 25 | # Changes 26 | You can find change log under [releases](https://github.com/skodaconnect/homeassistant-skodaconnect/releases) 27 | 28 | # Links 29 | - [Automations](https://github.com/skodaconnect/homeassistant-skodaconnect/blob/main/README.md#automations) 30 | - [Change log](https://github.com/skodaconnect/homeassistant-skodaconnect/releases) 31 | - [Configuration](https://github.com/skodaconnect/homeassistant-skodaconnect/blob/main/README.md#configure) 32 | - [Documentation](https://github.com/skodaconnect/homeassistant-skodaconnect/blob/main/README.md) 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | skodaconnect==1.3.11 2 | homeassistant>=2024.4.0 3 | --------------------------------------------------------------------------------