2026-05-14 20:23:40 +02:00
2026-05-15 10:20:14 +02:00
2026-05-15 10:20:14 +02:00
2026-05-15 10:20:14 +02:00
2026-01-11 00:39:18 +01:00
2026-01-11 00:39:18 +01:00
2026-05-15 01:25:57 +02:00
2026-05-15 01:25:57 +02:00
2026-05-15 10:20:14 +02:00
2026-05-15 01:09:55 +02:00
2026-05-15 01:09:55 +02:00

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 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

# 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:

modbus:
  host: "192.168.1.200"
  port: 502
  slave_id: 1
  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 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 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

Architecture Documentation

Development Guides

License

This project is under the AGPL-3.0 license. You can find it in the LICENSE.md file.

Description
Smart Temperature & Appliance control
Readme 1.2 MiB
Languages
Rust 92.4%
TypeScript 3.1%
CSS 1.8%
Vue 1.3%
Nix 1%
Other 0.4%