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!

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