Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Normalize line endings: LF in the repository for everything Git detects as text
* text=auto eol=lf

# Source code
*.c text eol=lf
*.cpp text eol=lf
*.h text eol=lf
*.hpp text eol=lf
*.py text eol=lf
*.js text eol=lf
*.css text eol=lf
*.html text eol=lf

# Scripts (LF required for execution)
*.sh text eol=lf

# Documentation and configuration
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.json text eol=lf
*.csv text eol=lf
*.txt text eol=lf
*.ini text eol=lf

# Build system
*.cmake text eol=lf
CMakeLists.txt text eol=lf
Kconfig text eol=lf
Kconfig.* text eol=lf

# KiCad text formats (s-expression based)
*.kicad_pcb text eol=lf
*.kicad_sch text eol=lf
*.kicad_pro text eol=lf

# Binary files — never normalize
*.pdf -text
*.ico -text
*.png -text
*.jpg -text
*.jpeg -text
*.webp -text
*.gz -text
*.bin -text
*.elf -text
*.hex -text
*.woff -text
*.woff2 -text
*.zip -text
25 changes: 25 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copilot Instructions — WateringSystem

The source of truth for project rules is the root `CLAUDE.md` (plus `firmware/CLAUDE.md` for firmware specifics). Read those first. Key rules:

## Language

- English everywhere: code, comments, commit messages, PR titles/descriptions, issues, documentation. The repository is public.

## Frozen legacy code

- `src/`, `include/`, `data/`, `test/`, and `platformio.ini` are **FROZEN** — the production greenhouse unit runs this code.
- Never modify these paths on `main` or feature branches. Legacy patches go only through the `arduino-maintenance` branch.

## Active development

- All new work happens in `firmware/` — ESP-IDF v6.0.1, C++.
- Interface-based architecture (`ISensor`, `IActuator`, `IModbusClient`, ...) with RAII; prefer `std::string` over raw buffers.
- Include guards in all headers: `WATERINGSYSTEM_PATH_FILE_H` (e.g. `WATERINGSYSTEM_SENSORS_SOILSENSOR_H`).

## Git

- Conventional commits, written in English.
- Never squash-merge — always use merge commits (squash breaks spec-kit branch chaining).

See `docs/PRD-esp-idf-migration.md` for project scope and `docs/prd/` for the PR breakdown.
64 changes: 64 additions & 0 deletions .github/workflows/arduino-legacy-cold.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Cold reproducibility build of the frozen legacy Arduino/PlatformIO firmware.
#
# This workflow lives on main (the default branch) because GitHub only honors
# workflow_dispatch and schedule triggers for workflows on the default branch —
# the arduino-legacy.yml on the arduino-maintenance branch itself only covers
# push/PR events to that branch. This file checks out and builds the frozen
# arduino-maintenance branch as a reproducibility canary.
#
# Deliberately NO caching: a cold build proves the pinned platform/library
# packages are still downloadable from scratch, which is the actual guarantee
# behind "git checkout arduino-final always builds".
name: arduino-legacy-cold

on:
workflow_dispatch:
schedule:
# Monthly canary: 06:00 UTC on the 1st.
- cron: "0 6 1 * *"

jobs:
build:
name: Cold build of frozen legacy firmware (pinned deps, no cache)
runs-on: ubuntu-latest
steps:
- name: Checkout arduino-maintenance
uses: actions/checkout@v4
with:
ref: arduino-maintenance

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install PlatformIO Core (pinned)
run: pip install platformio==6.1.19

- name: Build firmware
run: pio run -e wateringsystem

- name: Build LittleFS filesystem image
run: pio run -e wateringsystem -t buildfs

# if-no-files-found: error only fires when ZERO files match, so a partial
# match could silently upload an incomplete artifact set. Verify all four
# files exist before uploading.
- name: Verify all artifacts exist
run: |
test -f .pio/build/wateringsystem/firmware.bin
test -f .pio/build/wateringsystem/littlefs.bin
test -f .pio/build/wateringsystem/bootloader.bin
test -f .pio/build/wateringsystem/partitions.bin

