BESS Controller Architecture
The BESS Controller is a Go service that runs on RevPi industrial controllers at microgrid sites. It provides real-time battery control, device communication, and market integration capabilities.
System Architecture
Core Components
Main Controller (controller.go)
The central orchestration engine that:
- Manages control loop timing (60-second cycles)
- Prioritizes control modes based on configuration
- Applies constraints (SOE limits, power limits)
- Handles mode transitions and scheduling
type Controller struct {
components []ControlComponent
activeComponent ControlComponent
constraints ActiveConstraints
telemetry TelemetryClient
schedule Schedule
}
Control Components
Each control mode implements the ControlComponent interface:
type ControlComponent interface {
Name() string
Priority() int
IsActive(time.Time) bool
CalculateTarget(state SystemState) (float64, error)
UpdateState(readings TelemetryData)
}
Available Components:
- NIV Chase (
comp_niv_chase.go): Follow grid imbalance signals - Dynamic Peak (
comp_dynamic_peak.go): Reduce peak demand - Import Avoidance (
comp_import_avoidance.go): Prevent grid import - Export Avoidance (
comp_export_avoidance.go): Prevent grid export - Axle Integration (
comp_axle.go): Execute market dispatches - ToSOE (
comp_to_soe.go): Target specific SOE levels
Device Drivers
PowerPack Driver (powerpack/)
Communicates with battery systems:
type PowerPackClient struct {
modbusClient *modbus.Client
address uint8
registers map[string]uint16
}
// Key registers
const (
RegTargetPower = 2008 // Write target power
RegSOE = 2026 // Read state of energy
RegStatus = 2000 // Read system status
)
Acuvim Driver (acuvim2/)
Reads power meter data:
type AcuvimClient struct {
modbusClient *modbus.Client
address uint8
}
// Key measurements
func (c *AcuvimClient) ReadPower() (float64, error)
func (c *AcuvimClient) ReadEnergy() (float64, error)
func (c *AcuvimClient) ReadVoltage() (float64, error)
Data Platform Integration
Supabase Client (supabase/)
Handles cloud data upload:
type SupabaseClient struct {
url string
anonKey string
userKey string
httpClient *http.Client
}
func (c *SupabaseClient) UploadReadings(readings []Reading) error {
// Batch upload with retry logic
// Handles 409 conflicts for duplicates
}
Control Modes
NIV Chase Mode
Follows Net Imbalance Volume to provide grid balancing:
type NivChaseComponent struct {
modoClient *modo.Client
targetFactor float64 // Scaling factor (0.0-1.0)
maxPower float64 // Power limit (kW)
}
func (n *NivChaseComponent) CalculateTarget(state SystemState) float64 {
niv := n.modoClient.GetLatestNIV()
target := niv * n.targetFactor * 1000 // MW to kW
return clamp(target, -n.maxPower, n.maxPower)
}
Configuration:
components:
- type: niv_chase
priority: 1
schedule:
- start: "00:00"
end: "23:59"
config:
target_factor: 0.8
max_power: 100
Dynamic Peak Avoidance
Reduces site peak demand:
type DynamicPeakComponent struct {
threshold float64 // Trigger level (kW)
target float64 // Target reduction (kW)
historicalPeak float64 // Current period peak
}
func (d *DynamicPeakComponent) CalculateTarget(state SystemState) float64 {
siteDemand := state.MeterPower
if siteDemand > d.threshold {
// Discharge to reduce peak
return -(siteDemand - d.target)
}
return 0
}
Import/Export Avoidance
Prevents power flow in specified direction:
type ImportAvoidanceComponent struct {
buffer float64 // Safety margin (kW)
}
func (i *ImportAvoidanceComponent) CalculateTarget(state SystemState) float64 {
gridPower := state.MeterPower
if gridPower > i.buffer {
// Discharge to offset import
return -gridPower + i.buffer
}
return 0
}
Configuration Structure
YAML Configuration
# Site configuration example
site:
name: "WLCE"
location: "Water Lilies"
devices:
bess:
- id: "123e4567-e89b-12d3-a456-426614174000"
name: "PowerPack-01"
modbus:
address: 1
host: "192.168.1.100"
port: 502
meters:
- id: "987e6543-e21b-12d3-a456-426614174000"
name: "Grid-Meter"
modbus:
address: 2
host: "192.168.1.101"
port: 502
controller:
poll_interval: 60 # seconds
constraints:
min_soe: 10 # %
max_soe: 90 # %
max_power: 100 # kW
min_power: -100 # kW
components:
- type: "niv_chase"
priority: 1
enabled: true
schedule:
- days: ["monday", "tuesday", "wednesday", "thursday", "friday"]
start: "16:00"
end: "19:00"
config:
modo_api_key: "${MODO_API_KEY}"
target_factor: 0.8
- type: "dynamic_peak"
priority: 2
enabled: true
schedule:
- days: ["all"]
start: "00:00"
end: "23:59"
config:
threshold: 150
target: 120
data_platform:
supabase:
url: "${SUPABASE_URL}"
anon_key: "${SUPABASE_ANON_KEY}"
service_key: "${SUPABASE_SERVICE_KEY}"
upload_interval: 60 # seconds
batch_size: 100
Environment Variables
Sensitive configuration via environment:
# /home/pi/bess_controller/envvars
export SUPABASE_URL="https://your-project.supabase.co"
export SUPABASE_ANON_KEY="your-anon-key-here"
export SUPABASE_SERVICE_KEY="your-service-key-here"
export MODO_API_KEY="your-modo-api-key"
export AXLE_API_KEY="your-axle-api-key"
Control Flow
Main Loop Sequence
Priority Resolution
Components are evaluated in priority order:
- Check if component is scheduled active
- Evaluate component conditions
- Calculate target power
- Apply safety constraints
- Send command to BESS
Testing
Unit Tests
cd bess_controller/src
go test ./controller -v
Integration Tests
# Test with mock Modbus server
go test ./controller -tags=integration
Debug Mode
# debug.yaml
debug:
enabled: true
mock_devices: true
log_level: "debug"
dry_run: true
Performance Metrics
- Poll Cycle: 60 seconds typical
- Modbus Latency: <100ms per device
- Decision Time: <10ms
- Upload Latency: <500ms
- Memory Usage: ~50MB
- CPU Usage: <5% on RevPi
Error Handling
- Automatic reconnection for Modbus failures
- Telemetry buffering during network outages
- Graceful degradation to safe mode
- Comprehensive logging via systemd journal
Next Steps
- Control Modes - Detailed mode documentation
- Deployment Guide - Install on RevPi
- API Reference - Integration points