Files
sp80/.planning/research/ARCHITECTURE.md

26 KiB

Architecture Research: RTU Web Interface

Domain: Embedded IoT/SCADA Web Interface for Rainfall Monitoring
Researched: 2026-03-13
Confidence: HIGH

System Overview

Modern RTU (Remote Terminal Unit) web interfaces are embedded applications that bridge physical sensors with human operators. For a Raspberry Pi-based rainfall monitoring system, the architecture must balance real-time data visualization, configuration management, and remote accessibility while operating on constrained hardware.

┌─────────────────────────────────────────────────────────────────────────────┐
│                          PRESENTATION LAYER                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐             │
│  │  Dashboard UI   │  │  Settings UI    │  │ Calibration UI  │             │
│  │  (Home Screen)  │  │  (11 Submenus)  │  │  (ADC/Levels)   │             │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘             │
│           │                    │                    │                       │
│           └────────────────────┼────────────────────┘                       │
│                                ↓                                            │
│                    ┌─────────────────────┐                                  │
│                    │   Navigation/Router   │                                  │
│                    └─────────────────────┘                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                          STATE MANAGEMENT LAYER                              │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                    Sensor Data Store                                 │   │
│  │  (Rainfall, Voltage, ADC, Status)                                    │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                    Configuration Store                               │   │
│  │  (Settings, Calibration, Network, Station Info)                      │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────────────────────┤
│                         DATA ACCESS LAYER                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │
│  │ Sensor API   │  │ Config API   │  │ File Manager │  │ Network API  │    │
│  │ (Real-time)  │  │ (CRUD)       │  │ (CSV/Flash)  │  │ (FTP/SCP)    │    │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘    │
│           │              │              │              │                   │
├───────────┴──────────────┴──────────────┴──────────────┴───────────────────┤
│                         HARDWARE INTERFACE LAYER                             │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │
│  │ Rainfall     │  │ ADC Inputs   │  │ GPIO/Power   │  │ Network      │    │
│  │ Sensor       │  │ (4-20mA)     │  │ Monitoring   │  │ Interface    │    │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘    │
└─────────────────────────────────────────────────────────────────────────────┘

Component Responsibilities

Component Responsibility Implementation Notes
Dashboard UI Real-time sensor visualization, status indicators, quick actions Optimized for 1024x600, refresh every 1-5s
Settings UI 11 configuration views (Utility, Calibration, Flash, Mobile, ADC, Rain, EVAP, GPRS, Level, Siren, Network) Form-based, validation, persistence
Calibration UI ADC channel calibration, level sensor calibration Wizard-style, step-by-step
Navigation Route management between views, breadcrumb trails React Router, conditional rendering
Sensor Data Store Real-time sensor readings, historical data buffering In-memory + localStorage for resilience
Config Store Application settings, station configuration, calibration values localStorage/IndexedDB
API Layer Abstract hardware communication, RESTful interface to backend Fetch API, error handling, retries
Hardware Interface GPIO access, serial communication, ADC reading Python backend or Node.js native addons
src/
├── components/
│   ├── ui/                    # shadcn/ui base components
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── input.tsx
│   │   ├── select.tsx
│   │   └── ...
│   ├── dashboard/             # Dashboard-specific components
│   │   ├── SensorCard.tsx
│   │   ├── RainfallDisplay.tsx
│   │   ├── VoltageGauge.tsx
│   │   └── StatusBar.tsx
│   ├── settings/              # Settings view components
│   │   ├── SettingsLayout.tsx
│   │   ├── SettingsNav.tsx
│   │   └── forms/
│   │       ├── AdcSettingsForm.tsx
│   │       ├── NetworkSettingsForm.tsx
│   │       └── ...
│   └── layout/                # App shell components
│       ├── AppLayout.tsx
│       ├── Header.tsx
│       ├── Sidebar.tsx
│       └── KioskWrapper.tsx
├── hooks/
│   ├── useSensorData.ts       # Real-time sensor data hook
│   ├── useConfig.ts           # Configuration management hook
│   ├── useHardware.ts         # Hardware status hook
│   └── useNetwork.ts          # Network status hook
├── stores/
│   ├── sensorStore.ts         # Sensor data state management
│   ├── configStore.ts         # Configuration state management
│   └── uiStore.ts             # UI state (theme, layout mode)
├── services/
│   ├── api.ts                 # API client configuration
│   ├── sensorService.ts       # Sensor data API calls
│   ├── configService.ts       # Configuration API calls
│   └── fileService.ts         # File management API calls
├── types/
│   ├── sensor.ts              # Sensor data types
│   ├── config.ts              # Configuration types
│   └── api.ts                 # API response types
├── utils/
│   ├── formatters.ts          # Data formatting utilities
│   ├── validators.ts          # Form validation
│   └── constants.ts           # App constants
├── views/
│   ├── Dashboard.tsx          # Main dashboard view
│   ├── Settings/              # Settings views
│   │   ├── index.tsx
│   │   ├── AdcSettings.tsx
│   │   ├── NetworkSettings.tsx
│   │   └── ...
│   ├── Calibration.tsx        # Calibration view
│   └── FlashMemory.tsx        # File manager view
├── App.tsx                    # Root component
├── main.tsx                   # Entry point
└── index.css                  # Global styles + Tailwind

