From 16def17ae0d8f0d04f7de5d1a64950c01f864e59 Mon Sep 17 00:00:00 2001 From: myTselection Date: Sat, 5 Aug 2023 23:36:55 +0200 Subject: [PATCH] 7.1.0 select individual gas station in config flow --- README.md | 3 +- custom_components/carbu_com/config_flow.py | 58 +++++++++++++- custom_components/carbu_com/manifest.json | 2 +- custom_components/carbu_com/sensor.py | 75 +++++++++++-------- .../carbu_com/translations/en.json | 12 +++ .../carbu_com/translations/fr.json | 12 +++ .../carbu_com/translations/nl.json | 12 +++ custom_components/carbu_com/utils.py | 14 +++- 8 files changed, 147 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 90bd031..0102713 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ For electricity price expectations [this Entso-E HACS integration](https://githu - Restart Home Assistant - Add 'Carbu.com' integration via HA Settings > 'Devices and Services' > 'Integrations' - Provide country, postal code and select the desired sensors - - If your postal code is not unique, the name of the town can be selected from the dropdown in the next step of the setup config flow. See [carbu.com](https://carbu.com) website for known towns and postal codes. (Only for BE/FR/LU) + - The name of the town can be selected from the dropdown in the next step of the setup config flow. See [carbu.com](https://carbu.com) website for known towns and postal codes. (Only for BE/FR/LU) + - For BE/FR/LU: an extra checkbox can be set to select a specific individual gas station. If set, a station can be selected in a dropdown with known gas stations close to the provided postalcode and town. No sensor for 5km and 10km will be made available, only the price sensor for the individual selected station. - For Italy & Netherlands, the town will be requested in the second step of the config flow - A filter on supplier brand name can be set (optional). If the filter match, the fuel station will be considered, else next will be searched. A python regex filter value be set diff --git a/custom_components/carbu_com/config_flow.py b/custom_components/carbu_com/config_flow.py index f0a4271..722c231 100644 --- a/custom_components/carbu_com/config_flow.py +++ b/custom_components/carbu_com/config_flow.py @@ -82,10 +82,13 @@ def create_town_carbu_schema(towns): vol.Required("town", description="Town") ] = selector({ "select": { - "options": towns, + "options": [item['n'] for item in towns], "mode": "dropdown" } }) + data_schema[ + vol.Optional("individualstation", default=False, description="Select an individual station") + ] = bool data_schema[ vol.Optional(FuelType.OILSTD.name_lowercase, default=default_oilstd, description="Standard oil sensors") ] = bool @@ -98,6 +101,23 @@ def create_town_carbu_schema(towns): return data_schema + +def create_station_carbu_schema(stations): + """Create a default schema based on if a option or if settings + is already filled out. + """ + data_schema = OrderedDict() + data_schema[ + vol.Required("station", description="Fuel station") + ] = selector({ + "select": { + "options": [f"{item['name']}, {item['address']}" for item in stations], + "mode": "dropdown" + } + }) + + return data_schema + def create_town_schema(): """Create a default schema based on if a option or if settings is already filled out. @@ -119,6 +139,7 @@ class ComponentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): _init_info = {} _carbuLocationInfo = {} _towns = [] + _stations = [] _session = None def __init__(self): @@ -138,7 +159,7 @@ async def async_step_user(self, user_input=None): # pylint: disable=dangerous-d self._towns = [] carbuLocationInfo = await self.hass.async_add_executor_job(lambda: self._session.convertPostalCodeMultiMatch(user_input.get('postalcode'), user_input.get('country'))) for location in carbuLocationInfo: - self._towns.append(location.get('n')) + self._towns.append(location) _LOGGER.debug(f"carbuLocationInfo: {carbuLocationInfo} towns {self._towns}") return await self.async_step_town_carbu() # Other countries: get town @@ -152,11 +173,26 @@ async def _show_config_form(self, user_input): return self.async_show_form( step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors ) + + def find_town_by_name(self, target_name): + for item in self._towns: + if item.get('n') == target_name: + return item + return None # If the name is not found in the list async def async_step_town_carbu(self, user_input=None): # pylint: disable=dangerous-default-value """Handle a flow initialized by the user.""" if user_input is not None: self._init_info.update(user_input) + # _LOGGER.debug(f"individualstation: {user_input.get('individualstation')}") + if user_input.get('individualstation') == True: + town = self.find_town_by_name(user_input.get('town')) + self._stations = [] + carbuStationInfo = await self.hass.async_add_executor_job(lambda: self._session.getFuelPrices(self._init_info.get('postalcode'), self._init_info.get('country'), self._init_info.get('town'), town.get('id'), FuelType.DIESEL, False)) + for station in carbuStationInfo: + self._stations.append(station) + _LOGGER.debug(f"stations: {self._stations}") + return await self.async_step_station_carbu() return self.async_create_entry(title=NAME, data=self._init_info) return await self._show_town_carbu_config_form(self._towns) @@ -165,15 +201,29 @@ async def _show_town_carbu_config_form(self, towns): """Show the configuration form to edit location data.""" data_schema = create_town_carbu_schema(towns) return self.async_show_form( - step_id="town", data_schema=vol.Schema(data_schema), errors=self._errors + step_id="town_carbu", data_schema=vol.Schema(data_schema), errors=self._errors ) - async def async_step_town(self, user_input=None): # pylint: disable=dangerous-default-value + async def async_step_station_carbu(self, user_input=None): # pylint: disable=dangerous-default-value """Handle a flow initialized by the user.""" if user_input is not None: self._init_info.update(user_input) return self.async_create_entry(title=NAME, data=self._init_info) + return await self._show_station_carbu_config_form(self._stations) + + async def _show_station_carbu_config_form(self, stations): + """Show the configuration form to edit location data.""" + data_schema = create_station_carbu_schema(stations) + return self.async_show_form( + step_id="station_carbu", data_schema=vol.Schema(data_schema), errors=self._errors + ) + + async def async_step_town(self, user_input=None): # pylint: disable=dangerous-default-value + """Handle a flow initialized by the user.""" + if user_input is not None: + self._init_info.update(user_input) + return self.async_create_entry(title=NAME, data=self._init_info) return await self._show_town_config_form(self._towns) async def _show_town_config_form(self, towns): diff --git a/custom_components/carbu_com/manifest.json b/custom_components/carbu_com/manifest.json index caf7c3e..963d93b 100644 --- a/custom_components/carbu_com/manifest.json +++ b/custom_components/carbu_com/manifest.json @@ -9,5 +9,5 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/myTselection/carbu_com/issues", "requirements": ["bs4","requests","ratelimit"], - "version": "7.0.3" + "version": "7.1.0" } diff --git a/custom_components/carbu_com/sensor.py b/custom_components/carbu_com/sensor.py index 7dbe55f..ee064ca 100644 --- a/custom_components/carbu_com/sensor.py +++ b/custom_components/carbu_com/sensor.py @@ -50,6 +50,8 @@ async def dry_setup(hass, config_entry, async_add_devices): postalcode = config.get("postalcode") town = config.get("town") filter = config.get("filter") + individualstation = config.get("individualstation", False) + station = config.get("station","") super95 = config.get(FuelType.SUPER95.name_lowercase) super98 = config.get(FuelType.SUPER98.name_lowercase) diesel = config.get(FuelType.DIESEL.name_lowercase) @@ -66,20 +68,23 @@ async def dry_setup(hass, config_entry, async_add_devices): ) await componentData._forced_update() assert componentData._price_info is not None - + + # _LOGGER.debug(f"postalcode {postalcode} station: {station} individualstation {individualstation}") + if super95: - sensorSuper95 = ComponentPriceSensor(componentData, FuelType.SUPER95, postalcode, False, 0) + sensorSuper95 = ComponentPriceSensor(componentData, FuelType.SUPER95, postalcode, False, 0, station) # await sensorSuper95.async_update() sensors.append(sensorSuper95) - sensorSuper95Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER95, postalcode, 5) - # await sensorSuper95Neigh.async_update() - sensors.append(sensorSuper95Neigh) - - sensorSuper95Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER95, postalcode, 10) - # await sensorSuper95Neigh.async_update() - sensors.append(sensorSuper95Neigh) - + if not individualstation: + sensorSuper95Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER95, postalcode, 5) + # await sensorSuper95Neigh.async_update() + sensors.append(sensorSuper95Neigh) + + sensorSuper95Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER95, postalcode, 10) + # await sensorSuper95Neigh.async_update() + sensors.append(sensorSuper95Neigh) + if country.lower() in ['be','fr','lu']: sensorSuper95Prediction = ComponentFuelPredictionSensor(componentData, FuelType.SUPER95_Prediction) # await sensorSuper95Prediction.async_update() @@ -87,36 +92,38 @@ async def dry_setup(hass, config_entry, async_add_devices): if super98: - sensorSuper98 = ComponentPriceSensor(componentData, FuelType.SUPER98, postalcode, False, 0) + sensorSuper98 = ComponentPriceSensor(componentData, FuelType.SUPER98, postalcode, False, 0, station) # await sensorSuper95.async_update() sensors.append(sensorSuper98) - sensorSuper98Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER98, postalcode, 5) - # await sensorSuper95Neigh.async_update() - sensors.append(sensorSuper98Neigh) - - sensorSuper98Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER98, postalcode, 10) - # await sensorSuper95Neigh.async_update() - sensors.append(sensorSuper98Neigh) + if not individualstation: + sensorSuper98Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER98, postalcode, 5) + # await sensorSuper95Neigh.async_update() + sensors.append(sensorSuper98Neigh) + + sensorSuper98Neigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.SUPER98, postalcode, 10) + # await sensorSuper95Neigh.async_update() + sensors.append(sensorSuper98Neigh) if diesel: - sensorDiesel = ComponentPriceSensor(componentData, FuelType.DIESEL, postalcode, False, 0) + sensorDiesel = ComponentPriceSensor(componentData, FuelType.DIESEL, postalcode, False, 0, station) # await sensorDiesel.async_update() sensors.append(sensorDiesel) - sensorDieselNeigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.DIESEL, postalcode, 5) - # await sensorDieselNeigh.async_update() - sensors.append(sensorDieselNeigh) + if not individualstation: + sensorDieselNeigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.DIESEL, postalcode, 5) + # await sensorDieselNeigh.async_update() + sensors.append(sensorDieselNeigh) + + sensorDieselNeigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.DIESEL, postalcode, 10) + # await sensorDieselNeigh.async_update() + sensors.append(sensorDieselNeigh) - sensorDieselNeigh = ComponentPriceNeighborhoodSensor(componentData, FuelType.DIESEL, postalcode, 10) - # await sensorDieselNeigh.async_update() - sensors.append(sensorDieselNeigh) - if country.lower() in ['be','fr','lu']: sensorDieselPrediction = ComponentFuelPredictionSensor(componentData, FuelType.DIESEL_Prediction) # await sensorDieselPrediction.async_update() sensors.append(sensorDieselPrediction) - + if oilstd and country.lower() in ['be','fr','lu']: sensorOilstd = ComponentPriceSensor(componentData, FuelType.OILSTD, postalcode, True, quantity) # await sensorOilstd.async_update() @@ -229,8 +236,8 @@ async def get_oil_price_prediction_info(self): self._price_info[FuelType.OILEXTRA_Prediction] = prediction_info _LOGGER.debug(f"{NAME} prediction_info oilPrediction {prediction_info}") - async def getStationInfoFromPriceInfo(self, priceinfo, postalcode, fueltype, max_distance, filter): - stationInfo = await self._hass.async_add_executor_job(lambda: self._session.getStationInfoFromPriceInfo(priceinfo, postalcode, fueltype, max_distance, filter)) + async def getStationInfoFromPriceInfo(self, priceinfo, postalcode, fueltype, max_distance, filter, individual_station=""): + stationInfo = await self._hass.async_add_executor_job(lambda: self._session.getStationInfoFromPriceInfo(priceinfo, postalcode, fueltype, max_distance, filter, individual_station)) return stationInfo @@ -304,12 +311,13 @@ def clear_session(self): class ComponentPriceSensor(Entity): - def __init__(self, data, fueltype: FuelType, postalcode, isOil, quantity): + def __init__(self, data, fueltype: FuelType, postalcode, isOil, quantity, individual_station = ""): self._data = data self._fueltype = fueltype self._postalcode = postalcode self._isOil = isOil self._quantity = quantity + self._individual_station = individual_station self._last_update = None self._price = None @@ -326,6 +334,7 @@ def __init__(self, data, fueltype: FuelType, postalcode, isOil, quantity): self._date = None self._score = None self._country = data._country + self._id = None @property def state(self): @@ -353,7 +362,8 @@ async def async_update(self): self._date = self._priceinfo.get("data")[0].get("available").get("visible")# x.data[0].available.visible # self._quantity = self._priceinfo.get("data")[0].get("quantity") else: - stationInfo = await self._data.getStationInfoFromPriceInfo(self._priceinfo, self._postalcode, self._fueltype, 0, self._data._filter) + # _LOGGER.debug(f'indiv. station: {self._individual_station}') + stationInfo = await self._data.getStationInfoFromPriceInfo(self._priceinfo, self._postalcode, self._fueltype, 0, self._data._filter, self._individual_station) # stationInfo = await self._data._hass.async_add_executor_job(lambda: self._data._session.getStationInfoFromPriceInfo(self._priceinfo, self._postalcode, self._fueltype, 0, self._data._filter)) self._price = stationInfo.get("price") self._supplier = stationInfo.get("supplier") @@ -419,7 +429,8 @@ def extra_state_attributes(self) -> dict: "quantity": self._quantity, "score": self._score, "filter": self._data._filter, - "country": self._country + "country": self._country, + "id": self._id # "suppliers": self._priceinfo } diff --git a/custom_components/carbu_com/translations/en.json b/custom_components/carbu_com/translations/en.json index ec70bd5..060d810 100644 --- a/custom_components/carbu_com/translations/en.json +++ b/custom_components/carbu_com/translations/en.json @@ -14,13 +14,25 @@ } }, "town": { + "data": { + "town": "Town" + } + }, + "town_carbu": { "data": { "town": "Town", + "individualstation": "Select an individual gas station", "oilstd": "Mazout Heating Oil standard (50S)", "oilextra": "Mazout Heating Oil extra", "quantity": "Mazout Heating Oil quanitity (liters)" } }, + "station_carbu": { + "description": "Select an individual gas station", + "data": { + "station": "Fuel station" + } + }, "edit": { "description": "Setup Carbu.com and Mazout.com sensors.", "data": { diff --git a/custom_components/carbu_com/translations/fr.json b/custom_components/carbu_com/translations/fr.json index 0a25ffa..9cc5688 100644 --- a/custom_components/carbu_com/translations/fr.json +++ b/custom_components/carbu_com/translations/fr.json @@ -14,13 +14,25 @@ } }, "town": { + "data": { + "town": "Ville" + } + }, + "town_carbu": { "data": { "town": "Ville", + "individualstation": "Sélectionnez une station-service individuelle", "oilstd": "Fioul Standard (50S)", "oilextra": "Fioul Extra", "quantity": "Quantité Fioul (liters)" } }, + "station_carbu": { + "description": "Sélectionnez une station-service individuelle", + "data": { + "station": "Fuel station" + } + }, "edit": { "description": "Configuration des capteurs Carbu.com et Mazout.com.", "data": { diff --git a/custom_components/carbu_com/translations/nl.json b/custom_components/carbu_com/translations/nl.json index d5041ec..60705ca 100644 --- a/custom_components/carbu_com/translations/nl.json +++ b/custom_components/carbu_com/translations/nl.json @@ -14,13 +14,25 @@ } }, "town": { + "data": { + "town": "Stad" + } + }, + "town_carbu": { "data": { "town": "Stad", + "individualstation": "Selecteer een individueel tankstation", "oilstd": "Standaard Mazout Verwarmingsolie (50S)", "oilextra": "Extra Mazout Verwarmingsolie", "quantity": "Hoeveelheid Mazout Verwarmingsolie (liters)" } }, + "station_carbu": { + "description": "Selecteer een individueel tankstation", + "data": { + "station": "Tank station" + } + }, "edit": { "description": "Configureer Carbu.com en Mazout.com sensoren.", "data": { diff --git a/custom_components/carbu_com/utils.py b/custom_components/carbu_com/utils.py index 007cbc5..daf54d0 100644 --- a/custom_components/carbu_com/utils.py +++ b/custom_components/carbu_com/utils.py @@ -350,7 +350,7 @@ def getFuelPricesNL(self, postalcode, country, town, locationinfo, fueltype: Fue nl_prices = response.json() all_stations.extend(nl_prices) - _LOGGER.debug(f"NL All station data retrieved: {all_stations}") + # _LOGGER.debug(f"NL All station data retrieved: {all_stations}") stationdetails = [] for block in all_stations: @@ -539,6 +539,7 @@ def getOilPrice(self, locationinfo, volume, oiltypecode): namespace = api_details.get("api").get("namespace") offers = api_details.get("api").get("routes").get("offers") #x.api.routes.offers oildetails_url = f"{url}{namespace}{offers}?api_key={api_key}&sk={sk}&areaCode={locationinfo}&productId={oiltypecode}&quantity={volume}&locale=nl-BE" + _LOGGER.debug(f"oildetails_url: {oildetails_url}") response = self.s.get(oildetails_url,headers=header,timeout=30, verify=False) if response.status_code != 200: @@ -628,7 +629,7 @@ def getStationInfoLatLon(self,latitude, longitude, fuel_type: FuelType, max_dist return self.getStationInfoFromPriceInfo(price_info, postal_code_country[0], fuel_type, max_distance, filter) - def getStationInfoFromPriceInfo(self,price_info, postalcode, fuel_type: FuelType, max_distance=0, filter=""): + def getStationInfoFromPriceInfo(self,price_info, postalcode, fuel_type: FuelType, max_distance=0, filter="", individual_station=""): data = { "price" : None, "distance" : 0, @@ -650,7 +651,8 @@ def getStationInfoFromPriceInfo(self,price_info, postalcode, fuel_type: FuelType "fuelname" : None, "fueltype" : fuel_type, "date" : None, - "country": None + "country": None, + "id": None } # _LOGGER.debug(f"getStationInfoFromPriceInfo {fuel_type.name}, postalcode: {postalcode}, max_distance : {max_distance}, filter: {filter}, price_info: {price_info}") @@ -664,6 +666,11 @@ def getStationInfoFromPriceInfo(self,price_info, postalcode, fuel_type: FuelType match = re.search(filterSet, station.get("brand").lower()) if not match: continue + if individual_station != "": + # _LOGGER.debug(f"utils individual_station: {station.get('name')}, {station.get('address')}") + if f"{station.get('name')}, {station.get('address')}" != individual_station: + continue + # if max_distance == 0 and str(postalcode) not in station.get("address"): # break try: @@ -692,6 +699,7 @@ def getStationInfoFromPriceInfo(self,price_info, postalcode, fuel_type: FuelType if data["postalcode"] not in data["postalcodes"]: data["postalcodes"].append(data["postalcode"]) data['country'] = station.get('country') + data['id'] = station.get('id') # _LOGGER.debug(f"before break {max_distance}, country: {station.get('country') }, postalcode: {station.get('postalcode')} required postalcode {postalcode}") if max_distance == 0: # _LOGGER.debug(f"break {max_distance}, country: {station.get('country') }, postalcode: {station.get('postalcode')} required postalcode {postalcode}")