- name: Upload flashable artifacts
uses: actions/upload-artifact@v4
with:
name: arduino-firmware-cold
retention-days: 90
if-no-files-found: error
path: |
.pio/build/wateringsystem/firmware.bin
.pio/build/wateringsystem/littlefs.bin
.pio/build/wateringsystem/bootloader.bin
.pio/build/wateringsystem/partitions.bin
64 changes: 64 additions & 0 deletions .github/workflows/firmware-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: firmware-build

on:
push:
branches: [main]
paths:
- "firmware/**"
- ".github/workflows/firmware-build.yml"
pull_request:
paths:
- "firmware/**"
- ".github/workflows/firmware-build.yml"
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board: [rev1_devkit, rev2]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: Build firmware (${{ matrix.board }})
uses: espressif/esp-idf-ci-action@9d38657f3d789ca759b2b37aaf5ceffbc42c4f0d # v1
with:
esp_idf_version: v6.0.1
target: esp32
path: firmware
command: idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.board.${{ matrix.board }}" build

# Unknown CONFIG_ keys in overlay files are silently ignored by IDF; a
# typo in sdkconfig.board.* would yield a green rev1 (Kconfig default)
# build labeled as rev2. Verify the board choice actually took effect.
- name: Verify board config took effect (${{ matrix.board }})
run: |
if [ "${{ matrix.board }}" = "rev1_devkit" ]; then
grep -qx "CONFIG_BOARD_REV1_DEVKIT=y" firmware/sdkconfig
else
grep -qx "CONFIG_BOARD_REV2=y" firmware/sdkconfig
fi

# if-no-files-found: error only fires when ZERO files match, so a
# partial match could silently upload an incomplete artifact set.
# Verify all three binaries exist before uploading.
- name: Verify all binaries exist
run: |
test -f firmware/build/wateringsystem.bin
test -f firmware/build/bootloader/bootloader.bin
test -f firmware/build/partition_table/partition-table.bin

- name: Upload binaries
uses: actions/upload-artifact@v4
with:
name: wateringsystem-${{ matrix.board }}
if-no-files-found: error
path: |
firmware/build/wateringsystem.bin
firmware/build/partition_table/partition-table.bin
firmware/build/bootloader/bootloader.bin
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,15 @@ _autosave*
fp-info-cache
*-backups/

# GitHub Copilot
.github/copilot-instructions.md
# ESP-IDF (firmware/dependencies.lock is intentionally tracked)
firmware/build/
firmware/sdkconfig
firmware/sdkconfig.old
firmware/managed_components/

# Session dialog documents (not part of any PR)
docs/phase0-plan.md
docs/phase0-review.md

# Claude Code
.claude/
Expand Down
139 changes: 104 additions & 35 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,119 @@
# [PROJECT_NAME] Constitution
<!-- Example: Spec Constitution, TaskFlow Constitution, etc. -->
<!--
Sync Impact Report
- Version change: (template, unversioned) → 1.0.0 (initial ratification)
- Modified principles: n/a (initial adoption)
- Added sections: Core Principles (I–VI), Additional Constraints, Development Workflow, Governance
- Removed sections: none (template placeholders replaced)
- Templates requiring updates:
✅ .specify/templates/plan-template.md — generic Constitution Check gate, compatible as-is
✅ .specify/templates/spec-template.md — no constitution-specific tokens, compatible as-is
✅ .specify/templates/tasks-template.md — test-first task ordering aligns with Principle II, compatible as-is
- Follow-up TODOs: none
-->

# WateringSystem Constitution

## Core Principles

