DEV Community

Laurent Charignon
Laurent Charignon

Posted on • Originally published at blog.laurentcharignon.com

Building a Smart Home with AppDaemon: From Baby Monitors to Language Learning

Over the past year, I've been building a comprehensive home automation system using AppDaemon, a Python framework for Home Assistant automation. What started as simple light controls has evolved into a sophisticated ecosystem of 30+ interconnected applications managing everything from CPAP machines to interactive language learning games for my child.

Why AppDaemon?

Home Assistant's built-in automations are great for simple rules, but when you need complex logic, state management, or want to reuse code across multiple automations, AppDaemon shines. It provides:

  • Full Python programming capabilities
  • Object-oriented design with inheritance
  • Async/await support for concurrent operations
  • Direct access to Home Assistant's state and service APIs
  • Easy testing with pytest

The Architecture: Base Class Pattern

At the core of my system is a BaseApp class that all automations inherit from:

class BaseApp(hass.Hass):
    ArgClass=None
    def __init__(self, ad, name, logging, args, config, app_config, global_vars):
        super().__init__(ad, name, logging, args, config, app_config, global_vars)
        assert self.ArgClass is not None
        self.statsd=statsd.StatsClient("statsd.lan", 9125)
        self.instance_name = name
        self.filtered_args = {k:v for k,v in self.args.items() if k in fields_}
        self.input = self.ArgClass(**self.filtered_args)
Enter fullscreen mode Exit fullscreen mode

This pattern provides several benefits:

  1. Type Safety: Using dataclasses for arguments ensures type checking and validation
  2. Metrics Collection: Built-in StatsD integration for monitoring
  3. Consistent Logging: Standardized logging across all apps
  4. Code Reuse: Common functionality available to all automations

Real-World Applications

1. CPAP Light Synchronization

One of the more unique automations synchronizes bedroom lighting with my CPAP machine. When the CPAP starts (detected via power monitoring), the lights gradually dim to create ideal sleeping conditions:

class CPAPLightSync(BaseApp):
    async def initialize(self):
        await self.listen_state(
            self.cpap_state_changed,
            self.input.cpap_sensor,
            attribute="state"
        )

    async def cpap_state_changed(self, entity, attribute, old, new, *_):
        if float(new) > 5:  # CPAP is running
            await self.turn_off("light.bedroom", transition=30)
Enter fullscreen mode Exit fullscreen mode

2. Multi-Language Learning Game

Perhaps the most ambitious application is an interactive language learning system for my son Luca. It integrates a smart button (Hatch Baby Rest) with text-to-speech to teach vocabulary in multiple languages:

@dataclass
class LucaMultiLanguageLearningArgs:
    hatch_button: str
    media_player: str
    dictionary_path: str
    available_languages: Dict[str, Dict[str, str]]
    current_language: str = "french"
Enter fullscreen mode Exit fullscreen mode

The system:

  • Loads JSON dictionaries for French, Spanish, Farsi, and other languages
  • Tracks recently used words to avoid repetition
  • Speaks word pairs (foreign word → English translation)
  • Plays Spotify playlists during specific hours
  • Handles Unicode characters gracefully for non-Latin scripts

3. Motion-Activated Devices

The MotionActivatedDevice class demonstrates the power of abstraction:

class MotionActivatedDevice(BaseApp):
    async def motion_detected(self, entity, attribute, old, new, *_):
        if new == "on" and self.should_activate():
            await self.activate_device()
            self.schedule_deactivation()
Enter fullscreen mode Exit fullscreen mode

This single class powers multiple automations:

  • Bathroom lights that stay on longer during nighttime
  • Office fans that run when occupied
  • Display screens that wake on approach

4. Smart Environmental Control

The CarbonDioxydeRegulator monitors CO₂ levels and automatically controls ventilation:

async def regulate_co2(self, entity, attribute, old, new, *_):
    co2_level = float(new)
    if co2_level > 1000:
        await self.turn_on("fan.office")
    elif co2_level < 800:
        await self.turn_off("fan.office")
Enter fullscreen mode Exit fullscreen mode

Testing Infrastructure

Quality is ensured through comprehensive testing. The repository includes pytest-based tests that validate argument parsing, state changes, and service calls:

def test_args_types():
    for app in apps:
        module = importlib.import_module(f"apps.{app}")
        assert hasattr(module.ArgClass, '__dataclass_fields__')
Enter fullscreen mode Exit fullscreen mode

Enhanced Git Hooks for Quality

To maintain code quality, I've implemented a sophisticated pre-commit hook that goes beyond simple test execution. The hook provides:

  • Visual Progress Indicators: Green checkmarks (✓) for passing checks, red crosses (✗) for failures
  • Staged Test Results: Shows exactly which tests passed or failed with formatted output
  • Syntax Validation: Checks all Python files for syntax errors before commit
  • Debug Statement Detection: Warns about leftover print statements or pdb imports
  • Detailed Error Reporting: Highlights file paths and line numbers in test failures

The hook transforms the typical pytest output into a developer-friendly format that makes it immediately obvious what needs attention.

Metrics and Observability

Every automation reports metrics to a StatsD server, enabling monitoring of:

  • Automation execution frequency
  • State change patterns
  • Error rates and types
  • Performance metrics

This data feeds into Grafana dashboards, providing insights into home patterns and automation effectiveness.

Key Learnings

After a year of development, here are the key takeaways:

  1. Start with a solid foundation: The BaseApp pattern has saved countless hours
  2. Type hints are invaluable: Dataclasses catch configuration errors early
  3. Test everything: Automated tests prevent regressions when updating complex automations
  4. Monitor everything: Metrics reveal patterns you didn't know existed
  5. Keep it maintainable: Clear naming and documentation matter when returning to code months later

Looking Forward

The system continues to evolve. Upcoming projects include:

  • Voice-activated scene controls using local speech recognition
  • Predictive heating/cooling based on weather forecasts and historical patterns
  • Integration with security cameras for person detection
  • Automated plant watering based on soil moisture sensors

Conclusion

AppDaemon has transformed my home into a responsive environment that adapts to our family's needs. From ensuring optimal sleep conditions to helping my son learn new languages, these automations have become an invisible but essential part of daily life.

The combination of Python's flexibility, Home Assistant's ecosystem, and AppDaemon's framework creates a powerful platform for home automation that goes far beyond simple "if this then that" rules.

The full source code is available on GitHub, and I welcome contributions and questions from the community. Whether you're looking to automate a single room or your entire home, I hope these patterns and examples help you build something amazing.


Have questions about specific automations or want to share your own AppDaemon projects? Feel free to reach out or open an issue on the repository.

Top comments (0)