-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
Add Google Air Quality integration #145237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
return self.async_abort( | ||
reason="access_not_configured", | ||
description_placeholders={ | ||
"message": f"Missing required scope: {OAUTH2_SCOPE}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this in a placeholder? How does one add this?
async def async_step_coordinates( | ||
self, user_input: dict[str, Any] | ||
) -> ConfigFlowResult: | ||
"""Handle coordinate input and create the config entry.""" | ||
session = aiohttp_client.async_get_clientsession(self.hass) | ||
auth = api.AsyncConfigFlowAuth( | ||
session, self._oauth_data[CONF_TOKEN][CONF_ACCESS_TOKEN] | ||
) | ||
client = GoogleAirQualityApi(auth) | ||
location = user_input[CONF_LOCATION] | ||
lat = location[CONF_LATITUDE] | ||
lon = location[CONF_LONGITUDE] | ||
|
||
try: | ||
user_resource_info = await client.async_air_quality(lat, lon) | ||
except NoDataForLocationError: | ||
return self._show_form_user( | ||
user_input, | ||
errors={"base": "no_data_for_location"}, | ||
) | ||
except GoogleAirQualityApiError as ex: | ||
return self.async_abort( | ||
reason="access_not_configured", | ||
description_placeholders={"message": str(ex)}, | ||
) | ||
except Exception: | ||
self.logger.exception("Unknown error occurred") | ||
return self.async_abort(reason="unknown") | ||
|
||
unique_id = f"{lat}_{lon}" | ||
|
||
await self.async_set_unique_id(unique_id) | ||
self._oauth_data.update( | ||
{ | ||
CONF_LATITUDE: lat, | ||
CONF_LONGITUDE: lon, | ||
"region_code": user_resource_info.region_code, | ||
} | ||
) | ||
self._abort_if_unique_id_configured() | ||
return self.async_create_entry( | ||
title=f"Coordinates {lat}, {lon}", data=self._oauth_data | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to use subentries here? This way we can use the same account for multiple locations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I have implemented it here.
I haven't used subentries before and didn't find any examples in combination with a coordinator. So I'm happy about feedback.
"carbon_monoxide": { | ||
"default": "mdi:molecule-co" | ||
}, | ||
"nitrogen_dioxide": { | ||
"default": "mdi:molecule" | ||
}, | ||
"ozone": { | ||
"default": "mdi:molecule" | ||
}, | ||
"sulphur_dioxide": { | ||
"default": "mdi:molecule" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aren't these all device classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, they are, but the API delivers other units, than in the device classes allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do they provide?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CO is now a device class.
The API provides ppb for NO2, O3 and SO2. But home assistant requires µg/m³
"excellent_air_quality", | ||
"good_air_quality", | ||
"low_air_quality", | ||
"moderate_air_quality", | ||
"poor_air_quality", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
strip _air_quality
imo, easier to template with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some regions report for example moderate_air_quality
and some moderate_air_pollution
. So the stripped version can be confusing.
self._attr_translation_placeholders = { | ||
"local_aqi": coordinator.data.indexes[1].display_name | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the local AQI for the country, in which the selected coordinates are.
For Dutch it would for example be: LKI (NL)
https://developers.google.com/maps/documentation/air-quality/laqis?hl=de
"1_blue": "1 - Blue", | ||
"1_green": "1 - Green", | ||
"10_33_percent_of_guideline": "10-33% of guideline", | ||
"1a_very_good_air_quality": "1A - Very good air quality", | ||
"1b_good_air_quality": "1B - Good air quality", | ||
"2_cyan": "2 - Cyan", | ||
"2_light_green": "2 - Light green", | ||
"2a_acceptable_air_quality": "2A - Acceptable air quality", | ||
"2b_acceptable_air_quality": "2B - Acceptable air quality", | ||
"3_green": "3 - Green", | ||
"3_yellow": "3 - Yellow", | ||
"33_66_percent_of_guideline": "33-66% of guideline", | ||
"3a_aggravated_air_quality": "3A - Aggravated air quality", | ||
"3b_bad_air_quality": "3B - Bad air quality", | ||
"4_orange": "4 - Orange", | ||
"4_yellow_watch": "4 - Yellow/Watch", | ||
"5_orange_alert": "5 - Orange/Alert", | ||
"5_red": "5 - Red", | ||
"6_red_alert_plus": "6 - Red/Alert+", | ||
"66_100_percent_of_guideline": "66-100% of guideline", | ||
"above_average_air_pollution": "Above average air pollution", | ||
"acceptable": "Acceptable", | ||
"acceptable_air_quality": "Acceptable air quality", | ||
"acutely_unhealthy_air_quality": "Acutely unhealthy air quality", | ||
"alarm_level": "Alarm level", | ||
"alert_level": "Alert level", | ||
"alert_threshold": "Alert threshold", | ||
"average_air_pollution": "Average air pollution", | ||
"average_air_quality": "Average air quality", | ||
"bad_air_quality": "Bad air quality", | ||
"below_average_air_pollution": "Below average air pollution", | ||
"caution": "Caution", | ||
"clean": "Clean", | ||
"considerable_air_pollution": "Considerable air pollution", | ||
"degraded_air_quality": "Degraded air quality", | ||
"desirable_air_quality": "Desirable air quality", | ||
"emergency": "Emergency", | ||
"emergency_level": "Emergency level", | ||
"evident_air_pollution": "Evident air pollution", | ||
"excellent": "Excellent", | ||
"excellent_air_quality": "[%key:component::google_air_quality::entity::sensor::uaqi_category::state::excellent_air_quality%]", | ||
"extremely_bad_air_quality": "Extremely bad air quality", | ||
"extremely_poor_air_quality": "Extremely poor air quality", | ||
"extremely_unfavorable_air_quality": "Extremely unfavorable air quality", | ||
"extremely_unhealthy_air_quality": "Extremely unhealthy air quality", | ||
"fair_air_quality": "Fair air quality", | ||
"fairly_good_air_quality": "Fairly good air quality", | ||
"good": "Good", | ||
"good_air_quality": "[%key:component::google_air_quality::entity::sensor::uaqi_category::state::good_air_quality%]", | ||
"greater_than_100_percent_of_guideline": "Greater than 100% of guideline", | ||
"hazardous_air_quality": "Hazardous air quality", | ||
"heavily_polluted": "Heavily Polluted", | ||
"heavy_air_pollution": "Heavy air pollution", | ||
"high_air_pollution": "High air pollution", | ||
"high_health_risk": "High health risk", | ||
"high_pollution": "High pollution", | ||
"horrible_air_quality": "Horrible air quality", | ||
"less_than_10_percent_of_guideline": "Less than 10% of guideline", | ||
"light_air_pollution": "Light air pollution", | ||
"low_air_pollution": "Low air pollution", | ||
"low_air_quality": "[%key:component::google_air_quality::entity::sensor::uaqi_category::state::low_air_quality%]", | ||
"low_health_risk": "Low health risk", | ||
"low_pollution": "Low pollution", | ||
"medium_air_pollution": "Medium air pollution", | ||
"medium_air_quality": "Medium air quality", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What in the world does this mean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the different possible values for the air quality in all the countries.
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Test errors are unrelated |
Breaking change
Proposed change
This PR introduces a new integration for the Google Air Quality API, allowing Home Assistant users to retrieve real-time air quality data based on geographic coordinates.

The integration uses this library: https://github.com/Thomas55555/python-google-air-quality-api
The documentation for the API can be found here: https://developers.google.com/maps/documentation/air-quality
Type of change
Additional information
Checklist
ruff format homeassistant tests
)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest
.requirements_all.txt
.Updated by running
python3 -m script.gen_requirements_all
.To help with the load of incoming pull requests: