Files
sta/specs/001-modbus-relay-control/decisions.md
T
phundrak a683810bdc 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-22 00:57:10 +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