### [PRINCIPLE_1_NAME]
<!-- Example: I. Library-First -->
[PRINCIPLE_1_DESCRIPTION]
<!-- Example: Every feature starts as a standalone library; Libraries must be self-contained, independently testable, documented; Clear purpose required - no organizational-only libraries -->
### I. Safety First (NON-NEGOTIABLE)

Pumps MUST be off at boot, after any reset (watchdog, brownout, panic), and across
OTA restarts — driving pump GPIOs to a safe OFF state is the first action of the
firmware. Invalid, stale, or missing sensor data in automatic mode MUST force a pump
stop. Manual mode is an explicit operator override and is the only path that bypasses
sensor-validity checks. All fail-safe behaviors are enumerated in
`docs/parity-checklist.md` and every one of them MUST be covered by host tests.
No feature, refactor, or optimization may weaken a fail-safe behavior; WiFi or
network failure MUST never affect watering safety.

### II. Host-Testability

All application logic — watering control, scheduling, safety conditions, reservoir
management — MUST be testable on the host against mock implementations. Hardware
access happens only behind the interface layer (`ISensor`, `IEnvironmentalSensor`,
`ISoilSensor`, `IActuator`, `IWaterPump`, `IModbusClient`, `IDataStorage`).
Code that touches IDF hardware APIs directly belongs in driver components that
implement these interfaces and contain no business logic. The host test suite runs
in CI on every push; watering logic and safety conditions target 100% host coverage.

### III. Reproducible Builds

The toolchain and every dependency are pinned: ESP-IDF v6.0.1 via the
`espressif/idf` Docker image in both local and CI builds, exact component versions
in `idf_component.yml` (with `dependencies.lock` tracked), and exact PlatformIO
platform/library versions on the legacy branch. CI MUST build all board targets
(`BOARD_REV1_DEVKIT`, `BOARD_REV2`) green from a clean checkout. A build that works
only on a developer machine does not exist; `idf.py build` in the pinned container
is the canonical build. Version bumps of toolchain or dependencies are deliberate,
reviewed changes — never floating ranges.

### IV. Frozen Legacy

The Arduino firmware (`src/`, `include/`, `data/`, `test/`, `platformio.ini`) runs
the production greenhouse unit and MUST NEVER be modified on `main` or on feature
branches. Legacy patches go exclusively through the `arduino-maintenance` branch
(via `git worktree`), are built by CI with pinned dependencies, and are tagged
`arduino-v2.3.x`. The `arduino-maintenance` branch is never merged into `main`;
the `arduino-final` tag is the permanent buildable reference.

### [PRINCIPLE_2_NAME]
<!-- Example: II. CLI Interface -->
[PRINCIPLE_2_DESCRIPTION]
<!-- Example: Every library exposes functionality via CLI; Text in/out protocol: stdin/args → stdout, errors → stderr; Support JSON + human-readable formats -->
### V. Checkpoint-Gated AI Workflow

### [PRINCIPLE_3_NAME]
<!-- Example: III. Test-First (NON-NEGOTIABLE) -->
[PRINCIPLE_3_DESCRIPTION]
<!-- Example: TDD mandatory: Tests written → User approved → Tests fail → Then implement; Red-Green-Refactor cycle strictly enforced -->
Development is spec-kit driven: each PR derives from `docs/prd/PR-NN-*.md` and gets
`specs/NNN-*/` artifacts (spec, plan, tasks). Human checkpoints are mandatory stops:
CHECKPOINT 2 (plan approval before implementation) and CHECKPOINT 3 (review approval
before commit/PR) MUST NOT be auto-proceeded; CHECKPOINT 1 (clarify) stops only when
open questions exist. Code review (`pr-review-toolkit`) is never skipped. The
orchestrating session never writes implementation code directly — implementation is
delegated to subagents, which inherit the session model (`model: inherit`).
Merge commits always; squash merges are forbidden (they break spec-kit branch
chaining). One branch at a time; `git worktree` for true parallelism.