Structure Rationale

  • components/ui/: shadcn/ui components are copy-paste ready, open for modification
  • components/dashboard/: Dashboard-specific visualization components, optimized for 7" display
  • components/settings/: Modular settings forms, one per configuration category
  • hooks/: Custom React hooks for data fetching and hardware interaction
  • stores/: State management (Zustand recommended for minimal overhead)
  • services/: API abstraction layer for hardware communication
  • views/: Page-level components corresponding to routes

Architectural Patterns

Pattern 1: Dual-Mode Display Architecture

What: Single codebase supporting two display modes:

  • Kiosk Mode (1024x600): Fixed layout, touch-optimized, no browser chrome
  • Remote Mode (Full HD): Responsive layout, desktop-friendly, expanded details

When to use: When RTU has both local touchscreen display and remote web access requirements.

Trade-offs:

  • Pros: Single codebase, consistent UX, easier maintenance
  • Cons: Conditional complexity, testing matrix doubles

Implementation:

// Detect display mode based on port or viewport
const useDisplayMode = () => {
  const [mode, setMode] = useState<'kiosk' | 'remote'>('kiosk');
  
  useEffect(() => {
    // Detect from window location (port 8080 = kiosk, 9090 = remote)
    const port = window.location.port;
    setMode(port === '9090' ? 'remote' : 'kiosk');
  }, []);
  
  return mode;
};

// Usage in components
const SensorCard = () => {
  const mode = useDisplayMode();
  return mode === 'kiosk' ? <CompactCard /> : <DetailedCard />;
};

Pattern 2: Polling-Based Real-Time Updates

What: Periodic data fetching with optimistic UI updates for sensor readings.

When to use: When WebSocket is not available or too resource-intensive for embedded hardware.

Trade-offs:

  • Pros: Simple, works on constrained hardware, battery-friendly
  • Cons: Not truly real-time, potential for stale data

Implementation:

const useSensorPolling = (interval = 5000) => {
  const [data, setData] = useState<SensorData | null>(null);
  const [isStale, setIsStale] = useState(false);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await sensorService.getCurrent();
        setData(response);
        setIsStale(false);
      } catch (error) {
        setIsStale(true);
      }
    };
    
    fetchData();
    const intervalId = setInterval(fetchData, interval);
    
    return () => clearInterval(intervalId);
  }, [interval]);
  
  return { data, isStale };
};

Pattern 3: Offline-First Configuration

What: Configuration changes persisted locally first, synced to backend asynchronously.

When to use: When network connectivity is intermittent or when critical settings must survive power cycles.

Trade-offs:

  • Pros: Resilient to failures, fast UI response, works offline
  • Cons: Conflict resolution complexity, potential for desync

Implementation:

interface ConfigStore {
  local: Config;
  remote: Config | null;
  syncStatus: 'synced' | 'pending' | 'error';
  updateConfig: (update: Partial<Config>) => Promise<void>;
}

const useConfigStore = create<ConfigStore>((set, get) => ({
  local: loadFromLocalStorage(),
  remote: null,
  syncStatus: 'synced',
  
  updateConfig: async (update) => {
    // Update local immediately
    const newConfig = { ...get().local, ...update };
    set({ local: newConfig, syncStatus: 'pending' });
    saveToLocalStorage(newConfig);
    
    // Sync to backend
    try {
      await configService.save(newConfig);
      set({ remote: newConfig, syncStatus: 'synced' });
    } catch (error) {
      set({ syncStatus: 'error' });
      // Will retry on next connection
    }
  }
}));

Data Flow

Request Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                              USER ACTION                                     │
│                         (Touch/Button Click)                                 │
└─────────────────────────────────────────────────────────────────────────────┘
                                      ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│                           REACT COMPONENT                                    │
│                    (Event Handler Triggered)                                 │
└─────────────────────────────────────────────────────────────────────────────┘
                                      ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│                           CUSTOM HOOK                                        │
│                    (Business Logic / Validation)                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                      ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│                           SERVICE LAYER                                      │
│                    (API Call to Python Backend)                              │
└─────────────────────────────────────────────────────────────────────────────┘
                                      ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│                           HARDWARE LAYER                                     │
│                    (GPIO / Serial / ADC Read/Write)                          │
└─────────────────────────────────────────────────────────────────────────────┘
                                      ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│                           RESPONSE FLOW                                      │
│                    (Data → Store → Component Re-render)                      │
└─────────────────────────────────────────────────────────────────────────────┘

State Management

┌─────────────────────────────────────────────────────────────────────────────┐
│                              ZUSTAND STORE                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐             │
│  │  Sensor Data    │  │  Configuration  │  │  UI State       │             │
│  │  (Real-time)    │  │  (Persistent)   │  │  (Transient)    │             │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘             │
│           │                    │                    │                       │
│           └────────────────────┼────────────────────┘                       │
│                                ↓                                            │
│                    ┌─────────────────────┐                                  │
│                    │   Selectors / Hooks  │                                  │
│                    └─────────────────────┘                                  │
│                                ↓                                            │
│                    ┌─────────────────────┐                                  │
│                    │   React Components   │                                  │
│                    └─────────────────────┘                                  │
└─────────────────────────────────────────────────────────────────────────────┘

