|
|
3 weeks ago | |
|---|---|---|
| .ai | 3 weeks ago | |
| .claude | 3 weeks ago | |
| internal | 3 weeks ago | |
| .dockerignore | 3 weeks ago | |
| .gitattributes | 3 weeks ago | |
| .gitignore | 3 weeks ago | |
| AGENTS.md | 3 weeks ago | |
| CLAUDE.md | 3 weeks ago | |
| Dockerfile | 3 weeks ago | |
| README.md | 3 weeks ago | |
| ROADMAP.md | 3 weeks ago | |
| ROADMAP.template.md | 3 weeks ago | |
| docker-compose.yml | 3 weeks ago | |
| go.mod | 3 weeks ago | |
| go.sum | 3 weeks ago | |
| main.go | 3 weeks ago | |
| main_test.go | 3 weeks ago | |
README.md
mqtt_washingMachine
Overview
A small Go service that shows whether the laundry is done using the Tasmota plug's
MQTT ENERGY.Power readings. The phone-first page has a white background, a large
status message, and the last three power readings, such as 47.70W. Missing
readings appear as --.
The backend runs continuously, even with no page open. Every 60 seconds it records the latest valid message received since the previous tick, or a missing sample if none arrived. It keeps only the last three samples in memory, oldest first. The page fetches the current status on load and whenever a valid MQTT power message arrives, using Server-Sent Events (SSE). It also polls every 60 seconds as a fallback. The readings still show the last three interval samples, not a live measurement.
Status rules
Rules apply in this order:
| Condition | Message | Color |
|---|---|---|
| Fewer than three samples recorded since startup | Waiting for data... | Grey |
| Any of the last three samples is missing | Incomplete data | Grey |
| Any sample is at least 3.00 W | Laundry not yet done | Red |
| All three samples are below 3.00 W | Laundry done | Green |
Exactly 3.00 W is active; 2.99 W and 0 W are idle. Malformed JSON and
payloads without numeric ENERGY.Power are logged and ignored; they do not replace
the last valid message or make a missing interval valid. If the HTTP request fails,
the page shows a grey "Status unavailable" message and retries at the next refresh.
Getting Started
Docker Compose
Install Docker with the Compose plugin on a server that can reach your MQTT broker. From this repository, run:
docker compose up --build -d
docker compose logs -f laundry-status
Open http://<your-server>:8080/ on a phone connected to your home Wi-Fi. By
default the service connects to 192.168.1.11:1883 and subscribes to
tele/tasmota_washingmashine_C21183/SENSOR. No broker is included in the Compose
file; it connects to your existing one.
To override settings, export the environment variables below or place them in a
local .env file before running Compose. For example:
MQTT_BROKER=tcp://192.168.1.11:1883 docker compose up --build -d
Keep credential files local. The Docker build context excludes .env files.
The container runs as an unprivileged user, with a static Go binary and embedded
page; no volume or database is required. Stop it with docker compose down.
Configuration
| Variable | Default | Description |
|---|---|---|
MQTT_BROKER |
tcp://192.168.1.11:1883 |
Broker URL, including scheme and port |
MQTT_TOPIC |
tele/tasmota_washingmashine_C21183/SENSOR |
Tasmota sensor topic |
MQTT_USERNAME |
Empty | Optional MQTT username |
MQTT_PASSWORD |
Empty | Optional MQTT password |
HTTP_ADDR |
:8080 |
HTTP listen address |
SAMPLE_INTERVAL |
60s |
Positive Go duration between samples, for example 60s or 1m |
Empty variables use the defaults. In Docker, keep the HTTP listen address on all
interfaces (for example :8080). If you change its port, also update the target
port in the Compose ports mapping. To change only the published host port, edit
the left side of 8080:8080 and leave HTTP_ADDR unchanged.
Changing SAMPLE_INTERVAL affects backend sampling and the three-sample window;
the browser still refreshes on valid MQTT messages with a 60-second polling
fallback. Invalid or nonpositive intervals
stop startup with an error. The service retries an unavailable broker in the
background and subscribes again after reconnecting. HTTP remains available during
broker outages; intervals without valid messages become missing data.
Tasmota setup
Configure the plug to publish sensor telemetry to the topic above, with a payload containing a numeric power measurement, for example:
{"ENERGY":{"Power":139.60}}
The plug currently uses TelePeriod 10 (about six messages per minute). Sampling
does not depend on that exact setting: any telemetry interval works, but an
interval longer than SAMPLE_INTERVAL can leave gaps and show "Incomplete data".
Only the latest message within each sampling interval is used; this is not an
average of all measurements.
Run and validate locally
Install Go 1.26 or later, then run:
go run .
Validation commands:
go fmt ./...
go vet ./...
go test ./...
go test -race ./...
docker build -t laundry-status .
No automated E2E command is configured. For a manual MQTT smoke check, run the
service against a test broker and topic with SAMPLE_INTERVAL=1s. Use
mosquitto_pub to send a message repeatedly, at least once per second:
mosquitto_pub -h <test-broker> -t <test-topic> -m '{"ENERGY":{"Power":2.99}}'
After three valid samples, /api/status should report done. Publish 3.00 to
get running, then stop publishing for a sample interval to get incomplete.
Keep the page open: valid messages trigger status fetches without a reload, while
missing intervals appear on the next fallback poll when no messages arrive.
Readings only change after a sampling tick. Check the page at 360 px width for
readable text and no horizontal scrolling.
Status API
GET /api/status returns JSON with a state, display message, and zero to three
readings ordered oldest first. Responses are not cached. For example:
{"state":"incomplete","message":"Incomplete data","readings":[{"power":47.7,"valid":true},{"power":0,"valid":false},{"power":2.99,"valid":true}]}
Use valid to distinguish a missing sample from a real zero-watt reading. Startup
returns {"state":"waiting","message":"Waiting for data...","readings":[]}.
Status events
GET /api/events streams text/event-stream with Cache-Control: no-store.
It sends an immediate event on connection (including reconnection), then an
update notification after each valid MQTT power message:
event: update
data: {}
Each event ends with a blank line. Events contain no measurements; fetch
/api/status for the current sampled state. Invalid payloads do not notify
clients. Bursts may coalesce for slow subscribers without delaying MQTT handling
or other clients. An idle connection receives : keep-alive followed by a blank
line every 25 seconds. Streams outlive the ordinary HTTP write timeout and close
when the client disconnects or the service shuts down.
The page uses EventSource, which reconnects automatically. It refreshes on each
update, skips overlapping status fetches, and retains the 60-second poll even
when streaming is unavailable or the browser has no EventSource support.
If using a reverse proxy, disable buffering for text/event-stream and allow
idle connections longer than the 25-second keep-alive interval.
To inspect events manually, run curl -N http://localhost:8080/api/events while
publishing valid and invalid payloads to the test topic.
Add to Android home screen
- Open
http://<your-server>:8080/in Chrome on your Android phone. - Open Chrome's menu and choose Add to Home screen (or Install app if offered).
- Use the name Home Lanundry and confirm. The shortcut uses the cartoon washing-machine icon.
On plain HTTP on the home LAN, Chrome offers a home-screen shortcut rather than a full app installation, which requires HTTPS. The manifest requests a standalone window where supported; a shortcut may open in a regular browser tab. There is no offline mode: the phone must be able to reach the running service.
The binary embeds these home-screen assets; no extra files need to be deployed.
All routes below accept GET and send Cache-Control: no-cache for revalidation:
| Route | Content type | Purpose |
|---|---|---|
/manifest.webmanifest |
application/manifest+json |
Home Lanundry name, launch settings, and icon references |
/icons/icon.svg |
image/svg+xml |
SVG source and browser icon |
/icons/icon-192.png, /icons/icon-512.png |
image/png |
192×192 and 512×512 home-screen icons |
/icons/icon-maskable-512.png |
image/png |
512×512 full-bleed icon with artwork inside the maskable safe zone |
/apple-touch-icon.png |
image/png |
180×180 touch icon |
Limitations
- In-memory state only: a restart clears the window. With the default interval, the service shows "Waiting for data..." for approximately three minutes.
- A single device and three samples only; no persistent history or notifications.
- This is an idle-power heuristic. An unused machine or a sufficiently long pause below 3 W also results in "Laundry done"; it does not track wash-cycle phases.
- Use on a trusted home LAN only. HTTP has no TLS or authentication; keep the published port off the public internet.
AI Workflow
This project includes the persistent planner/implementer/reviewer workflow with file-based coordination, plus the PO orchestration layer. Manual and auto are two runtime modes for the same scaffold: they use the same generated files, task board, and review gate. Fewer role sessions means less coordination overhead and lower token cost.
Modes
This scaffold supports two workflow profiles that share the same generated files, task board, plan, review log, and handoff log.
fullkeeps the planner, implementer, reviewer, and optional PO split.litekeeps the planner but replaces separate implementer/reviewer sessions with oneaide devsession.
| Profile | Sessions and launchers | Who drives work | Commit cadence | PO support | Recommended fit |
|---|---|---|---|---|---|
full |
aide plan, aide implement, aide review, optional aide po |
Separate role sessions with explicit implement/review handoffs | One final task commit after reviewer approval | yes | Larger, riskier, or more review-heavy changes |
lite |
aide plan, aide dev |
Planner plus one dev session that implements and reviews, then halts for human approval at ready_for_review |
One real commit per task, including all_task |
no | Smaller changes or lower-coordination work |
How to switch
aide profile full
aide profile lite
Use aide profile full to return to the split workflow, or aide profile lite to switch to the dev-session flow.
full is the default for new scaffolds. When you switch to lite, aide implement, aide review, and aide po refuse and point you at aide dev. When you switch back to full, aide dev refuses and points you at aide implement and aide review.
Runtime modes
- Manual mode: start the planner, implementer, and reviewer in separate terminals, then drive each session yourself with the documented text commands.
- Auto mode: run
aide poto start the PO session, which uses MCP tools to coordinate the post-planning implementer/reviewer workflow for you.
Concepts
Cycle — a unit of work on a feature branch. One cycle = one branch = one PR.
Roles and file ownership:
| Role | Reads | Writes |
|---|---|---|
| PO | .ai/TASKS.md, .ai/PLAN.md, .ai/REVIEW.md, .ai/prompts/po.md |
MCP session commands via aide po |
| Planner | ROADMAP.md |
.ai/PLAN.md, .ai/TASKS.md |
| Implementer | .ai/PLAN.md, .ai/REVIEW.md |
source code, .ai/TASKS.md |
| Reviewer | .ai/PLAN.md, commits |
.ai/REVIEW.md, .ai/TASKS.md |
Status flow:
in_planning → ready_for_implement → in_implementation → ready_for_review → in_review → ready_to_commit → done
↑ |
└──── changes_requested ◄─────────────┘
Start a new development cycle
# Edit ROADMAP.md with your goals first, then:
aide cycle start feature/<scope>
Full profile quick start
aide plan # terminal 1
aide implement # terminal 2
aide review # terminal 3
Launch each role once, then keep those sessions open for the rest of the cycle.
Role launchers read default agent/model settings from .ai/config.json.
To override, pass the agent first, then any CLI flags.
Example: aide review claude --model sonnet
Claude starts interactively by default, and the Codex launcher uses interactive codex mode so the session stays open for role commands.
Lite profile quick start
aide profile lite
aide plan # terminal 1
aide dev # terminal 2
The dev session handles implementation and review, then halts for human approval at ready_for_review. In lite mode, aide implement, aide review, and aide po refuse and point you at aide dev.
Start the PO orchestrator (auto mode)
aide po
The PO session drives the post-planning task board flow through the aide MCP server. PO orchestration is available only in the full profile.
By default, aide po launches Claude with --model haiku, and aide po codex launches Codex with -m gpt-5.4-mini. .ai/config.json can override those defaults, and an explicit CLI --model or -m flag takes precedence.
If no tasks are in ready_for_implement or later, run the planner first.
The normal MCP completion path is session_run followed by session_wait, then a re-read of .ai/TASKS.md; session_get_output is for debugging only.
The MCP server appends structured debug logs to .ai/mcp-server.log, and the scaffold gitignore excludes that file.
Drive work inside the existing sessions
planner> start_plan
implementer> next_task T-001
reviewer> next_task T-001
implementer> commit_task T-001
dev> next_task T-001
dev> commit_task T-001
implementer> rework_task T-001
aide cycle end 0.7.0
Cross-platform CLI equivalents:
aide cycle end [VERSION] closes the cycle outside the persistent role session, and aide pr [--dry-run] creates or updates the branch PR.
Planner commands:
Before start_plan, freeform conversation with the planner is the roadmap-refinement phase. Use it to sharpen scope, acceptance criteria, constraints, and trade-offs directly in ROADMAP.md. start_plan is the explicit handoff into formal planning.
| Command | Description |
|---|---|
start_plan |
Read ROADMAP.md, write .ai/PLAN.md and .ai/TASKS.md |
rework_plan [TASK_ID] |
Revisit the plan when scope or approach changes |
Implementer commands:
| Command | Description |
|---|---|
next_task [TASK_ID] |
Pick up the next ready_for_implement task |
commit_task [TASK_ID] |
Turn a ready_to_commit task into one clean final commit, including task-specific .ai/ artifacts |
rework_task [TASK_ID] |
Address changes_requested findings from .ai/REVIEW.md |
aide cycle end [VERSION] |
Close the cycle after all tasks reach done, committing remaining .ai/ artifacts with a Release-As: footer |
status_cycle [TASK_ID] |
Show task status and recommended next action |
Reviewer commands:
| Command | Description |
|---|---|
next_task [TASK_ID] |
Pick up the next ready_for_review task and run review plus verification |
status_cycle [TASK_ID] |
Show task status and recommended next action |
File map
| File | Purpose | Tracked |
|---|---|---|
.ai/PLAN.md |
Current plan | yes |
.ai/TASKS.md |
Task board with status | yes |
.ai/prompts/po.md |
PO orchestration prompt for auto mode | yes |
.ai/REVIEW.md |
Review findings | yes (tracked cycle log) |
.ai/HANDOFF.md |
Runtime handoff log | yes (tracked cycle log) |
.ai/config.json |
Per-role launch defaults | yes |
ROADMAP.md |
Cycle goals (edit before planning) | yes |
AGENTS.md |
Project-specific and workflow-managed agent rules | yes |
CLAUDE.md |
Agent instruction entry point (@AGENTS.md) |
yes |
aide po |
Launch the PO orchestration session | yes |
Create a PR
aide pr
Full workflow details and session recovery rules are in AGENTS.md.