Files
sta/specs/001-modbus-relay-control/decisions.md
Lucien Cartier-Tilet 623e77dfc9 docs: add project specs and documentation for Modbus relay control
Initialize project documentation structure:
- Add CLAUDE.md with development guidelines and architecture principles
- Add project constitution (v1.1.0) with hexagonal architecture and SOLID principles
- Add MCP server configuration for Context7 integration

Feature specification (001-modbus-relay-control):
- Complete feature spec for web-based Modbus relay control system
- Implementation plan with TDD approach using SQLx for persistence
- Type-driven development design for domain types
- Technical decisions document (SQLx over rusqlite, SQLite persistence)
- Detailed task breakdown (94 tasks across 8 phases)
- Specification templates for future features

Documentation:
- Modbus POE ETH Relay hardware documentation
- Modbus Application Protocol specification (PDF)

Project uses SQLx for compile-time verified SQL queries, aligned with
type-driven development principles.
2026-01-11 00:39:18 +01:00

7.7 KiB

Implementation Decisions

Date: 2025-12-28 Feature: Modbus Relay Control System

User Decisions

Q1: Communication Pattern

Decision: HTTP Polling (as specified in spec) Rationale: WebSocket would be overkill for this project scale

Q2: Frontend Development Approach

Decision: Develop frontend alongside backend, but API endpoints must be implemented first before corresponding frontend features Approach: API-first development - implement and test each endpoint before building UI for it

Q3: Hardware Availability

Decision: Physical hardware available for testing Details:

  • 8-channel Modbus relay device accessible now
  • IP address: Variable (configurable)
  • Port: 501 or 502 (confirm in docs: docs/Modbus_POE_ETH_Relay.md)
  • Device will be available during development phase

Q4: Relay Label Persistence

Decision: SQLite database with SQLx Implementation Priority:

  1. Preferred: SQLite database with SQLx (compile-time SQL verification, async-native, type-safe)
  2. Alternative: YAML file (read at startup, write on update)

Recommendation: Use SQLite with SQLx for MVP - simpler than managing YAML file updates, good for future features, aligns with type-driven development principles

Q5: Error Recovery Strategy

Decision: Exponential retry with timeout Strategy:

  • When device becomes unhealthy/unavailable: attempt reconnection every 5 seconds
  • Maximum retry duration: 5 minutes
  • After 5 minutes: give up and mark device as unhealthy
  • Resume connection attempts when user makes new API request
  • Background task monitors connection health

Q6: Firmware Version

Decision: Check docs for availability, hide if unavailable Behavior:

  • If firmware version available via Modbus: Display in health endpoint
  • If not available: Omit field entirely from health response (not null/empty string)
  • Action: Verify in docs/Modbus_POE_ETH_Relay.md

Q7: Deployment Environment

Development: Thinkpad x220 (NixOS) Production Backend: Raspberry Pi 3B+ (available next week) - on same network as relay device Production Frontend: Cloudflare Pages (or equivalent static hosting) Reverse Proxy: Traefik on Raspberry Pi with Authelia middleware for authentication Network: Raspberry Pi on same network as relay device, frontend accesses backend via HTTPS through Traefik

Q8: Testing Approach

Decision: Implement both real hardware tests AND mocks Rationale:

  • Hardware available now for integration testing
  • Mocks needed for future maintenance (after device shipped)
  • Mocks enable fast unit tests without hardware dependency
  • Follows TDD principles with mock-based development

Testing Strategy:

  1. Unit Tests: Use mocks (mockall) - fast, no hardware needed
  2. Integration Tests: Use real hardware - verify actual Modbus communication
  3. CI/CD: Use mocks (hardware not available in CI)
  4. Manual Testing: Use real hardware during development

Derived Decisions

Deployment Architecture

Decision: Frontend on Cloudflare Pages, backend on Raspberry Pi behind Traefik reverse proxy Components:

  • Frontend: Static Vue 3 app hosted on Cloudflare Pages (fast global CDN delivery)
  • Backend: Rust HTTP API on Raspberry Pi (same local network as Modbus relay device)
  • Reverse Proxy: Traefik on Raspberry Pi providing:
    • HTTPS termination (TLS certificates)
    • Authelia middleware for user authentication
    • Reverse proxy routing to backend HTTP service
  • Communication: Frontend → HTTPS (via Traefik) → Backend → Modbus TCP → Relay Device