Key Data Flows

  1. Sensor Data Flow:

    • Hardware (GPIO/ADC) → Python Backend → REST API → React Hook → Component
    • Update frequency: 1-5 seconds for display, configurable for logging
  2. Configuration Flow:

    • User Input → Validation → Local Storage → API Sync → Hardware Apply
    • Two-phase commit: UI optimistic, backend async
  3. File Management Flow:

    • CSV files stored on Flash/USB → Python File Service → Frontend Display
    • Upload/Download via HTTP multipart or WebDAV/FTP/SCP for remote

Build Order Implications

Based on component dependencies, suggested build order:

Phase 1: Foundation
├── Core UI components (shadcn/ui setup)
├── Type definitions
├── API client infrastructure
└── Basic routing

Phase 2: Dashboard
├── Sensor data hooks
├── Dashboard layout
├── Sensor card components
└── Real-time data display

Phase 3: Settings Framework
├── Settings navigation
├── Form components
├── Config store
└── Settings persistence

Phase 4: Individual Settings
├── Station Info
├── Date/Time
├── Network Setup
├── Mobile Settings
└── ADC Settings

Phase 5: Advanced Features
├── Calibration views
├── File manager
├── Network stack (FTP/SCP/SFTP/WebDAV)
└── CSV processing

Phase 6: Dual-Mode Support
├── Kiosk mode optimization (1024x600)
├── Remote mode layout (Full HD)
├── Responsive adaptations
└── Performance tuning

Critical Path:

  • Dashboard requires sensor API → Build sensor service first
  • Settings require config API → Build config service before settings forms
  • File manager requires backend file service → Coordinate with Python backend team

Scaling Considerations

Scale Users Architecture Adjustments
Single RTU 1 local + few remote Current architecture sufficient
Multi-RTU Fleet 10-100 devices Add centralized management dashboard
Enterprise 1000+ devices Separate management server, device fleet API

Scaling Priorities for Single RTU

  1. First bottleneck: UI responsiveness on Pi Zero 2 W

    • Fix: Code splitting, lazy loading, virtual scrolling for large datasets
  2. Second bottleneck: Storage for historical data

    • Fix: Implement data retention policies, CSV rotation, external storage
  3. Third bottleneck: Network throughput for file transfers

    • Fix: Compression, chunked transfers, background sync

Anti-Patterns to Avoid

Anti-Pattern 1: Heavy Frameworks on Embedded

What people do: Use Next.js, heavy state management (Redux), or large component libraries.

Why it's wrong: Pi Zero 2 W has limited RAM (512MB) and single-core performance. Bundle size directly impacts load time and runtime performance.

Do this instead:

  • Use Vite for minimal overhead build
  • Zustand or React Context for state (not Redux)
  • Copy-paste components (shadcn/ui) instead of importing entire libraries
  • Tree-shake aggressively, analyze bundle size

Anti-Pattern 2: Real-Time Everything

What people do: WebSocket connections for all data, constant polling at high frequency.

Why it's wrong: Drains battery (if solar-powered), saturates network, unnecessary for rainfall data that changes slowly.

Do this instead:

  • Adaptive polling: fast during events, slow during idle
  • Only subscribe to actively viewed sensors
  • Use caching and display stale data with indicators

Anti-Pattern 3: Tight Hardware Coupling

What people do: Direct GPIO access from frontend JavaScript (via unsafe methods).

Why it's wrong: Security risk, platform lock-in, hard to test.

Do this instead:

  • Clean API boundary between UI and hardware
  • Python backend service for hardware access
  • Mock API for development/testing

Anti-Pattern 4: Ignoring Kiosk Constraints

What people do: Design for desktop, shoehorn into kiosk mode.

Why it's wrong: 1024x600 is cramped, touch targets must be large, no right-click context menus.

Do this instead:

  • Design mobile-first, even for kiosk
  • Minimum 44px touch targets
  • Large fonts, high contrast
  • No hover-dependent interactions

Integration Points

External Services

Service Integration Pattern Notes
Sensor Hardware REST API via Python backend GPIO, I2C, SPI abstraction
Network Stack Background service + API FTP/SCP/SFTP/WebDAV daemons
CSV Export File system + HTTP download Scheduled generation, on-demand download
Remote Server HTTP POST / MQTT Data transmission, status heartbeat

Internal Boundaries

Boundary Communication Notes
Frontend ↔ Backend HTTP REST API JSON, stateless, retry logic
Backend ↔ Hardware Python libraries GPIOZero, smbus2, spidev
Config ↔ Storage localStorage/IndexedDB Offline resilience
UI ↔ State Zustand hooks Minimal re-renders

Sources


Architecture research for: TCKRTUIYO RTU Rainfall Monitoring System
Researched: 2026-03-13