Skip to content

Python · Home automation · Full stack

AC Automation

A safety-first Python automation system for controlling and analysing a real Mitsubishi air conditioner.

I started this project after noticing that the built-in Auto mode of my Wi-Fi-enabled air conditioner remained largely static regardless of changing indoor conditions, outdoor weather or time of day.

What began as a small Python script grew into a full-stack automation system with a FastAPI backend, scheduled control loop, real MELCloud hardware integration, adaptive rule-based analytics and a React operator dashboard.

Role
Full-Stack & Python Developer
Period
April 2026 – May 2026
Status
Functional prototype · Paused after hardware validation
Environment
Local deployment · Home network
1,832
Passing automated tests
19
Operator dashboard sections
3
Safety checks before hardware execution
Real AC
Validated against physical hardware

Overview

The Problem

Wi-Fi control makes a conventional air conditioner easier to access remotely, but it does not automatically make its behaviour context-aware.

In my case, the factory Auto mode followed static logic. When conditions hovered around a threshold, the system could repeatedly switch state, while everyday control still depended heavily on manually checking the weather and adjusting the unit based on personal judgement.

I wanted to explore a different approach: a system that could combine real indoor readings, outdoor weather, time-dependent context and historical behaviour while still keeping every decision understandable and under operator control.

The Solution

The resulting application runs a scheduled control cycle that gathers current conditions, builds a validated representation of the system state and selects the appropriate heating, cooling or automatic strategy.

A separate analytics layer examines historical behaviour and can suggest small adjustments to control thresholds. These recommendations are deterministic and explainable rather than produced by a machine-learning model.

Most importantly, the analytics layer is advisory. It does not directly change the physical system by itself. The operator remains in control, and commands pass through multiple safety checks before they can reach the air conditioner.

Architecture

System Architecture

Control path

APScheduler

periodic control cycle

Collect System Data
MELCloudOpen-MeteoAstral
SystemState

Pydantic validation

Decision Engine
Strategy Selector
HeatingCoolingAuto
Control Advisor
Safety Guards

DRY_RUN → MASTER_SWITCH → Manual Override

MELCloud → Physical AC

Mitsubishi hardware

Persistence branch

Decision
SQLite

audit log

Cycle data
JSON

history

Operator path

React Dashboard

TypeScript · 19 sections

REST / JSON
FastAPI
AnalyticsHistoryProposalsConfiguration

Process boundary

FastAPI and APScheduler share a single application process.

  • Python / backend
  • External service
  • Persistence
  • Frontend
  • Hardware / safety

I deliberately kept the application as a single-process monolith rather than introducing microservices or a distributed task queue.

The workload is small and predictable: one physical device, one operator and a control cycle running periodically rather than thousands of concurrent requests. Adding Redis, Celery or multiple services would have increased operational complexity without solving a real problem at this stage.

FastAPI and APScheduler therefore run inside the same application process, while external integrations and persistence remain isolated behind dedicated modules.

Python

Python at the Core

Python powers the entire backend.

REST API
FastAPI serves the operator interface.
Scheduled automation
APScheduler runs the automation cycle.
Decision logic
Heating, cooling, auto-selection and safety logic.
Integrations
MELCloud, weather and sun-position data.
Validation
Pydantic models validate system state and configuration.
Persistence
SQLAlchemy / SQLite and JSON analytics storage.
Testing
pytest, QA scenarios and regression packs.

The codebase is organised as a multi-module application with dedicated packages for API routes, services, strategies, models, database access and utilities rather than as a single automation script.

Engineering

Engineering Highlights

01

Bridging synchronous logic with an asynchronous hardware library

The MELCloud client library is asynchronous, while the scheduler and decision engine were intentionally designed around a predictable synchronous flow.

Rather than converting the entire application to async, I isolated the difference inside the MELCloud service.

A dedicated background thread owns its own asyncio event loop. Synchronous callers submit coroutines through run_coroutine_threadsafe() and receive normal return values.

Result

The async complexity remains contained inside one integration module while the rest of the system keeps a simple synchronous interface.

02