Rationale:

  • Frontend on CDN provides fast page loads from anywhere
  • Backend must be local to Modbus device (local network communication)
  • Traefik handles authentication/HTTPS without application-level complexity
  • Backend runs HTTP internally, Traefik handles TLS termination

Security Layers:

  1. Authelia authentication at reverse proxy (user login)
  2. HTTPS encryption for frontend-backend communication
  3. Unencrypted Modbus TCP on local network only (acceptable for local-only device)

Architecture Approach

Decision: Hexagonal Architecture with trait-based abstraction Layers:

  • Domain: Pure business logic (RelayId, RelayState, Relay entity)
  • Application: Use cases (GetRelayStatus, ToggleRelay, BulkControl)
  • Infrastructure: Modbus client implementation + SQLite repository
  • Presentation: HTTP API handlers (Poem)

Database Choice

Decision: SQLite with SQLx for relay labels and configuration

Why SQLx over rusqlite:

  • Compile-time SQL verification: Queries are checked against actual database schema during compilation
  • Type safety: Column types verified to match Rust types at compile time
  • Async-native: Built for tokio async/await (no need for spawn_blocking wrappers)
  • Type-driven development alignment: "Parse, don't validate" - SQL errors caught at compile time, not runtime
  • Better observability: Built-in query logging and tracing integration
  • Macro-based queries: query! and query_as! macros provide ergonomic, safe database access

Benefits of SQLite:

  • No external dependencies (embedded)
  • ACID transactions for label updates
  • Simple schema (one table for relay labels)
  • Easy to back up (single file)
  • Works on both NixOS and Raspberry Pi

Schema:

CREATE TABLE relay_labels (
    relay_id INTEGER PRIMARY KEY CHECK(relay_id >= 1 AND relay_id <= 8),
    label TEXT NOT NULL CHECK(length(label) <= 50)
);

Dependencies:

sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }

Modbus Port Discovery

Confirmed from Documentation (docs/Modbus_POE_ETH_Relay.md):

  • Modbus RTU over TCP: Uses TCP server mode, port is configurable (typically 8234 or custom)
  • Modbus TCP: Port automatically changes to 502 when "Modbus TCP protocol" is selected in Advanced Settings
  • Recommended: Use Modbus RTU over TCP (default, simpler configuration)
  • Device must be configured as: "Multi-host non-storage type" gateway (CRITICAL - storage type sends spurious queries)

Firmware Version Availability

Confirmed from Documentation (docs/Modbus_POE_ETH_Relay.md:417-442):

  • Available: YES - Firmware version can be read via Modbus function code 0x03
  • Register Address: 0x8000 (Read Holding Register)
  • Command: 01 03 80 00 00 01 AD CA
  • Response Format: 2-byte value, convert to decimal and divide by 100 (e.g., 0x00C8 = 200 = v2.00)
  • Implementation: Read once at startup and cache, update on successful reconnection

Connection Management

Decision: Background connection health monitor Behavior:

  • Monitor task checks connection every 5 seconds
  • On failure: retry with exponential backoff (max 5 seconds interval)
  • After 5 minutes of failures: mark unhealthy, stop retrying
  • On new API request: resume connection attempts
  • On successful reconnection: reset retry counter, mark healthy

Frontend Technology Stack

Decision: Vue 3 + TypeScript + Vite Components:

  • OpenAPI TypeScript client generation (type-safe API calls)
  • HTTP polling with setInterval (2-second intervals)
  • Reactive state management (ref/reactive, no Pinia needed for this simple app)
  • UI library: TBD (Nuxt UI, Vuetify, or custom - decide during frontend implementation)

Next Steps

  1. Verify Modbus port in documentation
  2. Design architecture approaches (minimal, clean, pragmatic)
  3. Select approach with user
  4. Create detailed implementation plan
  5. Begin TDD implementation

Notes

  • User has hardware access now, but device will ship after first version
  • Mocks are critical for long-term maintainability
  • SQLite preferred over YAML for runtime updates
  • Connection retry strategy balances responsiveness with resource usage