Skip to content

Add Airthings air quality ability#215

Open
brianchilders wants to merge 5 commits intoopenhome-dev:devfrom
brianchilders:add-airthings
Open

Add Airthings air quality ability#215
brianchilders wants to merge 5 commits intoopenhome-dev:devfrom
brianchilders:add-airthings

Conversation

@brianchilders
Copy link

What does this Ability do?

Fetches live indoor air quality readings from your Airthings devices and speaks a plain-English summary, flagging any values that exceed WHO/EPA/EU health guidelines for CO2, VOC, PM2.5, radon, and humidity.

Suggested Trigger Words

  • "check air quality"
  • "how's the air in here"
  • "airthings reading"
  • "check my Airthings"

Type

  • New community Ability
  • Improvement to existing Ability
  • Bug fix
  • Documentation update

External APIs

Testing

  • Tested in OpenHome Live Editor
  • All exit paths tested (said "stop", "exit", etc.)
  • Error scenarios tested (API down, bad input, etc.)

Checklist

  • Files are in community/my-ability-name/
  • main.py follows SDK pattern (extends MatchingCapability, has register_capability + call)
  • README.md included with description, suggested triggers, and setup
  • resume_normal_flow() called on every exit path
  • No print() — using editor_logging_handler
  • No hardcoded API keys — using placeholders
  • No blocked imports (redis, connection_manager, user_config)
  • No asyncio.sleep() or asyncio.create_task() — using session_tasks
  • Error handling on all external calls
  • Tested in OpenHome Live Editor

Anything else?

Demo Video on YouTube: https://youtu.be/WuVuMUek63c

@brianchilders brianchilders requested a review from a team as a code owner March 16, 2026 03:38
@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

🔀 Branch Merge Check

PR direction: add-airthingsdev

Passedadd-airthingsdev is a valid merge direction

@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

✅ Community PR Path Check — Passed

All changed files are inside the community/ folder. Looks good!

@github-actions github-actions bot added the community-ability Community-contributed ability label Mar 16, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

✅ Ability Validation Passed

📋 Validating: community/airthings
  ✅ All checks passed!

@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

🔍 Lint Results

🔧 Auto-formatted

Some files were automatically cleaned and formatted with autoflake + autopep8 and committed.

  • Unused imports removed (autoflake)
  • Unused variables removed (autoflake)
  • PEP8 formatting applied (autopep8)

__init__.py — Empty as expected

Files linted: community/airthings/main.py

✅ Flake8 — Passed

✅ All checks passed!

Copy link
Author

@brianchilders brianchilders left a comment

Choose a reason for hiding this comment

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

I have reviewed the changes and it looks like it's primarily formatting changes.

@uzair401
Copy link
Contributor

Hey @brianchilders, thanks for the submission — the ability is well structured and the error handling is thorough! Found two things that need to be addressed before this can be approved:

  1. asyncio.gather() — the SDK avoids unmanaged coroutines (same reason asyncio.create_task is explicitly restricted). asyncio.gather runs coroutines outside session_tasks, meaning if the session ends mid-execution, those coroutines have no way to be cancelled or cleaned up by the platform — potentially leaking open connections and resources. Please replace with a sequential for loop:
sample_results = []
for d in chosen_devices:
    result = await self._get_latest_samples(token, d.get("id", ""))
    sample_results.append(result)
  1. loop.run_in_executor() — please replace with asyncio.to_thread(), which is the pattern explicitly recommended by the SDK for wrapping blocking requests calls.

Copy link
Contributor

@uzair401 uzair401 left a comment

Choose a reason for hiding this comment

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

Hey @brianchilders, ran the ability through our voice naturalness audit. Here are a few suggestions to make it feel more natural on a smart speaker:

1. Hardcoded string matching — device selection
if "all" in reply_lower only catches the word "all" — a user might say "both", "every one", "all of them", "all devices", or "give me everything". Expand the check:

if any(w in reply_lower for w in ["all", "both", "every", "all of them", "all devices", "everything"]):
    return devices

2. No exit detection in device selection
If a user says "never mind" or "cancel" during device selection, it silently falls through to the first device instead of exiting. Add an exit check before processing the reply:

EXIT_WORDS = ["never mind", "forget it", "stop", "cancel", "exit", "quit"]
if any(w in reply_lower for w in EXIT_WORDS):
    self.capability_worker.resume_normal_flow()
    return []

3. LLM output has no word limit
The system prompt says "Be concise" but doesn't set a hard limit — the LLM could return a long response that goes straight into speak(). Add to the system prompt:

"Respond in 1-2 sentences, maximum 30 words. Plain spoken English only."

4. A few spoken strings to tighten up

  • "Airthings isn't set up yet. Please add your Client ID and Client Secret to the main.py file and try again." — mentions main.py which means nothing to a voice user. Suggest: "Airthings isn't set up yet. You'll need to add your credentials first."
  • "I found {len(devices)} devices: {device_names}. Which one would you like, or say 'all' for all of them?" — "say 'all'" is a screen instruction. Suggest: "Found {len(devices)} devices: {device_names}. Which one, or all of them?"
  • "Note: the readings for ... are more than an hour old and may not reflect current conditions." — slightly formal. Suggest: "Heads up — readings for {stale_names} are over an hour old, so they might be off."
  • "I couldn't find a device matching that name, so I'll use {first_name}." — Suggest: "Didn't catch that, using {first_name}."

Please address all of the above along with the SDK blockers mentioned in the earlier comment, then push your fixes and we'll take another look!

@brianchilders
Copy link
Author

Made recommended changes.
Tested recommended changes.
Pushed recommended changes.

@brianchilders brianchilders requested a review from uzair401 March 19, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-ability Community-contributed ability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants