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.
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:
- Preferred: SQLite database with SQLx (compile-time SQL verification, async-native, type-safe)
- 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:
- Unit Tests: Use mocks (mockall) - fast, no hardware needed
- Integration Tests: Use real hardware - verify actual Modbus communication
- CI/CD: Use mocks (hardware not available in CI)
- 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:
- Authelia authentication at reverse proxy (user login)
- HTTPS encryption for frontend-backend communication
- 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_blockingwrappers) - 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!andquery_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
- ✅ Verify Modbus port in documentation
- ✅ Design architecture approaches (minimal, clean, pragmatic)
- ✅ Select approach with user
- ✅ Create detailed implementation plan
- ✅ 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