Skip to content

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

Draft
wants to merge 72 commits into
base: dev
Choose a base branch
from

Conversation

Thomas55555
Copy link
Contributor

@Thomas55555 Thomas55555 commented May 19, 2025

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
image

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

@Thomas55555 Thomas55555 marked this pull request as ready for review May 19, 2025 20:50
@home-assistant home-assistant bot marked this pull request as draft May 22, 2025 21:21
@home-assistant home-assistant bot requested review from joostlek and zweckj June 17, 2025 10:10
"""Set up Air Quality Sensors."""
super().__init__(coordinator)
self.entity_description = description
name = f"{subentry.data[CONF_LATITUDE]}_{subentry.data[CONF_LONGITUDE]}"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we give the entities a nicer name, like the street name?
The problem would be, that we than could have duplicate names, if to locations in the same road.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here: c534957

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don’t allow user configurable names in the config flow, they can update that later if they want, I'd say it was fine before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not configureable. It's the reverse geocoding of the coordinates. So in my eyes it's userfriendly. Otherwise we just have something like this, which doesn't give much information:
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I'm on mobile and just saw CONF_NAME and assumed it was configurable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be changes to ATTR_NAME?

"name": "Google Air Quality",
"codeowners": ["@Thomas55555"],
"config_flow": true,
"dependencies": ["application_credentials"],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just had a look at the Google Weather integration PR. There is just the API-Key used. This would also work. So we can avoid the complicated OAuth2 workflow. Is this wanted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only problem is, that we can make no title for the main entry with the user name. We would then have to give the entry title for example a partly redacted version of the API-Key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed it
743403d

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to remove the dependency here.

await self.async_set_unique_id(user_input[CONF_API_KEY])
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=f"API-Key: {'*' * (len(user_input[CONF_API_KEY]) - 3)}{user_input[CONF_API_KEY][-3:]}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just pick a default name than to add parts of the api key here

except Exception:
_LOGGER.exception("Unknown error occurred")
return self.async_abort(reason="unknown")
await self.async_set_unique_id(user_input[CONF_API_KEY])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the api key change? If we can't get something more specific like an user id I would thing _async_abort_entries_match would work better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can only delete it and create a new one. But I would suggest to trigger an reauth flow for that (in another PR).
In opposite to the OAuth2 authentication, there is no possibility to get personal information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reauth flow will change the api key and I don't think you can change the unique id.

@frenck frenck requested a review from Copilot June 26, 2025 21:06
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request adds a new integration for the Google Air Quality API, enabling Home Assistant users to retrieve real‐time air quality information based on geographic coordinates. Key changes include new sensor, coordinator, and configuration flow logic; comprehensive test coverage including snapshots and fixtures; and updates to the manifest, requirements, and branding.

Reviewed Changes

Copilot reviewed 21 out of 23 changed files in this pull request and generated 1 comment.

File Description
tests/components/google_air_quality/ New tests, fixtures, and snapshots for verifying integration functionalities
homeassistant/components/google_air_quality/ New integration code including sensors, config flow, coordinator, manifest, and related files
requirements*.txt Updated dependencies to include the google_air_quality_api package
homeassistant/brands/google.json and CODEOWNERS Updated branding and ownership information

)


class GoogleAirQaulityApiFlowHandler(ConfigFlow, domain=DOMAIN):
Copy link
Preview

Copilot AI Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class name 'GoogleAirQaulityApiFlowHandler' appears to contain a typo. Consider renaming it to 'GoogleAirQualityApiFlowHandler' to improve clarity and consistency.

Copilot uses AI. Check for mistakes.

"name": "Google Air Quality",
"codeowners": ["@Thomas55555"],
"config_flow": true,
"dependencies": ["application_credentials"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to remove the dependency here.

@@ -0,0 +1,12 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add "integration_type": "service", The default is hub.


# Gold
docs-examples: todo
discovery-update-info: todo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exempt

@@ -0,0 +1,80 @@
rules:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you re-ordered these? They were in alphabetical order.

(
AirQualitySensorEntity(coordinator, description, subentry_id, subentry)
for description in AIR_QUALITY_SENSOR_TYPES
if description.exists_fn(coordinator.data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems only for the local_aqi this could be false. Since you only do this in setup, if it's false, is it guaranteed to stay false in subsequent requests?

_LOGGER.exception("Unknown error occurred")
return self.async_abort(reason="unknown")
await self.async_set_unique_id(user_input[CONF_API_KEY])
self._abort_if_unique_id_configured()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can abort for this reason before you validate the API key

for entry in hass.config_entries.async_entries(DOMAIN):
for subentry in entry.subentries.values():
if subentry.unique_id == unique_id:
return self.async_abort(reason="already_configured")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done before calling the API

unique_id = f"{lat}_{lon}"
for entry in hass.config_entries.async_entries(DOMAIN):
for subentry in entry.subentries.values():
if subentry.unique_id == unique_id:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment, if you want to allow reconfigure, the subentries shouldn't have unique_id.
Instead of exact equality, should we check here that coordinates are very close in proximity?

return self.async_abort(reason="unknown")

unique_id = f"{lat}_{lon}"
for entry in hass.config_entries.async_entries(DOMAIN):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the end of the loop this overrides the entry you define at line 114

await hass.async_block_till_done()
await self.hass.config_entries.async_reload(entry.entry_id)

self.hass.async_create_task(reload_later())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The correct way to do this is in async_setup_entry to add:

    entry.async_on_unload(entry.add_update_listener(async_update_options))

and in __init__.py to add:

async def async_update_options(
    hass: HomeAssistant, entry: GoogleWeatherConfigEntry
) -> None:
    """Update options."""
    await hass.config_entries.async_reload(entry.entry_id)

@home-assistant home-assistant bot marked this pull request as draft June 28, 2025 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants