Files
sta/specs/001-modbus-relay-control/decisions.md
Lucien Cartier-Tilet 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

178 lines
7.7 KiB
Markdown

# 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**:
```sql
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**:
```toml
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