Stable decisions instead of reacting to noise

In automatic mode, small temperature changes can make heating and cooling scores almost identical. A naive implementation could repeatedly switch strategy between cycles.

The strategy selector uses an epsilon threshold. When the scores are close enough, the previous strategy is preferred.

The adaptive threshold layer also applies a deadband and quantisation, ignoring adjustments below 0.05°C and rounding meaningful changes to fixed increments.

Result

The system responds to meaningful changes instead of statistical noise.

03

Explainable adaptive analytics instead of a black box

The system intentionally does not use machine learning. Historical signals are converted into threshold recommendations through transparent arithmetic:

adjustment = base × signal_strength × confidence

Low-confidence signals contribute nothing, while stronger evidence produces progressively larger adjustments. Comfort protection always has priority over efficiency-oriented recommendations.

Result

Every recommendation remains reproducible, testable and traceable to historical evidence and explicit rules.

Debugging deep dive

The stale-data lifecycle bug

  1. 01

    Problem

    The most interesting bug appeared only after the live system accumulated more than 50 history records.

    Several early “insufficient data” and sensor-quality signals remained stored even after the conditions that created them no longer existed.

  2. 02

    Root cause

    Four analytical stores shared the same underlying problem: their merge logic could add and update records, but never removed evidence that had become obsolete.

    A derived explanation was also incorrectly treated as an independent reliability signal, creating a hidden feedback loop.

  3. 03

    Fix

    I introduced targeted pruning before merge operations. Data-quality evidence that is not present in the freshly calculated result is removed before the new state is stored.

    I also removed the derived explanation from the reliability calculation and excluded stale candidate strategies from evidence counts.

  4. 04

    Result

    The data now follows a correct lifecycle.

    The full suite of 1,832 tests passed without regression, and the fix was validated against the live system after more than 100 accumulated history records.

Safety

Safety by Design

Because the software can control a physical device, safety is part of the architecture rather than an afterthought.

  1. 01

    Dry-run mode

    Real execution can remain disabled while the full control path is exercised.

  2. 02

    Master switch

    A central control can block automation globally.

  3. 03

    Manual override detection

    If someone changes the air conditioner through the physical remote or manufacturer application, the automation temporarily backs off instead of immediately overriding the human decision.

The analytics layer also remains advisory by default. Recommendations require explicit operator approval instead of being silently applied.

Stack

Technology

Backend

  • Python 3.9
  • FastAPI
  • APScheduler
  • Pydantic

Data

  • SQLAlchemy
  • SQLite
  • JSON

Integrations

  • MELCloud / pymelcloud
  • Open-Meteo
  • Astral

Frontend

  • React 18
  • TypeScript
  • Vite
  • Recharts

Testing

  • pytest
  • QA scenarios
  • regression packs

Tooling

  • Git
  • GitHub
  • VS Code

Outcome

Results

1,832
Automated tests

A broad pytest suite covering services, analytics, guardrails, pattern detection, forecasting and related behaviour.

Real hardware
Validated integration

The control path was tested against a physical Mitsubishi air conditioner through MELCloud rather than only through mocks.

19
Dashboard sections

The React/TypeScript operator interface exposes status, analytics, proposals, configuration and reporting.

13
Development phases

The project evolved incrementally from a small rule-based script into a complete full-stack prototype.

Project scope

The system remains a personal single-home prototype, not a commercial SaaS product. User accounts, multiple homes, billing and cloud deployment were planned but not implemented.

Next

Trade-offs & Next Steps

Relational persistence

Move the evolving JSON analytics state into a relational database once multi-user access or more complex querying requires it.

User accounts and multiple homes

Introduce authentication, device ownership and multi-tenant data isolation.

Cloud deployment

Move beyond the current local single-machine deployment model.

Measured optimisation

Add automated before/after benchmarking for accepted control recommendations so energy improvements can be quantified instead of treated only as a design objective.

Interface

Interface

Operator Overview
Analytics & Historical Signals
Configuration & Proposals
System Status
Raw Debug / Diagnostics

Explore the implementation

The repository contains the application source code, architecture documentation and development history.