# 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