docs: complete project research (stack, features, architecture, pitfalls, summary)
This commit is contained in:
476
.planning/research/ARCHITECTURE.md
Normal file
476
.planning/research/ARCHITECTURE.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# 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 |
|
||||
|
||||
## Recommended Project Structure
|
||||
|
||||
```
|
||||
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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
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
|
||||
|
||||
- Wikipedia: Remote Terminal Unit (RTU) - https://en.wikipedia.org/wiki/Remote_terminal_unit
|
||||
- React Documentation - https://react.dev/learn
|
||||
- Vite Documentation - https://vitejs.dev/guide/
|
||||
- shadcn/ui Documentation - https://ui.shadcn.com/docs
|
||||
- MDN: Progressive Web Apps - https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps
|
||||
- Raspberry Pi GPIO Documentation (training data)
|
||||
- SCADA System Architecture Patterns (training data)
|
||||
|
||||
---
|
||||
*Architecture research for: TCKRTUIYO RTU Rainfall Monitoring System*
|
||||
*Researched: 2026-03-13*
|
||||
Reference in New Issue
Block a user