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.
178 lines
7.7 KiB
Markdown
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
|