Files
sta/README.md

377 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<h1 align="center">STA</h1>
<div align="center">
<strong>
Smart Temperature & Appliance Control
</strong>
</div>
<br/>
<div align="center">
<!-- Wakapi -->
<img alt="Coding Time Badge" src="https://clock.phundrak.com/api/badge/phundrak/interval:any/project:sta">
<!-- Emacs -->
<a href="https://www.gnu.org/software/emacs/"><img src="https://img.shields.io/badge/Emacs-30.2-blueviolet.svg?style=flat-square&logo=GNU%20Emacs&logoColor=white" /></a>
</div>
<br/>
> **🤖 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 14 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<CorsSettings> 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.