- System Overview
- Core Components
- Data Flow: Board to Frontend
- Command Flow: Frontend to Board
- BLCU/TFTP Operations
- Network Architecture
- Known Issues and Recommendations
- Troubleshooting Guide
The Hyperloop UPV Control Station is a real-time monitoring and control system that bridges the gap between the pod's embedded systems and human operators. It consists of:
- Backend (Go): High-performance server handling network communication, packet processing, and state management
- Frontend (React/TypeScript): Real-time web interfaces for monitoring and control
- ADJ System: JSON-based configuration defining packet structures, board specifications, and communication protocols
- Real-time Performance: Sub-10ms fault detection to emergency stop
- Modular Architecture: Board-agnostic design using ADJ specifications
- Type Safety: Strongly typed packet definitions (backend), working towards frontend type safety
- Fault Tolerance: Automatic reconnection, graceful degradation
- Scalability: Concurrent packet processing, efficient memory usage
backend/
├── cmd/main.go # Entry point, initialization
├── internal/
│ ├── adj/ # ADJ parser and validator
│ ├── pod_data/ # Packet structure definitions
│ └── vehicle/ # Vehicle state management
└── pkg/
├── transport/ # Network layer (TCP/UDP/TFTP)
├── presentation/ # Packet encoding/decoding
├── broker/ # Message distribution
├── websocket/ # Frontend communication
└── boards/ # Board-specific logic (BLCU)
control-station/
├── src/
│ ├── services/ # WebSocket handlers
│ ├── components/ # UI components
│ └── state.ts # State management
common-front/
└── lib/
├── wsHandler/ # WebSocket abstraction
├── models/ # Type definitions
└── adapters/ # Data transformers
adj/
├── general_info.json # Ports, addresses, units
├── boards.json # Board registry
└── boards/
└── [BOARD_NAME]/
├── [BOARD_NAME].json # Board config
├── measurements.json # Data definitions
├── packets.json # Board→Backend
└── orders.json # Backend→Board
Boards send binary packets with this structure:
┌─────────────┬─────────────────────────┐
│ Header (2B) │ Payload (variable) │
├─────────────┼─────────────────────────┤
│ Packet ID │ Data fields per ADJ │
│ (uint16 LE) │ specification │
└─────────────┴─────────────────────────┘
Example: Temperature sensor packet
64 00 // Packet ID: 100 (little-endian)
01 // sensor_id: 1 (uint8)
CD CC 8C 41 // temperature: 17.6°C (float32 LE)
10 27 00 00 // timestamp: 10000ms (uint32 LE)
The backend receives packets through multiple channels:
- TCP Server (port 50500): Boards connect to backend
- TCP Client (port 50401): Backend connects to boards
- UDP Sniffer (port 50400): High-frequency sensor data
- TFTP (port 69): Firmware transfers (BLCU only)
// Simplified reception flow
func (t *Transport) HandleClient(config ClientConfig, boardAddr string) {
conn, err := net.Dial("tcp", boardAddr)
for {
packet := t.readPacket(conn)
t.routePacket(packet)
}
}The presentation layer decodes packets based on ADJ definitions:
// Decoding process
id := binary.LittleEndian.Uint16(data[0:2])
decoder := t.decoders[id]
packet := decoder.Decode(data[2:])
// Apply unit conversions
for field, value := range packet.Fields {
measurement := adj.GetMeasurement(field)
value = applyConversion(value, measurement.PodUnits, measurement.DisplayUnits)
}The vehicle layer processes packets and updates system state:
// Vehicle processing
func (v *Vehicle) ProcessPacket(packet Packet) {
// Update internal state
v.state.Update(packet)
// Check safety conditions
v.checkProtections(packet)
// Distribute via broker
v.broker.Publish("data/update", packet)
}The broker converts packets to JSON and sends to connected clients:
{
"topic": "data/update",
"payload": {
"board_id": 4,
"packet_id": 320,
"packet_name": "lcu_coil_current",
"timestamp": "2024-01-15T10:30:45.123Z",
"measurements": {
"lcu_coil_current_1": {
"value": 2.5,
"type": "float32",
"units": "A",
"enabled": true
}
}
}
}The React frontend receives and displays data:
// WebSocket handler
wsHandler.subscribe("data/update", {
id: "unique-id",
cb: (data: PacketUpdate) => {
// Update UI components
updateMeasurement(data.measurements);
updateCharts(data);
}
});Frontend creates structured order objects:
const order: Order = {
id: 9995, // From ADJ orders.json
fields: {
"ldu_id": {
value: 1,
isEnabled: true,
type: "uint8"
},
"lcu_desired_current": {
value: 2.5,
isEnabled: true,
type: "float32"
}
}
};wsHandler.post("order/send", order);Sends JSON message:
{
"topic": "order/send",
"payload": {
"id": 9995,
"fields": {
"ldu_id": { "value": 1, "type": "uint8" },
"lcu_desired_current": { "value": 2.5, "type": "float32" }
}
}
}// Order processing pipeline
func (b *Broker) HandleOrder(order Order) {
// 1. Validate order exists in ADJ
boardName := b.getTargetBoard(order.ID)
if !b.adj.ValidateOrder(boardName, order.ID) {
return error("Invalid order ID")
}
// 2. Create packet structure
packet := b.createPacket(order)
// 3. Encode to binary
data := b.encoder.Encode(packet)
// 4. Send to board
b.transport.SendTo(boardName, data)
}The encoder converts the order to binary format:
Order: { id: 9995, ldu_id: 1, current: 2.5 }
Binary output:
0B 27 // ID: 9995 (0x270B little-endian)
01 // ldu_id: 1
00 00 20 40 // current: 2.5 (float32 LE)
The transport layer sends the binary data to the target board via TCP.
The BLCU (BootLoader Control Unit) handles firmware updates using TFTP protocol.
- Frontend initiates upload:
wsHandler.exchange("blcu/upload", {
board: "LCU",
filename: "firmware.bin",
data: base64Data
});- Backend sends order to BLCU:
// Send upload order (ID: 700)
ping := createPacket(BlcuUploadOrderId, targetBoard)
transport.Send(ping)- Wait for ACK:
<-blcu.ackChan // Blocks until ACK received- TFTP Transfer:
client := tftp.NewClient(blcu.ip)
client.WriteFile(filename, tftp.BinaryMode, data)- Progress updates via WebSocket:
{
"topic": "blcu/register",
"payload": {
"operation": "upload",
"progress": 0.65,
"bytes_transferred": 340000,
"total_bytes": 524288
}
}Similar process but uses:
- Order ID: 701
client.ReadFile()for TFTP- Returns file data to frontend
type TFTPConfig struct {
BlockSize: 131072, // 128KB blocks
Retries: 3,
TimeoutMs: 5000,
BackoffFactor: 2,
EnableProgress: true
}- Backend: 192.168.0.9
- Boards: 192.168.1.x (defined in ADJ)
- BLCU: Typically 192.168.1.254
| Service | Port | Protocol | Direction | Purpose |
|---|---|---|---|---|
| TCP Server | 50500 | TCP | Board→Backend | Board connections |
| TCP Client | 50401 | TCP | Backend→Board | Backend initiates |
| UDP | 50400 | UDP | Bidirectional | High-freq data |
| TFTP | 69 | UDP | Bidirectional | Firmware transfer |
| WebSocket | 8080 | TCP/HTTP | Frontend→Backend | UI communication |
| SNTP | 123 | UDP | Board→Backend | Time sync (optional) |
The backend maintains persistent TCP connections with automatic reconnection:
// Exponential backoff for reconnection
backoff := 100ms
for {
conn, err := net.Dial("tcp", boardAddr)
if err != nil {
time.Sleep(backoff)
backoff = min(backoff * 1.5, 5s)
continue
}
backoff = 100ms
handleConnection(conn)
}-
BLCU Hardcoded Configuration
- Issue: BLCU packet IDs (700, 701) hardcoded in backend
- Impact: Cannot adapt to different BLCU versions
- Fix: Move BLCU configuration to ADJ like other boards
-
WebSocket Type Safety
- Issue: Frontend uses
anytype for payloads - Impact: Runtime errors, poor IDE support
- Fix: Generate TypeScript types from ADJ specifications
- Issue: Frontend uses
-
Monolithic main.go
- Issue: 800+ lines in single file
- Impact: Hard to maintain and test
- Fix: Refactor into logical modules
-
Error Handling Standardization
- Implement consistent error types
- Add proper error wrapping
- Improve error messages for operators
-
Testing Coverage
- Current: ~30%
- Target: 80%+
- Focus on packet encoding/decoding edge cases
-
Configuration Management
- Implement hot reload for non-critical settings
- Add configuration validation
- Support environment-specific configs
-
Security Enhancements
- Add authentication for WebSocket connections
- Implement TLS for external connections
- Add audit logging for critical operations
-
Connection Pooling
- Implement proper connection pool with health checks
- Add connection limits and metrics
- Support load balancing for multiple boards
-
Message Batching
- Batch WebSocket updates for better performance
- Implement configurable update rates
- Add client-side throttling
- Check board is in config.toml:
[vehicle]
boards = ["LCU", "HVSCU", "BMSL"]- Verify network connectivity:
ping 192.168.1.4 # Board IP
netstat -an | grep 504 # Check connections- Check ADJ configuration:
cat adj/boards.json | jq
cat adj/boards/LCU/LCU.json | jq- Verify order exists in ADJ:
jq '.[] | select(.id == 9995)' adj/boards/LCU/orders.json- Check WebSocket message format:
- Open browser DevTools → Network → WS
- Verify message structure matches specification
- Check backend logs:
tail -f trace.json | jq 'select(.msg | contains("order"))'- Check BLCU connection:
- Verify BLCU IP in ADJ
- Test TFTP connectivity
- Check ACK timeout
- Verify file format:
- Binary files only
- Check file size limits
- Verify checksums if implemented
- Check browser console for errors
- Verify backend is running: Check PID file
- Network issues: Check for firewall/proxy interference
# Monitor all packet flow
tail -f trace.json | jq
# Filter specific board
tail -f trace.json | jq 'select(.board_id == 4)'
# Watch WebSocket messages
# In browser: DevTools → Network → WS → Messages
# Check system resources
htop # CPU/Memory usage
iftop # Network usage
# Capture network traffic
sudo tcpdump -i any -w capture.pcap 'port 50400 or port 50500'# Backend profiling (if enabled)
go tool pprof http://localhost:4040/debug/pprof/profile
# Check message rates
tail -f trace.json | jq -r .timestamp | uniq -c
# Monitor connection count
netstat -an | grep -c ESTABLISHEDThis document represents the complete architecture of the Hyperloop UPV Control Station as of 2025. For updates and corrections, please submit a pull request.