### [PRINCIPLE_4_NAME]
<!-- Example: IV. Integration Testing -->
[PRINCIPLE_4_DESCRIPTION]
<!-- Example: Focus areas requiring integration tests: New library contract tests, Contract changes, Inter-service communication, Shared schemas -->
### VI. English Outward

### [PRINCIPLE_5_NAME]
<!-- Example: V. Observability, VI. Versioning & Breaking Changes, VII. Simplicity -->
[PRINCIPLE_5_DESCRIPTION]
<!-- Example: Text I/O ensures debuggability; Structured logging required; Or: MAJOR.MINOR.BUILD format; Or: Start simple, YAGNI principles -->
Everything outward-facing is written in English: code, comments, commit messages,
PR titles and descriptions, issues, and documentation — this is a public repository.
This deliberately overrides any user-global Swedish-language conventions. Dialogue
with Paul (the project owner) remains in Swedish.

## [SECTION_2_NAME]
<!-- Example: Additional Constraints, Security Requirements, Performance Standards, etc. -->
## Additional Constraints

[SECTION_2_CONTENT]
<!-- Example: Technology stack requirements, compliance standards, deployment policies, etc. -->
- Target hardware: ESP32-WROOM-32E (4 MB flash); partition layout per
`docs/partition-plan.md` (A/B OTA + NVS + littlefs) — changes to the partition
table require an explicit migration plan.
- Language level: modern C++ on native ESP-IDF APIs; no Arduino compatibility
layers in `firmware/`; `std::string` instead of Arduino `String`; RAII; include
guards in the form `WATERINGSYSTEM_PATH_FILE_H`.
- Logging via `ESP_LOG*` with per-component tags; safety-relevant events (pump
start/stop, failures, OTA) MUST be persisted.
- Board differences are expressed through the Kconfig board choice
(`CONFIG_BOARD_REV1_DEVKIT` / `CONFIG_BOARD_REV2`) and the `board` component —
never through scattered `#ifdef`s in application logic.
- License: AGPL-3.0-or-later. No secrets in the repository; configuration and
credentials live in NVS on the device.

## [SECTION_3_NAME]
<!-- Example: Development Workflow, Review Process, Quality Gates, etc. -->
## Development Workflow

[SECTION_3_CONTENT]
<!-- Example: Code review requirements, testing gates, deployment approval process, etc. -->
- Pipeline per PR: researcher (optional) → `speckit.specify` → `speckit.clarify`
→ CP1 → `speckit.plan` → `speckit.tasks` → `speckit.analyze` → CP2 →
`speckit.implement` (via implementer subagent) → `pr-review-toolkit:review-pr`
→ CP3 → fixer (if findings) → verification re-review → commit/PR.
- Every implementation delivers a test checklist: host-test PRs are verified by CI;
hardware-near PRs ship a HIL checklist that Paul executes on the bench rig at
CHECKPOINT 3.
- `main` is protected: all changes via feature branch + PR with green CI.
- If review reveals the need for architectural change, return to `speckit.plan`
rather than patching forward.

## Governance
<!-- Example: Constitution supersedes all other practices; Amendments require documentation, approval, migration plan -->

[GOVERNANCE_RULES]
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->
This constitution supersedes all other practices in this repository, including
user-global instructions where they conflict (see Principle VI). Amendments are
made by PR that updates this file, must include a version bump per semantic
versioning (MAJOR: principle removal/redefinition; MINOR: new principle or
materially expanded guidance; PATCH: clarification), and require Paul's approval.
Every CHECKPOINT 2 plan review and CHECKPOINT 3 code review MUST verify compliance
with these principles; deviations require explicit, documented justification in the
plan's Complexity Tracking section. Runtime development guidance lives in
`CLAUDE.md` (root) and `firmware/CLAUDE.md`.

**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
**Version**: 1.0.0 | **Ratified**: 2026-06-10 | **Last Amended**: 2026-06-10
Loading
Loading