diff --git a/custom_components/drivvo/__init__.py b/custom_components/drivvo/__init__.py index 711a977..4ae9a60 100644 --- a/custom_components/drivvo/__init__.py +++ b/custom_components/drivvo/__init__.py @@ -1,3 +1,4 @@ +import dataclasses import hashlib import logging @@ -120,12 +121,15 @@ def get(): return None -async def get_data_vehicle(hass, user, password, id_vehicle, info): +async def get_data_vehicle(hass, user, password, id_vehicle): """Get The request from the api.""" def get(): return requests.get(url, headers={"x-token": token}) + def sort_by_key(list): + return list["data"] + token = await auth( hass=hass, user=user, @@ -133,25 +137,289 @@ def get(): token=True, ) if token: - if info == "abastecimento": - url = BASE_URL.format(f"veiculo/{id_vehicle}/abastecimento/web") - elif info == "base": - url = BASE_URL.format(f"veiculo/{id_vehicle}") + url = BASE_URL.format(f"veiculo/{id_vehicle}") + response_vehicle = await hass.async_add_executor_job(get) + if response_vehicle.ok: + api_data_vehicle = response_vehicle.json() else: return None + _LOGGER.debug( + "API Response Data Vehicle %s - Vehicle: %s", id_vehicle, api_data_vehicle + ) + + url = BASE_URL.format(f"veiculo/{id_vehicle}/abastecimento/web") + response_refuelling = await hass.async_add_executor_job(get) + if response_refuelling.ok: + api_data_refuellings = sorted( + response_refuelling.json(), key=sort_by_key, reverse=True + ) + else: + api_data_refuellings = None + _LOGGER.debug( + "API Response Data Vehicle %s - Refuelling: %s", + id_vehicle, + api_data_refuellings, + ) + + url = BASE_URL.format(f"veiculo/{id_vehicle}/servico/web") + response_services = await hass.async_add_executor_job(get) + if response_services.ok: + api_data_services = sorted( + response_services.json(), key=sort_by_key, reverse=True + ) + else: + api_data_services = None + _LOGGER.debug( + "API Response Data Vehicle %s - Services: %s", id_vehicle, api_data_services + ) + + url = BASE_URL.format(f"veiculo/{id_vehicle}/despesa/web") + response_expenses = await hass.async_add_executor_job(get) + if response_expenses.ok: + api_data_expenses = sorted( + response_expenses.json(), key=sort_by_key, reverse=True + ) + else: + api_data_expenses = None + _LOGGER.debug( + "API Response Data Vehicle %s - Expenses: %s", id_vehicle, api_data_expenses + ) - response = await hass.async_add_executor_job(get) + name: str | None = None + placa: str | None = None + if api_data_vehicle["nome"] is not None and api_data_vehicle["nome"] != "": + name = api_data_vehicle["nome"] + if api_data_vehicle["placa"] is not None and api_data_vehicle["placa"] != "": + placa = api_data_vehicle["placa"] + + if name is not None: + identification = name + elif placa: + identification = placa + else: + identification = f"{api_data_vehicle['marca']}/{api_data_vehicle['modelo']}" + + refuelling_date = None + refuelling_last_average = None + refuelling_general_average = None + refuelling_station = None + refuelling_type = None + refuelling_value = None + refuelling_distance = None + refuelling_reason = None + refuelling_price = None + refuelling_total = 0 + refuelling_value_total = None + refuelling_tank_full = None + refuelling_price_lowest = None + refuelling_volume = None + refuelling_volume_total = None + + refuellings_odometers = [] + if len(api_data_refuellings) > 0: + refuelling_total = len(api_data_refuellings) + + refuelling_value_total = 0 + refuelling_volume_total = 0 + for refuelling in api_data_refuellings: + refuelling_value_total += refuelling["valor_total"] + + if refuelling["volume"] != 0: + refuelling_volume_total += refuelling["volume"] + else: + refuelling_volume_total += ( + refuelling["valor_total"] / refuelling["preco"] + ) + + refuelling_distance = 0 + refuellings_odometers = [ + { + "odometro": refuelling["odometro"], + "data": refuelling["data"], + "volume": refuelling["volume"], + "tanque_cheio": refuelling["tanque_cheio"], + "preco": refuelling["preco"], + "valor_total": refuelling["valor_total"], + } + for refuelling in api_data_refuellings + ] + _LOGGER.debug( + "API Response Data Vehicle %s - odometers: %s", + id_vehicle, + refuellings_odometers, + ) - def sort_by_key(list): - return list["data"] + if len(refuellings_odometers) > 1: + refuelling_distance = ( + refuellings_odometers[0]["odometro"] + - refuellings_odometers[1]["odometro"] + ) + if refuelling_volume_total > 0: + refuelling_general_average = ( + refuellings_odometers[0]["odometro"] + - refuellings_odometers[len(refuellings_odometers) - 1][ + "odometro" + ] + ) / (refuelling_volume_total) + else: + refuelling_general_average = 0 + + volume = 0 + odometer_init = None + odometer_old = None + for odometer in refuellings_odometers: + if odometer["tanque_cheio"] and odometer_init is None: + odometer_init = odometer["odometro"] + + if odometer_init is not None: + if odometer["volume"] != 0: + volume = odometer["volume"] + else: + volume = odometer["valor_total"] / odometer["preco"] + + if (odometer["tanque_cheio"]) and ( + odometer["odometro"] != odometer_init + ): + odometer_old = odometer["odometro"] + break + + if volume > 0 and odometer_old is not None: + refuelling_last_average = ( + refuellings_odometers[0]["odometro"] - odometer_old + ) / (volume) + else: + refuelling_last_average = 0 + + refuelling_price_lowest = min( + [refuelling["preco"] for refuelling in api_data_refuellings] + ) - if response.ok: - data = response.json() - if info == "abastecimento": - data = sorted(data, key=sort_by_key, reverse=True) + refuelling = api_data_refuellings[0] + refuelling_date = refuelling["data"] + refuelling_type = refuelling["combustivel"] + refuelling_value = refuelling["valor_total"] + refuelling_price = refuelling["preco"] + if refuelling["volume"] != 0: + refuelling_volume = refuelling["volume"] + else: + refuelling_volume = refuelling["valor_total"] / refuelling["preco"] + refuelling_reason = refuelling["tipo_motivo"] + refuelling_tank_full = refuelling["tanque_cheio"] + + station = refuelling["posto_combustivel"] + if (station is not None) and ("nome" in station): + refuelling_station = station["nome"] + + distance_unit: str | None = None + if api_data_vehicle["unidade_distancia"] == 1: + distance_unit = "km" + elif api_data_vehicle["unidade_distancia"] == 2: + distance_unit = "mi" + + services_odometers = [] + if len(api_data_services) > 0: + services_odometers = [ + { + "odometro": service["odometro"], + "data": service["data"], + } + for service in api_data_services + ] + _LOGGER.debug( + "API Response Data Vehicle %s - odometers services: %s", + id_vehicle, + services_odometers, + ) - _LOGGER.debug("API Response Data Vehicle - %s: %s", info, data) - return data + expenses_odometers = [] + if len(api_data_expenses) > 0: + expenses_odometers = [ + { + "odometro": expense["odometro"], + "data": expense["data"], + } + for expense in api_data_expenses + ] + _LOGGER.debug( + "API Response Data Vehicle %s - odometers expenses: %s", + id_vehicle, + expenses_odometers, + ) - _LOGGER.debug("API Response Data Vehicle - %s: %s", info, response) - return None + odometers = refuellings_odometers + odometers.extend(services_odometers) + odometers.extend(expenses_odometers) + + odometers = sorted(odometers, key=sort_by_key, reverse=True) + + _LOGGER.debug( + "API Response Data Vehicle %s - odometers: %s", + id_vehicle, + odometers, + ) + + odometer_last = None + odometer_date_last = None + if len(odometers) > 0: + odometer_last = odometers[0]["odometro"] + odometer_date_last = odometers[0]["data"] + + data_return = DrivvoDataVehicle( + id=id_vehicle, + name=name, + identification=identification, + placa=placa, + odometer=odometer_last, + distance_unit=distance_unit, + odometer_date=odometer_date_last, + manufacturer=api_data_vehicle["marca"], + model=api_data_vehicle["modelo"], + refuelling_date=refuelling_date, + refuelling_last_average=refuelling_last_average, + refuelling_general_average=refuelling_general_average, + refuelling_station=refuelling_station, + refuelling_type=refuelling_type, + refuelling_value=refuelling_value, + refuelling_distance=refuelling_distance, + refuelling_reason=refuelling_reason, + refuelling_price=refuelling_price, + refuelling_total=refuelling_total, + refuelling_value_total=refuelling_value_total, + refuelling_tank_full=refuelling_tank_full, + refuelling_price_lowest=refuelling_price_lowest, + refuelling_volume=refuelling_volume, + refuelling_volume_total=refuelling_volume_total, + ) + + _LOGGER.debug("API Response Data Vehicle - Refuelling: %s", data_return) + return data_return + + +@dataclasses.dataclass +class DrivvoDataVehicle: + """Data parsed from the API.""" + + id: int + name: str | None + identification: str | None + placa: str | None + odometer: int | None + odometer_date: str | None + manufacturer: str + model: str + refuelling_date: str | None + refuelling_last_average: float | None + refuelling_general_average: float | None + refuelling_station: str | None + refuelling_type: str | None + refuelling_value: float | None + refuelling_distance: int | None + refuelling_reason: str | None + refuelling_price: float | None + refuelling_volume: float | None + refuelling_total: int | None + refuelling_value_total: float | None + refuelling_tank_full: bool | None + refuelling_price_lowest: float | None + distance_unit: str + refuelling_volume_total: float | None diff --git a/custom_components/drivvo/manifest.json b/custom_components/drivvo/manifest.json index ba692f4..2e584ac 100644 --- a/custom_components/drivvo/manifest.json +++ b/custom_components/drivvo/manifest.json @@ -10,5 +10,5 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/hudsonbrendon/sensor.drivvo/issues", "requirements": [], - "version": "1.0.4" -} + "version": "1.0.5" +} \ No newline at end of file diff --git a/custom_components/drivvo/sensor.py b/custom_components/drivvo/sensor.py index 52a31ab..b4e5fe4 100755 --- a/custom_components/drivvo/sensor.py +++ b/custom_components/drivvo/sensor.py @@ -50,26 +50,15 @@ async def async_setup_entry( user=config[CONF_EMAIL], password=config[CONF_PASSWORD], id_vehicle=vehicle, - info="base", ) ) is not None: - if vehicle_data["nome"] is not None and vehicle_data["nome"] != "": - vehicle_name = vehicle_data["nome"] - elif vehicle_data["placa"] is not None and vehicle_data["placa"] != "": - vehicle_name = vehicle_data["placa"] - else: - vehicle_name = f"{vehicle_data['marca']}/{vehicle_data['modelo']}" - async_add_entities( [ DrivvoSensor( hass, config[CONF_EMAIL], - vehicle_name, - vehicle_data["marca"], - vehicle_data["modelo"], - vehicle, config[CONF_PASSWORD], + vehicle_data, SCAN_INTERVAL, ) ], @@ -122,32 +111,27 @@ async def async_setup_platform( class DrivvoSensor(Entity): - def __init__(self, hass, email, name, marca, model, id_vehicle, password, interval): + def __init__(self, hass, email, password, data, interval): """Inizialize sensor.""" + self._attr_unique_id = f"{data.id}_refuellings" self._attr_has_entity_name = True + self._attr_translation_key = "refuellings" self._state = STATE_UNKNOWN self._hass = hass self._interval = interval self._email = email self._password = password - self._model = f"{marca}/{model}" - self._id_vehicle = id_vehicle - self._name = "Abastecimento" - self._supplies = [] - self._attr_unique_id = f"{id_vehicle}_abastecimento" + self._model = f"{data.manufacturer}/{data.model}" + self._id_vehicle = data.id self._attr_device_info = DeviceInfo( entry_type=dr.DeviceEntryType.SERVICE, - identifiers={(DOMAIN, id_vehicle)}, + identifiers={(DOMAIN, data.id)}, default_manufacturer="Drivvo", - name=name, - default_model=f"{marca}/{model}", + name=data.identification, + default_model=self._model, configuration_url="https://web.drivvo.com/", ) - - @property - def name(self): - """Return the name sensor.""" - return self._name + self.data = data @property def icon(self): @@ -157,101 +141,36 @@ def icon(self): @property def state(self): """Retorna o número de abastecimentos até então.""" - return len(self._supplies) - - @property - def supply(self): - """Abastecimento.""" - if len(self._supplies) > 0: - return self._supplies[0] - return None - - @property - def total_payment(self): - """Soma total de valores pagos em todos os abastecimentos.""" - if len(self._supplies) > 0: - total = 0 - for supply in self._supplies: - total += supply["valor_total"] - return total - return None - - @property - def km_travel(self): - """Km percorridos desde o ultimo abastecimento.""" - if len(self._supplies) > 0: - km = 0 - odometers = [supply["odometro"] for supply in self._supplies] - if len(odometers) > 1: - km = odometers[0] - odometers[1] - return km - return None - - @property - def cheapest_gasoline_until_today(self): - """Gasolina mais barata até hoje.""" - if len(self._supplies) > 0: - return min([supply["preco"] for supply in self._supplies]) - return None - - @property - def total_amount_of_supplies(self): - """Número total de abastetimentos.""" - if len(self._supplies) > 0: - return len(self._supplies) - return 0 + return self.data.refuelling_total @property def extra_state_attributes(self): """Atributos.""" - veiculo = self._model - soma_total_de_abastecimentos = self.total_amount_of_supplies - soma_total_de_valores_pagos_em_todos_os_abastecimentos = self.total_payment - km_percorridos_desde_o_ultimo_abastecimento = self.km_travel - gasolina_mais_barata_ate_entao = self.cheapest_gasoline_until_today - odometro = None - tipo_de_combustivel = None - motivo_do_abastecimento = None - data_do_abastecimento = None - valor_total_pago = None - preco_do_combustivel = None - encheu_o_tanque = None - posto = None - if self.supply is not None: - odometro = self.supply["odometro"] - tipo_de_combustivel = self.supply.get("combustivel") - motivo_do_abastecimento = self.supply.get("tipo_motivo") - data_do_abastecimento = self.supply.get("data") - valor_total_pago = self.supply["valor_total"] - preco_do_combustivel = self.supply["preco"] - encheu_o_tanque = "Sim" if self.supply.get("tanque_cheio") else "Não" - posto_combustivel = self.supply["posto_combustivel"] - if (posto_combustivel is not None) and ("nome" in posto_combustivel): - posto = posto_combustivel["nome"] - return { - "veiculo": veiculo, - "odometro": odometro, - "posto": posto, - "tipo_de_combustivel": tipo_de_combustivel, - "motivo_do_abastecimento": motivo_do_abastecimento, - "data_do_abastecimento": data_do_abastecimento, - "valor_total_pago": valor_total_pago, - "preco_do_combustivel": preco_do_combustivel, - "soma_total_de_abastecimentos": soma_total_de_abastecimentos, - "soma_total_de_valores_pagos_em_todos_os_abastecimentos": soma_total_de_valores_pagos_em_todos_os_abastecimentos, - "encheu_o_tanque": encheu_o_tanque, - "km_percorridos_desde_o_ultimo_abastecimento": km_percorridos_desde_o_ultimo_abastecimento, - "gasolina_mais_barata_ate_entao": gasolina_mais_barata_ate_entao, + "veiculo": self._model, + "odometro": self.data.odometer, + "data_odometro": self.data.odometer_date, + "ultima_media": self.data.refuelling_last_average, + "media_geral": self.data.refuelling_general_average, + "posto": self.data.refuelling_station, + "tipo_de_combustivel": self.data.refuelling_type, + "motivo_do_abastecimento": self.data.refuelling_reason, + "data_do_abastecimento": self.data.refuelling_date, + "valor_total_pago": self.data.refuelling_value, + "preco_do_combustivel": self.data.refuelling_price, + "soma_total_de_abastecimentos": self.data.refuelling_total, + "soma_total_de_valores_pagos_em_todos_os_abastecimentos": self.data.refuelling_value_total, + "encheu_o_tanque": self.data.refuelling_tank_full, + "km_percorridos_desde_o_ultimo_abastecimento": self.data.refuelling_distance, + "gasolina_mais_barata_ate_entao": self.data.refuelling_price_lowest, } async def async_update(self): """Atualiza os dados fazendo requisição na API.""" - self._supplies = await get_data_vehicle( + self.data = await get_data_vehicle( hass=self.hass, user=self._email, password=self._password, id_vehicle=self._id_vehicle, - info="abastecimento", ) diff --git a/custom_components/drivvo/translations/en.json b/custom_components/drivvo/translations/en.json index 1d51003..ebff27f 100644 --- a/custom_components/drivvo/translations/en.json +++ b/custom_components/drivvo/translations/en.json @@ -29,6 +29,67 @@ "reauth_successful": "Reauthentication successful." } }, + "entity": { + "sensor": { + "refuellings": { + "name": "Refuellings", + "state_attributes": { + "veiculo": { + "name": "Vehicle" + }, + "odometro": { + "name": "Last odometer" + }, + "data_odometro": { + "name": "Date of last odometer" + }, + "ultima_media": { + "name": "Last fuel average" + }, + "media_geral": { + "name": "General fuel average" + }, + "posto": { + "name": "Fuel station" + }, + "tipo_de_combustivel": { + "name": "Type of fuel" + }, + "motivo_do_abastecimento": { + "name": "Refuelling reason" + }, + "data_do_abastecimento": { + "name": "Date refuelling" + }, + "valor_total_pago": { + "name": "Value refuelling" + }, + "preco_do_combustivel": { + "name": "Fuel price" + }, + "soma_total_de_abastecimentos": { + "name": "Total refuellings" + }, + "soma_total_de_valores_pagos_em_todos_os_abastecimentos": { + "name": "Total value refuellings" + }, + "encheu_o_tanque": { + "name": "Tank full", + "state": { + "false": "No", + "true": "Yes" + } + }, + "km_percorridos_desde_o_ultimo_abastecimento": { + "name": "Distance traveled since the last refueling" + }, + "gasolina_mais_barata_ate_entao": { + "name": "Cheaper fuel" + } + } + } + } + }, "issues": { "yaml_deprecated": { "title": "Configuration of the Drivvo in YAML is deprecated", diff --git a/custom_components/drivvo/translations/pt-BR.json b/custom_components/drivvo/translations/pt-BR.json index 5653d21..12caf25 100644 --- a/custom_components/drivvo/translations/pt-BR.json +++ b/custom_components/drivvo/translations/pt-BR.json @@ -29,6 +29,67 @@ "reauth_successful": "Reautenticação realizada com sucesso." } }, + "entity": { + "sensor": { + "refuellings": { + "name": "Abastecimentos", + "state_attributes": { + "veiculo": { + "name": "Veículo" + }, + "odometro": { + "name": "Último odômetro" + }, + "data_odometro": { + "name": "Data do último odômetro" + }, + "ultima_media": { + "name": "Última média de combustível" + }, + "media_geral": { + "name": "Média geral de combustível" + }, + "posto": { + "name": "Posto de combustível" + }, + "tipo_de_combustivel": { + "name": "Tipo de Combustível" + }, + "motivo_do_abastecimento": { + "name": "Motivo do reabastecimento" + }, + "data_do_abastecimento": { + "name": "Data do reabastecimento" + }, + "valor_total_pago": { + "name": "Valor do reabastecimento" + }, + "preco_do_combustivel": { + "name": "Preço do combustível" + }, + "soma_total_de_abastecimentos": { + "name": "Total de abastecimentos" + }, + "soma_total_de_valores_pagos_em_todos_os_abastecimentos": { + "name": "Valor total dos abastecimentos" + }, + "encheu_o_tanque": { + "name": "Encheu o tanque", + "state": { + "false": "Não", + "true": "Sim" + } + }, + "km_percorridos_desde_o_ultimo_abastecimento": { + "name": "Distância percorrida desde o último abastecimento" + }, + "gasolina_mais_barata_ate_entao": { + "name": "Combustível mais barato" + } + } + } + } + }, "issues": { "yaml_deprecated": { "title": "A configuração YAML para o Drivvo está depreciada",