STA

Smart Temperature & Appliance Control

Coding Time Badge

> **🤖 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. ## 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 **US1 (MVP) — Complete** — Users can view all 8 relay states and toggle individual relays on/off via the web UI, backed by a Rust API with Modbus TCP control. Phases 1–4 complete: domain layer with type-driven development, infrastructure with mock/real Modbus controllers and SQLite persistence, application use cases, REST API with OpenAPI docs, and Vue 3 frontend with real-time polling. ## Architecture **Current:** - **Backend**: Rust 2024 with Poem web framework (hexagonal architecture) - **Frontend**: Vue 3 + TypeScript with real-time polling (2s interval) - **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:** - **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 ```bash # Run backend development server just backend run # Run frontend just frontend run # Run backend tests just backend test # Run backend linter just backend lint # Format backend code just backend format # Watch mode with bacon bacon # clippy-all (default) bacon test # test watcher ``` ### Configuration Edit `backend/settings/base.yaml` for Modbus device settings: ```yaml modbus: host: "192.168.1.200" port: 502 slave_id: 1 timeout_secs: 5 relay: label_max_length: 50 ``` Override with environment variables: ```bash APP__MODBUS__HOST=192.168.1.100 cargo run ``` #### CORS Configuration **Development Mode** (frontend on `localhost:5173`): ```yaml # 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): ```yaml # 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 with `allow_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: false` - `max_age_secs: 3600` (1 hour) See [CORS Configuration Guide](docs/cors-configuration.md) 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/relays` - List all relay states - `POST /api/relays/{id}/toggle` - Toggle individual relay state - `GET /api/health` - Health check endpoint - `GET /api/meta` - Application metadata **Planned Endpoints (see spec):** - `POST /api/relays/all/on` - Turn all relays on - `POST /api/relays/all/off` - Turn all relays off - `PUT /api/relays/{id}/label` - Set relay label ## Project Structure **Monorepo Layout:** ``` sta/ ├── backend/ # Rust backend │ ├── src/ │ │ ├── main.rs - Binary entry point │ │ ├── lib.rs - Library entry point │ │ ├── startup.rs - Application builder and server wiring │ │ ├── telemetry.rs - Logging and tracing setup │ │ │ │ │ ├── domain/ - Business logic │ │ │ ├── health.rs - HealthStatus state machine │ │ │ ├── modbus.rs - ModbusAddress type │ │ │ └── relay/ │ │ │ ├── entity.rs - Relay aggregate (state control) │ │ │ ├── controller.rs - RelayController trait │ │ │ ├── types/ │ │ │ │ ├── relayid.rs - RelayId newtype (1..=8) │ │ │ │ ├── relaylabel.rs - RelayLabel newtype │ │ │ │ └── relaystate.rs - RelayState enum (On/Off) │ │ │ └── repository/ │ │ │ └── label.rs - RelayLabelRepository trait │ │ │ │ │ ├── application/ - Use cases │ │ │ ├── health/ │ │ │ │ └── health_monitor.rs - Health monitoring │ │ │ └── use_cases/ │ │ │ ├── get_all_relays.rs - List all relays │ │ │ └── toggle_relay.rs - Toggle single relay │ │ │ │ │ ├── infrastructure/ - External integrations │ │ │ ├── modbus/ │ │ │ │ ├── client.rs - ModbusRelayController │ │ │ │ ├── client_test.rs - Unit tests │ │ │ │ ├── factory.rs - Controller factory (retry, fallback) │ │ │ │ └── mock_controller.rs - MockRelayController │ │ │ └── persistence/ │ │ │ ├── factory.rs - Repository factory │ │ │ ├── label_repository.rs - SQL implementation │ │ │ ├── label_repository_tests.rs - Unit tests │ │ │ ├── sqlite_repository.rs - SQLite implementation │ │ │ └── entities/ │ │ │ └── relay_label_record.rs - DB row struct │ │ │ │ │ ├── presentation/ - API handlers and DTOs │ │ │ ├── error.rs - API error types │ │ │ ├── api/ │ │ │ │ └── relay_api.rs - Relay HTTP handlers │ │ │ └── dto/ │ │ │ └── relay_dto.rs - Relay DTOs │ │ │ │ │ ├── route/ - Route definitions │ │ │ ├── health.rs - Health check │ │ │ └── meta.rs - App metadata │ │ │ │ │ ├── middleware/ │ │ │ └── rate_limit.rs - Rate limiting │ │ │ │ │ └── settings/ - Configuration │ │ ├── application.rs - App-wide settings │ │ ├── cors.rs - CORS settings │ │ ├── database.rs - Database settings │ │ ├── environment.rs - Environment enum │ │ ├── modbus.rs - Modbus settings │ │ ├── rate_limiting.rs - Rate limit config │ │ └── relay.rs - Relay settings │ │ │ ├── settings/ - YAML config files │ │ ├── base.yaml │ │ ├── development.yaml │ │ └── production.yaml │ │ │ └── tests/ - Integration/contract tests │ ├── contract/ │ │ └── test_relay_api.rs - Relay API contract tests │ ├── cors_test.rs - CORS integration tests │ ├── modbus_hardware_test.rs - Hardware tests (#[ignore]) │ ├── sqlite_repository_test.rs - SQLite integration tests │ └── sqlite_repository_functional_test.rs - Functional tests │ ├── src/ # Frontend (Vue 3 + TypeScript) │ ├── main.ts - App entry point │ ├── App.vue - Root component │ ├── style.css / style.less - Global styles │ ├── api/ │ │ ├── client.ts - HTTP client │ │ └── schema.ts - API types │ ├── components/ │ │ ├── RelayCard.vue - Relay card │ │ ├── StaFooter.vue - Footer │ │ └── StaHeader.vue - Header │ ├── composables/ │ │ ├── useMeta.ts - Page metadata │ │ ├── useRelay.ts - Relay state management │ │ └── useRelayPolling.ts - Real-time polling (2s) │ ├── pages/ │ │ └── RelaysView.vue - Main relay view │ ├── types/ │ │ ├── relay.ts - Relay type definitions │ │ └── mappers/ │ │ └── relayDtoMapper.ts │ └── utils/ │ └── isNil.ts │ ├── migrations/ - SQLx migrations │ ├── 0001_relay-labels.up.sql │ └── 0001_relay-labels.down.sql │ ├── docs/ - Documentation │ ├── cors-configuration.md │ ├── domain-layer.md │ └── Modbus_POE_ETH_Relay.md │ ├── specs/ - Specifications │ ├── constitution.md │ └── 001-modbus-relay-control/ │ ├── spec.md, plan.md, tasks.org │ ├── data-model.md, types-design.md │ ├── domain-layer-architecture.md │ ├── lessons-learned.md │ └── ... │ ├── nix/ - Nix flake configs ├── public/ - Static assets ├── justfile - Build commands ├── package.json ├── vite.config.ts ├── Cargo.toml └── flake.nix ``` ## 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** (US1 complete): - Vue 3 + TypeScript with composables (useRelayPolling) - Vite build tool - RelayCard and RelayGrid components with real-time polling - 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](docs/cors-configuration.md) - Cross-origin setup for frontend-backend communication - [Modbus Hardware Documentation](docs/Modbus_POE_ETH_Relay.md) - 8-channel relay device documentation ### Architecture Documentation - [Domain Layer Architecture](docs/domain-layer.md) - Type-driven domain design and implementation - [Domain Layer Details](specs/001-modbus-relay-control/domain-layer-architecture.md) - Comprehensive domain layer documentation - [Phase 2 Lessons Learned](specs/001-modbus-relay-control/lessons-learned.md) - Implementation insights and best practices ### Development Guides - [Project Constitution](specs/constitution.md) - Architectural principles and development guidelines - [Modbus Relay Control Spec](specs/001-modbus-relay-control/spec.md) - Feature specification - [CLAUDE.md](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](LICENSE.md) file.