├── .github └── workflows │ └── hacs_action.yml ├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── config_flow.py ├── const.py ├── device_tracker.py ├── example_dashboard.yaml ├── hacs.json ├── info.md ├── manifest.json ├── sensor.py ├── strings.json └── translations ├── de.json └── en.json /.github/workflows/hacs_action.yml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | validate: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Rename root folder to 'tronity' 17 | run: mkdir tronity && find . -maxdepth 1 -not -name ".github" -exec mv {} tronity/ \; 18 | 19 | - name: HACS Action 20 | uses: hacs/action@main 21 | with: 22 | CATEGORY: integration 23 | 24 | - name: hassfest 25 | uses: home-assistant/actions/hassfest@master -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TRONITY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tronity Integration for Home Assistant 2 | 3 | The Tronity Integration is a custom integration developed for Home Assistant, designed to enable you to monitor and 4 | utilize the data provided by Tronity. By integrating Tronity with Home Assistant, you can easily monitor your vehicle's 5 | data and create automations based on it. 6 | 7 | ## Installation 8 | 9 | #### HACS Installation Way 10 | 1. Open HACS 11 | 2. Select Integrations, then select the 3-dots in the upper-right corner, then select Custom Repositories. 12 | 3. Put the Reposity URL in the Repository field, then select Integration in the Category dropdown list and click Add. 13 | 4. Now you can search for Tronity in HACS and install it 14 | 5. After the installation you need to restart Home Assistent 15 | 6. Now you can skip Configuration and proceed with Usage 16 | 17 | #### Normalway 18 | 1. Download or clone the Tronity Integration to your local machine. 19 | 2. Navigate to the custom_components directory in your Home Assistant installation directory. 20 | 3. Create a new directory called tronity. 21 | 4. Copy the files from the downloaded Tronity Integration to the tronity directory. 22 | 5. Restart Home Assistant. 23 | 24 | ## Configuration 25 | 26 | 1. Open your configuration.yaml file in your Home Assistant installation directory. 27 | 2. Add the following code to the file:
28 | ```tronity:``` 29 | 3. Save the configuration file. 30 | 4. Restart Home Assistant. 31 | 32 | ## Usage 33 | 34 | 1. Navigate to the Setting section and click on Devices & Services. 35 | 2. Click on the Add Integration button. 36 | 3. Search for Tronity and select it. 37 | 4. In order to complete the integration setup, you will need to provide your Tronity client ID, Client Secret, and Vehicle ID. You can find this information on the Tronity Platform website: https://app.tronity.tech/. 38 | 5. Once the integration is set up, you can add Tronity sensors to your Home Assistant dashboard to monitor your vehicle's data. 39 | 40 | ### Setting up multiple vehicles 41 | To monitor data for multiple vehicles, you can set up multiple instances of Tronity in Home Assistant. Follow the same steps as above for each vehicle you want to monitor. Once you have set up the integrations for all your vehicles, you can add the corresponding Tronity sensors to your dashboard to view the data for each vehicle. 42 | 43 | ### How to implement the Tronity example_dashboard YAML file 44 | 1. Navigate to the Setting section and click on Dashboards. 45 | 2. Click on the Add Dashboard. 46 | 4. Select your newly created Dashboard. 47 | 3. Press the three dots on the top right, click on Edit Dashboard and select "Start with an empty dashboard". 48 | 4. Click the Add Card button and select "Manual Card" from the list of card types and paste the example_dashboard YAML file into the card's configuration text box. 49 | 5. Customize the configuration for your specific sensors by following the comments in the YAML file. 50 | 6. Optionally, replace the friendly names and icons in the YAML file with your own preferences. 51 | 7. Click Save to save the changes to the dashboard. 52 | 53 | ## Contributing 54 | 55 | If you would like to contribute to this custom integration, feel free to submit a pull request with your changes. 56 | 57 | ## Feedback 58 | 59 | If you encounter any issues or have any feedback on this custom integration, please raise an issue on the GitHub repository for this integration. 60 | 61 | 62 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | """The Tronity integration.""" 2 | from __future__ import annotations 3 | from typing import Any 4 | import aiohttp 5 | import async_timeout 6 | import asyncio 7 | from datetime import timedelta 8 | import logging 9 | 10 | 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.const import Platform 13 | from homeassistant.core import HomeAssistant 14 | from homeassistant.helpers.update_coordinator import ( 15 | CoordinatorEntity, 16 | DataUpdateCoordinator, 17 | UpdateFailed, 18 | ) 19 | 20 | from .const import ( 21 | DOMAIN, 22 | CONF_CLIENT_ID, 23 | CONF_VEHICLE_ID, 24 | CONF_CLIENT_SECRET, 25 | CONF_AUTH_URL, 26 | CONF_DATA_COORDINATOR, 27 | CONF_VEHICLES_URL, 28 | ) 29 | 30 | _LOGGER = logging.getLogger(__name__) 31 | 32 | PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.DEVICE_TRACKER] 33 | 34 | 35 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 36 | """Set up Tronity from a config entry.""" 37 | 38 | client_id = entry.data[CONF_CLIENT_ID] 39 | client_secret = entry.data[CONF_CLIENT_SECRET] 40 | vehicle_id = entry.data[CONF_VEHICLE_ID] 41 | auth_url = CONF_AUTH_URL 42 | vehicle_url = CONF_VEHICLES_URL 43 | 44 | async def get_bearer_token(client_id: str, client_secret: str) -> str: 45 | """Get bearer token for authentication.""" 46 | 47 | async with aiohttp.ClientSession() as session: 48 | async with session.post( 49 | auth_url, 50 | data={ 51 | "client_id": client_id, 52 | "client_secret": client_secret, 53 | "grant_type": "app", 54 | }, 55 | ) as response: 56 | response_json = await response.json() 57 | bearer_token = response_json.get("access_token") 58 | return bearer_token 59 | 60 | async def async_update_data(): 61 | """Fetch data from Tronity API.""" 62 | bearer_token = await get_bearer_token(client_id, client_secret) 63 | headers = {"Authorization": f"Bearer {bearer_token}"} 64 | 65 | try: 66 | async with async_timeout.timeout(60): 67 | async with aiohttp.ClientSession() as session: 68 | async with session.get( 69 | vehicle_url + vehicle_id + "/last_record", 70 | headers=headers, 71 | ) as response: 72 | data = await response.json() 73 | return data 74 | 75 | except asyncio.TimeoutError as exc: 76 | raise UpdateFailed("Timeout while communicating with API") from exc 77 | 78 | coordinator = DataUpdateCoordinator( 79 | hass, 80 | _LOGGER, 81 | name=DOMAIN, 82 | update_method=async_update_data, 83 | update_interval=timedelta(seconds=60), 84 | ) 85 | 86 | hass.data.setdefault(DOMAIN, {}) 87 | hass.data[DOMAIN][entry.entry_id] = {CONF_DATA_COORDINATOR: coordinator} 88 | 89 | await coordinator.async_config_entry_first_refresh() 90 | 91 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 92 | 93 | return True 94 | 95 | 96 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 97 | """Unload a config entry.""" 98 | 99 | unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) 100 | 101 | if unload_ok: 102 | hass.data[DOMAIN].pop(entry.entry_id) 103 | 104 | return unload_ok 105 | 106 | 107 | class TronityEntity(CoordinatorEntity): 108 | """Defines a base Tronity entity.""" 109 | 110 | _attr_has_entity_name = True 111 | 112 | def __init__(self, coordinator, entry) -> None: 113 | super().__init__(coordinator) 114 | self.vehicle_id = entry.data[CONF_VEHICLE_ID] 115 | self.display_name = entry.title 116 | 117 | @property 118 | def data(self): 119 | """Shortcut to access coordinator data for the entity.""" 120 | return self.coordinator.data 121 | -------------------------------------------------------------------------------- /config_flow.py: -------------------------------------------------------------------------------- 1 | """Config flow for Tronity integration.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | import aiohttp 6 | from typing import Any 7 | 8 | import voluptuous as vol 9 | 10 | from homeassistant import config_entries 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.data_entry_flow import FlowResult 13 | from homeassistant.exceptions import HomeAssistantError 14 | 15 | from .const import ( 16 | DOMAIN, 17 | CONF_CLIENT_ID, 18 | CONF_CLIENT_SECRET, 19 | CONF_AUTH_URL, 20 | CONF_VEHICLE_ID, 21 | CONF_VEHICLES_URL, 22 | CONF_DISPLAY_NAME, 23 | ) 24 | 25 | _LOGGER = logging.getLogger(__name__) 26 | 27 | DATA_SCHEMA = vol.Schema( 28 | { 29 | vol.Required(CONF_CLIENT_ID, default=""): str, 30 | vol.Required(CONF_CLIENT_SECRET, default=""): str, 31 | vol.Required(CONF_VEHICLE_ID, default=""): str, 32 | } 33 | ) 34 | 35 | 36 | async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: 37 | """Validate the user input.""" 38 | 39 | hub = TronityHub( 40 | hass, data[CONF_CLIENT_ID], data[CONF_CLIENT_SECRET], data[CONF_VEHICLE_ID] 41 | ) 42 | 43 | if not await hub.authenticate(): 44 | raise InvalidAuth 45 | 46 | title = await hub.get_display_name() 47 | 48 | return {"title": title} 49 | 50 | 51 | class TronityHub: 52 | """Initialize the TronityHub class for API authentication.""" 53 | 54 | def __init__( 55 | self, hass: HomeAssistant, client_id: str, client_secret: str, vehicle_id: str 56 | ) -> None: 57 | """Initialize.""" 58 | self.base_url = CONF_AUTH_URL 59 | self.vehicle_url = CONF_VEHICLES_URL 60 | self.hass = hass 61 | self.client_id = client_id 62 | self.client_secret = client_secret 63 | self.vehicle_id = vehicle_id 64 | 65 | async def get_bearer_token(self) -> str: 66 | async with aiohttp.ClientSession() as session: 67 | async with session.post( 68 | self.base_url, 69 | data={ 70 | "client_id": self.client_id, 71 | "client_secret": self.client_secret, 72 | "grant_type": "app", 73 | }, 74 | ) as response: 75 | response_json = await response.json() 76 | bearer_token = response_json.get("access_token") 77 | 78 | return bearer_token 79 | 80 | async def get_display_name(self) -> str: 81 | bearer_token = await self.get_bearer_token() 82 | headers = {"Authorization": f"Bearer {bearer_token}"} 83 | async with aiohttp.ClientSession() as session: 84 | async with session.get( 85 | self.vehicle_url + self.vehicle_id, 86 | headers=headers, 87 | ) as response: 88 | data = await response.json() 89 | return data["displayName"] 90 | 91 | async def get_api_status(self) -> int: 92 | """Handle the start of the TronityHub config flow, validating user input and creating an entry.""" 93 | async with aiohttp.ClientSession() as session: 94 | async with session.post( 95 | self.base_url, 96 | data={ 97 | "client_id": self.client_id, 98 | "client_secret": self.client_secret, 99 | "grant_type": "app", 100 | }, 101 | ) as response: 102 | return response.status 103 | 104 | async def authenticate(self) -> bool: 105 | status_code = await self.get_api_status() 106 | 107 | if status_code == 201 or status_code == 200: 108 | return True 109 | 110 | 111 | class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 112 | """Handle a config flow for Tronity.""" 113 | 114 | VERSION = 1 115 | 116 | async def async_step_user( 117 | self, user_input: dict[str, Any] | None = None 118 | ) -> FlowResult: 119 | """Handle the start of the config flow.""" 120 | errors: dict[str, str] = {} 121 | 122 | if user_input is not None: 123 | try: 124 | info = await validate_input(self.hass, user_input) 125 | except InvalidAuth: 126 | errors["base"] = "invalid_auth" 127 | except Exception: 128 | _LOGGER.exception("Unexpected exception") 129 | errors["base"] = "unknown" 130 | else: 131 | entry_data = {**user_input, CONF_DISPLAY_NAME: info["title"]} 132 | return self.async_create_entry(title=info["title"], data=entry_data) 133 | 134 | return self.async_show_form( 135 | step_id="user", data_schema=DATA_SCHEMA, errors=errors 136 | ) 137 | 138 | 139 | class InvalidAuth(HomeAssistantError): 140 | """Error to indicate there is invalid auth.""" 141 | -------------------------------------------------------------------------------- /const.py: -------------------------------------------------------------------------------- 1 | """Constants for the Tronity integration.""" 2 | 3 | DOMAIN = "tronity" 4 | CONF_CLIENT_ID = "client_id" 5 | CONF_CLIENT_SECRET = "client_secret" 6 | CONF_VEHICLE_ID = "vehicle_id" 7 | CONF_BEARER_TOKEN = "bearer_token" 8 | CONF_DISPLAY_NAME = "display_name" 9 | CONF_VEHICLE_DATA = "vehicle_data" 10 | CONF_DATA_COORDINATOR = "coordinator" 11 | 12 | CONF_AUTH_URL = "https://api.tronity.tech/authentication" 13 | CONF_VEHICLES_URL = "https://api.tronity.tech/tronity/vehicles/" 14 | -------------------------------------------------------------------------------- /device_tracker.py: -------------------------------------------------------------------------------- 1 | """Platform for Tronity device tracker integration.""" 2 | from homeassistant.components.device_tracker import SourceType, TrackerEntity 3 | from homeassistant.config_entries import ConfigEntry 4 | from homeassistant.core import HomeAssistant 5 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 6 | 7 | from .const import DOMAIN, CONF_DATA_COORDINATOR 8 | from . import TronityEntity 9 | 10 | 11 | async def async_setup_entry( 12 | hass: HomeAssistant, 13 | config_entry: ConfigEntry, 14 | async_add_entites: AddEntitiesCallback, 15 | ) -> None: 16 | """Set up the device tracker platform.""" 17 | coordinator = hass.data[DOMAIN][config_entry.entry_id][CONF_DATA_COORDINATOR] 18 | 19 | async_add_entites([TronityDeviceTracker(coordinator, config_entry)]) 20 | 21 | 22 | class TronityDeviceTracker(TronityEntity, TrackerEntity): 23 | """Class for the device tracker.""" 24 | 25 | _attr_icon = "mdi:map" 26 | _attr_force_update = False 27 | 28 | def __init__(self, coordinator, config_entry) -> None: 29 | """Initialize Tronity device tracker.""" 30 | super().__init__(coordinator, config_entry) 31 | self._attr_unique_id = f"{self.vehicle_id}_device_tracker" 32 | 33 | @property 34 | def name(self) -> str: 35 | """Return name of the sensor.""" 36 | return f"{self.vehicle_id}_device_tracker" 37 | 38 | @property 39 | def source_type(self) -> SourceType: 40 | """Return the source type""" 41 | return SourceType.GPS 42 | 43 | @property 44 | def latitude(self): 45 | """Return latitude value of the device.""" 46 | return self.coordinator.data["latitude"] 47 | 48 | @property 49 | def longitude(self): 50 | """Return longitude value of the device.""" 51 | return self.coordinator.data["longitude"] 52 | -------------------------------------------------------------------------------- /example_dashboard.yaml: -------------------------------------------------------------------------------- 1 | type: vertical-stack 2 | cards: 3 | - type: entity 4 | # Enter the entity ID of your display name sensor here (must start with 'sensor.tronity_') 5 | # Example: sensor.tronity_opel_corsa_display_name 6 | entity: sensor.tronity_ 7 | # Enter the friendly name you want to display for this sensor 8 | # Example: Display Name 9 | name: 10 | # Enter the icon you want to display for this sensor 11 | # Example: mdi:car 12 | icon: 13 | 14 | - type: entities 15 | entities: 16 | # Enter the entity ID of your odometer sensor here (must start with 'sensor.tronity_') 17 | # Example: sensor.tronity_opel_corsa_odometer 18 | - entity: sensor.tronity_ 19 | # Enter the friendly name you want to display for this sensor 20 | # Example: Odometer 21 | name: 22 | # Enter the entity ID of your plugged sensor here (must start with 'sensor.tronity_') 23 | # Example: sensor.tronity_opel_corsa_plugged 24 | - entity: sensor.tronity_ 25 | # Enter the friendly name you want to display for this sensor 26 | # Example: Plugged 27 | name: 28 | # Enter the icon you want to display for this sensor 29 | # Example: mdi:power-plug 30 | icon: 31 | # Enter the entity ID of your charging sensor here (must start with 'sensor.tronity_') 32 | # Example: sensor.tronity_die_ente_charging 33 | - entity: sensor.tronity_ 34 | # Enter the friendly name you want to display for this sensor 35 | # Example: Charging 36 | name: 37 | # Enter the icon you want to display for this sensor 38 | # Example: mdi:battery-charging 39 | icon: 40 | # Enter the entity ID of your charge remaining time sensor here (must start with 'sensor.tronity_') 41 | # Example: sensor.tronity_die_ente_charge_remaining_time 42 | - entity: sensor.tronity_ 43 | # Enter the friendly name you want to display for this sensor 44 | # Example: Charging Remaining Time 45 | name: 46 | # Enter the entity ID of your charger power sensor here (must start with 'sensor.tronity_') 47 | # Example: sensor.tronity_die_ente_charger_power 48 | - entity: sensor.tronity_ 49 | # Enter the friendly name you want to display for this sensor 50 | # Example: Charger Power 51 | name: 52 | 53 | - type: horizontal-stack 54 | cards: 55 | # Enter the entity ID of your remaining range sensor here (must start with 'sensor.tronity_') 56 | # Example: sensor.tronity_opel_corsa_range 57 | - type: entity 58 | entity: sensor.tronity_ 59 | # Enter the friendly name you want to display for this sensor 60 | # Example: Remaining Range 61 | name: 62 | state_color: false 63 | # Enter the entity ID of your battery level sensor here (must start with 'sensor.tronity_') 64 | # Example: sensor.tronity_opel_corsa_level 65 | - type: gauge 66 | entity: sensor.tronity_ 67 | # Enter the friendly name you want to display for this sensor 68 | # Example: Battery Level 69 | name: 70 | severity: 71 | green: 50 72 | yellow: 25 73 | red: 0 74 | 75 | - type: history-graph 76 | entities: 77 | # Enter the entity ID of your battery history graph sensor here (must start with 'sensor.tronity_') 78 | # Example: sensor.tronity_opel_corsa_level 79 | - entity: sensor.tronity_ 80 | # Enter the title you want to display for this graph 81 | # Example: Battery Level 82 | title: -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TRONITY", 3 | "content_in_root": true 4 | } 5 | -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | # Tronity Integration 2 | 3 | The Tronity Integration is a custom integration developed for Home Assistant, designed to enable you to monitor and utilize the data provided by Tronity. By integrating Tronity with Home Assistant, you can easily monitor your vehicle's data and create automations based on it. 4 | 5 | ## Features 6 | 7 | - Real-time monitoring of vehicle data, including odometer, range, battery level, charging status, and more. 8 | - Support for multiple vehicles, with the ability to set up separate integrations for each vehicle. 9 | - Integration with Home Assistant automations and dashboard widgets for creating custom views of your vehicle data. 10 | 11 | ## Contributing 12 | 13 | If you would like to contribute to this custom integration, feel free to submit a pull request with your changes. 14 | 15 | ## Feedback 16 | If you encounter any issues or have any feedback on this custom integration, please raise an issue on the GitHub repository for this integration. -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "tronity", 3 | "name": "Tronity", 4 | "codeowners": ["@hanno-tronity"], 5 | "config_flow": true, 6 | "dependencies": [], 7 | "documentation": "https://github.com/tronity/homeassistant", 8 | "iot_class": "local_polling", 9 | "issue_tracker": "https://github.com/tronity/homeassistant/issues", 10 | "requirements": [], 11 | "version": "0.2.0" 12 | } 13 | -------------------------------------------------------------------------------- /sensor.py: -------------------------------------------------------------------------------- 1 | from homeassistant.core import HomeAssistant 2 | from homeassistant.config_entries import ConfigEntry 3 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 4 | from homeassistant.components.sensor import ( 5 | SensorEntity, 6 | SensorEntityDescription, 7 | SensorStateClass, 8 | SensorDeviceClass, 9 | ) 10 | from homeassistant.helpers.typing import StateType 11 | 12 | from . import TronityEntity 13 | from .const import DOMAIN, CONF_DATA_COORDINATOR 14 | 15 | 16 | SENSOR_ENTITIES = [ 17 | SensorEntityDescription( 18 | key="odometer", 19 | icon="mdi:speedometer", 20 | device_class=SensorDeviceClass.DISTANCE, 21 | native_unit_of_measurement="km", 22 | state_class=SensorStateClass.TOTAL_INCREASING, 23 | ), 24 | SensorEntityDescription( 25 | key="range", 26 | icon="mdi:ev-station", 27 | device_class=SensorDeviceClass.DISTANCE, 28 | native_unit_of_measurement="km", 29 | state_class=SensorStateClass.MEASUREMENT, 30 | ), 31 | SensorEntityDescription( 32 | key="level", 33 | device_class=SensorDeviceClass.BATTERY, 34 | native_unit_of_measurement="%", 35 | state_class=SensorStateClass.MEASUREMENT, 36 | ), 37 | SensorEntityDescription( 38 | key="charging", 39 | icon="mdi:battery-charging", 40 | ), 41 | SensorEntityDescription( 42 | key="plugged", 43 | icon="mdi:power-plug", 44 | device_class=SensorDeviceClass.ENUM, 45 | options=[False, True], 46 | ), 47 | SensorEntityDescription( 48 | key="chargerPower", 49 | device_class=SensorDeviceClass.POWER, 50 | native_unit_of_measurement="kW", 51 | state_class=SensorStateClass.MEASUREMENT, 52 | ), 53 | SensorEntityDescription( 54 | key="chargeRemainingTime", 55 | device_class=SensorDeviceClass.DURATION, 56 | native_unit_of_measurement="s", 57 | state_class=SensorStateClass.MEASUREMENT, 58 | ), 59 | ] 60 | 61 | 62 | async def async_setup_entry( 63 | hass: HomeAssistant, 64 | config_entry: ConfigEntry, 65 | async_add_entities: AddEntitiesCallback, 66 | ) -> None: 67 | """Set up the sensor platform.""" 68 | coordinator = hass.data[DOMAIN][config_entry.entry_id][CONF_DATA_COORDINATOR] 69 | 70 | entities: list[SensorEntity] = [DisplayName(coordinator, config_entry)] 71 | 72 | for description in SENSOR_ENTITIES: 73 | entities.append(TronitySensorEntity(coordinator, description, config_entry)) 74 | 75 | async_add_entities(entities) 76 | 77 | 78 | class TronitySensorEntity(SensorEntity, TronityEntity): 79 | """Representation of a Tronity vehicle sensor.""" 80 | 81 | entity_description: SensorEntityDescription 82 | 83 | def __init__(self, coordinator, description, config_entry: ConfigEntry) -> None: 84 | """Initialize Tronity sensor.""" 85 | super().__init__(coordinator, config_entry) 86 | self.entity_description = description 87 | self._attr_unique_id = f"{self.vehicle_id}_{description.key}" 88 | 89 | @property 90 | def name(self) -> str: 91 | """Return name of the sensor.""" 92 | return f"tronity.{self.display_name}.{self.entity_description.key}" 93 | 94 | @property 95 | def native_value(self) -> StateType: 96 | """Return the state of the sensor.""" 97 | return self.data[self.entity_description.key] 98 | 99 | 100 | class DisplayName(SensorEntity, TronityEntity): 101 | """Fetch display name.""" 102 | 103 | entity_description: SensorEntityDescription 104 | 105 | def __init__(self, coordinator, config_entry: ConfigEntry) -> None: 106 | """Initialize Tronity sensor.""" 107 | super().__init__(coordinator, config_entry) 108 | self._attr_unique_id = f"{self.vehicle_id}_display_name" 109 | self._attr_icon = "mdi:car" 110 | 111 | @property 112 | def name(self) -> str: 113 | """Return name of the sensor.""" 114 | return "tronity.display_name" 115 | 116 | @property 117 | def native_value(self) -> StateType: 118 | """Return the state of the sensor.""" 119 | return self.display_name 120 | -------------------------------------------------------------------------------- /strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "data": { 6 | "client_id": "Client Id", 7 | "client_secret": "Client Secret", 8 | "vehicle_id": "Vehicle Id" 9 | 10 | } 11 | } 12 | }, 13 | "error": { 14 | "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", 15 | "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", 16 | "unknown": "[%key:common::config_flow::error::unknown%]" 17 | }, 18 | "abort": { 19 | "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Das Gerät ist bereits konfiguriert" 5 | }, 6 | "error": { 7 | "cannot_connect": "Verbindung fehlgeschlagen", 8 | "invalid_auth": "Authentifizierung fehlgeschlagen", 9 | "unknown": "Unerwarteter Fehler" 10 | }, 11 | "step": { 12 | "user": { 13 | "data": { 14 | "client_id": "Client Id", 15 | "client_secret": "Client Secret", 16 | "vehicle_id": "Vehicle Id" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Device is already configured" 5 | }, 6 | "error": { 7 | "cannot_connect": "Failed to connect", 8 | "invalid_auth": "Invalid authentication", 9 | "unknown": "Unexpected error" 10 | }, 11 | "step": { 12 | "user": { 13 | "data": { 14 | "client_id": "Client Id", 15 | "client_secret": "Client Secret", 16 | "vehicle_id": "Vehicle Id" 17 | } 18 | } 19 | } 20 | } 21 | } --------------------------------------------------------------------------------