Update README to reflect completed Phase 3 infrastructure layer: - Documented ModbusRelayController, MockRelayController, SqliteRelayLabelRepository, and HealthMonitor implementations - Added testing coverage details (20+ tests across infrastructure components) - Updated architecture diagrams and project structure - Changed task reference to tasks.org format - Updated dependency list with production infrastructure dependencies Ref: Phase 3 tasks in specs/001-modbus-relay-control/tasks.org
STA - Smart Temperature & Appliance Control
🤖 AI-Assisted Development Notice: This project uses Claude Code as a development assistant for task planning, code organization, and workflow management. However, all code is human-written, reviewed, and validated by the project maintainer. AI is used as a productivity tool, not as the author of the implementation.
Web-based Modbus relay control system for managing 8-channel relay modules over TCP.
⚠️ Development Status: This project is in early development. Core features are currently being implemented following a specification-driven approach.
Overview
STA will provide a modern web interface for controlling Modbus-compatible relay devices, eliminating the need for specialized industrial software. The goal is to enable browser-based relay control with real-time status updates.
Current Status
Phase 1 Complete - Foundation
- ✅ Monorepo structure (backend + frontend at root)
- ✅ Rust web server with Poem 3.1 framework
- ✅ Configuration system (YAML + environment variables)
- ✅ Modbus TCP and relay settings structures
- ✅ Health check and metadata API endpoints
- ✅ OpenAPI documentation with Swagger UI
- ✅ Rate limiting middleware
- ✅ SQLite schema and repository for relay labels
- ✅ Vue 3 + TypeScript frontend scaffolding with Vite
- ✅ Type-safe API client generation from OpenAPI specs
Phase 0.5 Complete - CORS Configuration & Production Security
- ✅ T009: CorsSettings struct with comprehensive unit tests (5 tests)
- ✅ T010: CorsSettings implementation with restrictive fail-safe defaults
- ✅ T011: Development YAML configuration with permissive CORS
- ✅ T012: Production YAML configuration with restrictive CORS
- ✅ T013: From for Cors trait unit tests (6 tests)
- ✅ T014: From for Cors implementation with security validation
- ✅ T015: Middleware chain integration using From trait
- ✅ T016: Integration tests for CORS headers (9 comprehensive tests)
Key CORS Features Implemented
- Environment-specific CORS configuration (development vs production)
- Wildcard origin support for development (
allowed_origins: ["*"]) - Multiple specific origins for production
- Credentials support for Authelia authentication
- Security validation (prevents wildcard + credentials)
- Configurable preflight cache duration
- Hardcoded secure methods and headers
- Structured logging for CORS configuration
- Comprehensive test coverage (15 tests total)
Phase 2 Complete - Domain Layer (Type-Driven Development)
- ✅ T017-T018: RelayId newtype with 1-8 validation and zero-cost abstraction
- ✅ T019-T020: RelayState enum (On/Off) with serialization support
- ✅ T021-T022: Relay aggregate with state control methods (toggle, turn_on, turn_off)
- ✅ T023-T024: RelayLabel newtype with 1-50 character validation
- ✅ T025-T026: ModbusAddress type with From trait (1-8 → 0-7 offset mapping)
- ✅ T027: HealthStatus enum with state machine (Healthy/Degraded/Unhealthy)
Key Domain Layer Features Implemented
- 100% test coverage for domain layer (50+ comprehensive tests)
- Zero external dependencies (pure business logic)
- All newtypes use
#[repr(transparent)]for zero-cost abstractions - Smart constructors with
Result<T, E>for type-safe validation - TDD workflow (red-green-refactor) for all implementations
- RelayController and RelayLabelRepository trait definitions
- Complete separation from infrastructure concerns (hexagonal architecture)
Phase 3 Complete - Infrastructure Layer
- ✅ T028-T029: MockRelayController tests and implementation
- ✅ T030: RelayController trait with async methods (read_state, write_state, read_all, write_all)
- ✅ T031: ControllerError enum (ConnectionError, Timeout, ModbusException, InvalidRelayId)
- ✅ T032: MockRelayController comprehensive tests (6 tests)
- ✅ T025a-f: ModbusRelayController implementation (decomposed):
- Connection setup with tokio-modbus
- Timeout-wrapped read_coils and write_single_coil helpers
- RelayController trait implementation
- ✅ T034: Integration test with real hardware (uses #[ignore] attribute)
- ✅ T035-T036: RelayLabelRepository trait and SQLite implementation
- ✅ T037-T038: MockRelayLabelRepository for testing
- ✅ T039-T040: HealthMonitor service with state tracking
Key Infrastructure Features Implemented
- ModbusRelayController: Thread-safe Modbus TCP client with timeout handling
- Uses
Arc<Mutex<Context>>for concurrent access - Native Modbus TCP protocol (MBAP header, no CRC16)
- Configurable timeout with
tokio::time::timeout
- Uses
- MockRelayController: In-memory testing without hardware
- Uses
Arc<Mutex<HashMap<RelayId, RelayState>>>for state - Optional timeout simulation for error handling tests
- Uses
- SqliteRelayLabelRepository: Compile-time verified SQL queries
- Automatic migrations via SQLx
- In-memory mode for testing
- HealthMonitor: State machine for health tracking
- Healthy -> Degraded -> Unhealthy transitions
- Recovery on successful operations
Planned - Phases 4-8
- 📋 US1: Monitor & toggle relay states - MVP (Phase 4)
- 📋 US2: Bulk relay controls (Phase 5)
- 📋 US3: Health status display (Phase 6)
- 📋 US4: Relay labeling (Phase 7)
- 📋 Production deployment (Phase 8)
See tasks.org for detailed implementation roadmap.
Architecture
Current:
- Backend: Rust 2024 with Poem web framework (hexagonal architecture)
- Configuration: YAML-based with environment variable overrides
- API: RESTful HTTP with OpenAPI documentation
- CORS: Production-ready configurable middleware with security validation
- Middleware Chain: Rate Limiting -> CORS -> Data injection
- Modbus Integration: tokio-modbus for Modbus TCP communication
- Persistence: SQLite for relay labels with compile-time SQL verification
Planned:
- Frontend: Vue 3 with TypeScript
- Deployment: Backend on Raspberry Pi, frontend on Cloudflare Pages
- Access: Traefik reverse proxy with Authelia authentication
Quick Start
Prerequisites
- Rust 1.83+ (edition 2024)
- Just command runner
Development
# Run development server
just run
# Run tests
just test
# Run linter
just lint
# Format code
just format
# Watch mode with bacon
bacon # clippy-all (default)
bacon test # test watcher
Configuration
Edit backend/settings/base.yaml for Modbus device settings:
modbus:
host: "192.168.0.200"
port: 502
slave_id: 0
timeout_secs: 5
relay:
label_max_length: 50
Override with environment variables:
APP__MODBUS__HOST=192.168.1.100 cargo run
CORS Configuration
Development Mode (frontend on localhost:5173):
# backend/settings/development.yaml
cors:
allowed_origins:
- "*" # Permissive for local development
allow_credentials: false # MUST be false with wildcard
max_age_secs: 3600
Production Mode (frontend on Cloudflare Pages):
# backend/settings/production.yaml
cors:
allowed_origins:
- "https://sta.yourdomain.com" # Specific origin only
allow_credentials: true # Required for Authelia authentication
max_age_secs: 3600
Security Notes:
- Wildcard
"*"origin is only allowed withallow_credentials: false - Production must use specific origins (e.g.,
https://sta.example.com) - Multiple origins are supported as a list
- Credentials must be enabled for Authelia authentication to work
Hardcoded Security Defaults:
- Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS (all required methods)
- Headers: content-type, authorization (minimum for API + auth)
Fail-Safe Defaults:
allowed_origins: [](restrictive - no origins allowed)allow_credentials: falsemax_age_secs: 3600(1 hour)
See CORS Configuration Guide for complete documentation.
API Documentation
The server provides OpenAPI documentation via Swagger UI:
- Swagger UI:
http://localhost:3100/ - OpenAPI Spec:
http://localhost:3100/specs
Current Endpoints:
GET /api/health- Health check endpointGET /api/meta- Application metadata
Planned Endpoints (see spec):
GET /api/relays- List all relay statesPOST /api/relays/{id}/toggle- Toggle relay statePOST /api/relays/all/on- Turn all relays onPOST /api/relays/all/off- Turn all relays offPUT /api/relays/{id}/label- Set relay label
Project Structure
Monorepo Layout:
sta/ # Repository root
├── backend/ # Rust backend workspace member
│ ├── src/
│ │ ├── lib.rs - Library entry point
│ │ ├── main.rs - Binary entry point
│ │ ├── startup.rs - Application builder and server config
│ │ ├── telemetry.rs - Logging and tracing setup
│ │ │
│ │ ├── domain/ - Business logic layer (Phase 2)
│ │ │ ├── relay/ - Relay domain aggregate
│ │ │ │ ├── types/ - RelayId, RelayState, RelayLabel newtypes
│ │ │ │ ├── entity.rs - Relay aggregate with state control
│ │ │ │ ├── controller.rs - RelayController trait & ControllerError
│ │ │ │ └── repository/ - RelayLabelRepository trait
│ │ │ ├── modbus.rs - ModbusAddress type with conversion
│ │ │ └── health.rs - HealthStatus state machine
│ │ │
│ │ ├── application/ - Use cases and orchestration (Phase 3)
│ │ │ └── health/ - Health monitoring service
│ │ │ └── health_monitor.rs - HealthMonitor with state tracking
│ │ │
│ │ ├── infrastructure/ - External integrations (Phase 3)
│ │ │ ├── modbus/ - Modbus TCP communication
│ │ │ │ ├── client.rs - ModbusRelayController (real hardware)
│ │ │ │ ├── client_test.rs - Hardware integration tests
│ │ │ │ └── mock_controller.rs - MockRelayController for testing
│ │ │ └── persistence/ - Database layer
│ │ │ ├── entities/ - Database record types
│ │ │ ├── sqlite_repository.rs - SqliteRelayLabelRepository
│ │ │ └── label_repository.rs - MockRelayLabelRepository
│ │ │
│ │ ├── presentation/ - API layer (planned Phase 4)
│ │ ├── settings/ - Configuration module
│ │ │ ├── mod.rs - Settings aggregation
│ │ │ └── cors.rs - CORS configuration
│ │ ├── route/ - HTTP endpoint handlers
│ │ │ ├── health.rs - Health check endpoints
│ │ │ └── meta.rs - Application metadata
│ │ └── middleware/ - Custom middleware
│ │ └── rate_limit.rs
│ │
│ ├── settings/ - YAML configuration files
│ │ ├── base.yaml - Base configuration
│ │ ├── development.yaml - Development overrides
│ │ └── production.yaml - Production overrides
│ └── tests/ - Integration tests
│ └── cors_test.rs - CORS integration tests
│
├── migrations/ - SQLx database migrations
├── src/ # Frontend source (Vue/TypeScript)
│ └── api/ - Type-safe API client
├── docs/ # Project documentation
│ ├── cors-configuration.md - CORS setup guide
│ ├── domain-layer.md - Domain layer architecture
│ └── Modbus_POE_ETH_Relay.md - Hardware documentation
├── specs/ # Feature specifications
│ ├── constitution.md - Architectural principles
│ └── 001-modbus-relay-control/
│ ├── spec.md - Feature specification
│ ├── plan.md - Implementation plan
│ ├── tasks.org - Task breakdown (org-mode format)
│ ├── data-model.md - Data model specification
│ ├── types-design.md - Domain types design
│ ├── domain-layer-architecture.md - Domain layer docs
│ └── lessons-learned.md - Phase 2/3 insights
├── package.json - Frontend dependencies
├── vite.config.ts - Vite build configuration
└── justfile - Build commands
Technology Stack
Currently Used:
- Rust 2024 edition
- Poem 3.1 (web framework with OpenAPI support)
- Tokio 1.48 (async runtime)
- tokio-modbus (Modbus TCP client for relay hardware)
- SQLx 0.8 (async SQLite with compile-time SQL verification)
- async-trait (async methods in traits)
- config (YAML configuration)
- tracing + tracing-subscriber (structured logging)
- governor (rate limiting)
- thiserror (error handling)
- serde + serde_yaml (configuration deserialization)
Frontend (scaffolding complete):
- Vue 3 + TypeScript
- Vite build tool
- openapi-typescript (type-safe API client generation)
Testing Strategy
Phase 0.5 CORS Testing:
- Unit Tests: 11 tests in
backend/src/settings/cors.rs- CorsSettings deserialization (5 tests)
- From for Cors trait (6 tests)
- Security validation (wildcard + credentials check)
- Integration Tests: 9 tests in
backend/tests/cors_test.rs- Preflight OPTIONS requests
- Actual request CORS headers
- Max-age configuration
- Credentials handling
- Allowed methods verification
- Wildcard origin behavior
- Multiple origins support
- Unauthorized origin rejection
Test Coverage Achieved: 15 comprehensive tests covering all CORS scenarios
Phase 2 Domain Layer Testing:
- Unit Tests: 50+ tests embedded in domain modules
- RelayId validation (5 tests)
- RelayState serialization (3 tests)
- RelayLabel validation (5 tests)
- Relay aggregate behavior (8 tests)
- ModbusAddress conversion (3 tests)
- HealthStatus state transitions (15 tests)
- TDD Approach: Red-Green-Refactor for all implementations
- Coverage: 100% for domain layer (zero external dependencies)
Test Coverage Achieved: 100% domain layer coverage with comprehensive test suites
Phase 3 Infrastructure Testing:
- MockRelayController Tests: 6 tests in
mock_controller.rs- Read/write state operations
- Read/write all relay states
- Invalid relay ID handling
- Thread-safe concurrent access
- ModbusRelayController Tests: Hardware integration tests (#[ignore])
- Real hardware communication tests
- Connection timeout handling
- SqliteRelayLabelRepository Tests: Database layer tests
- CRUD operations on relay labels
- In-memory database for fast tests
- Compile-time SQL verification
- HealthMonitor Tests: 15+ tests in
health_monitor.rs- State transitions (Healthy -> Degraded -> Unhealthy)
- Recovery from failure states
- Concurrent access safety
Test Coverage Achieved: Comprehensive coverage across all layers with TDD approach
Documentation
Configuration Guides
- CORS Configuration - Cross-origin setup for frontend-backend communication
- Modbus Hardware Documentation - 8-channel relay device documentation
Architecture Documentation
- Domain Layer Architecture - Type-driven domain design and implementation
- Domain Layer Details - Comprehensive domain layer documentation
- Phase 2 Lessons Learned - Implementation insights and best practices
Development Guides
- Project Constitution - Architectural principles and development guidelines
- Modbus Relay Control Spec - Feature specification
- CLAUDE.md - Developer guide and code style rules
License
This project is under the AGPL-3.0 license. You can find it in the LICENSE.md file.