BESS Subsystem Simulation Model (AI / Code Generation Version)
This document specifies the equivalent mathematical models for the BESS subsystem using formal notation for automated code generation. Targets: Python (NumPy), C/C++ (embedded RPi), MATLAB/Simulink.
Sign convention: $I_b > 0$ = Charging, $I_b < 0$ = Discharging. $P_{req} > 0$ = Charge request, $P_{req} < 0$ = Discharge request.
Simulation Loop (Top-Level Pseudocode)
The following shows the recommended execution order at each discrete time step $k$ with step size $\Delta t$:
# ─── Initialization (run once before loop) ──────────────────────────
SoC = SoC_initial # e.g., 0.5
Vc1 = 0.0
Vc2 = 0.0
Tb = 25.0 # degrees Celsius
Pout = 0.0
Qout = 0.0
SoH = 1.0
SoR = 1.0
Vdc_pcs_prev = V_BESS_nom # used for algebraic loop decoupling
# ─── Per-step loop ───────────────────────────────────────────────────
for k in range(N_steps):
# 1. Interpolate ECM parameters from CSV at current SoC
OCV, R0, R1, C1, R2, C2 = interpolate_csv(SoC)
R0_eff = R0 * SoR # apply aging resistance scale
# 2. BMS: compute power limits
I_lim_chg = min(I_max_charge, (V_cell_max - OCV) / R0_eff)
I_lim_dis = min(I_max_discharge, (OCV - V_cell_min) / R0_eff)
P_lim_chg = I_lim_chg * Vb
P_lim_dis = I_lim_dis * Vb
# 3. PCS: limit and track setpoint
Preq_lim = clamp(Preq, -P_lim_dis, +P_lim_chg)
Preq_lim = clamp(Preq_lim, -S_max, +S_max)
Pout = Pout + dt * (Preq_lim - Pout) / tau_p
Qout = Qout + dt * (Qreq_lim - Qout) / tau_q
# 4. PCS: DC/AC power balance (use Vdc_pcs from previous step)
if Pout >= 0: # charging (rectifier)
Pdc_pcs = Pout * eta_pcs
else: # discharging (inverter)
Pdc_pcs = Pout / eta_pcs
Ib = Pdc_pcs / Vdc_pcs_prev
# 5. Battery Array: update states
SoC = clamp(SoC + (Ib * dt / Q_cap), 0.0, 1.0)
Vc1 = Vc1 + dt * (-(Vc1 / (R1 * C1)) + (Ib / C1))
Vc2 = Vc2 + dt * (-(Vc2 / (R2 * C2)) + (Ib / C2))
Vb = OCV + Vc1 + Vc2 + (Ib * R0_eff)
# 6. DC Line: compute PCS DC terminal voltage
Vdc_pcs = Vb + Ib * R_dc_line
Vdc_pcs_prev = Vdc_pcs # store for next step decoupling
# 7. Thermal model
P_heat = Ib**2 * R0_eff + Vc1**2 / R1 + Vc2**2 / R2
Tb = Tb + dt * (1 / (m_cp)) * (P_heat - (Tb - T_amb) / R_th)
# 8. BMS: alarms
check_alarms(SoC, Vb, Tb, N_series, V_cell_min, V_cell_max, T_max, ...)
# 9. SoH / SoR: update per cycle (outside per-step, at cycle boundary)
# SoH -= SoH_degrad_per_cycle
# SoR = 1.0 + (1.0 - SoH) * resistance_aging_factor
1. Battery Array Model
1.1 Second-Order LMFP Equivalent Circuit Model
Reference Data: Parameters $R_0, R_1, C_1, R_2, C_2$, and $OCV(SoC)$ are sourced from HPPC experimental data. - Dataset: lmfp_2nd_order_ecm_parameters.csv - Note: $OCV$ for LMFP features a two-plateau structure due to the Manganese addition.
Interpolation (linear, at each step):
f(SoC) = f_{low} + \frac{SoC - SoC_{low}}{SoC_{high} - SoC_{low}} \cdot (f_{high} - f_{low})
Variables & States: - $SoC$: State of Charge (State, $0.0 \dots 1.0$). Initial: 0.5 - $V_{c1}$: Voltage across first RC pair (State, Volts). Initial: 0.0 - $V_{c2}$: Voltage across second RC pair (State, Volts). Initial: 0.0 - $I_{b}$: Battery terminal current (Input, Amps). Positive for charge. - $V_{b}$: Battery terminal voltage (Output, Volts)
Parameters: - $Q_{cap}$: Total array capacity (Amp-seconds). Default: 360,000 As (100 Ah) - $OCV(SoC)$: Open Circuit Voltage. Lookup + interpolate from CSV. - $R_0, R_1, R_2$: Resistances ($\Omega$). Lookup + interpolate from CSV. - $C_1, C_2$: Capacitances (Farads). Lookup + interpolate from CSV. - $I_{max_charge}$: Max continuous charge current (Amps). Default: 100 A - $I_{max_discharge}$: Max continuous discharge current (Amps). Default: 100 A
Discrete State Equations (implement at each step $k$):
SoC[k] = \mathrm{clamp}\!\left(SoC[k-1] + \frac{I_{b}[k] \cdot \Delta t}{Q_{cap}},\ 0,\ 1\right)
V_{c1}[k] = V_{c1}[k-1] + \Delta t \left( -\frac{V_{c1}[k-1]}{R_1 C_1} + \frac{I_{b}[k]}{C_1} \right)
V_{c2}[k] = V_{c2}[k-1] + \Delta t \left( -\frac{V_{c2}[k-1]}{R_2 C_2} + \frac{I_{b}[k]}{C_2} \right)
Output Equation:
V_{b} = OCV(SoC) + V_{c1} + V_{c2} + I_{b} \cdot R_{0,eff}
1.2 Simplified Thermal Model
Variables & States: - $T_{b}$: Battery temperature (State, $^\circ C$). Initial: 25.0 C - $T_{amb}$: Ambient temperature (Input). Default: 25.0 C
Parameters: - $m \cdot c_p$: Total heat capacity (J/$^\circ C$). Default: 10,000,000 - $R_{th}$: Thermal resistance to ambient ($^\circ C$/W). Default: 0.1
Heat Generation:
P_{heat} = I_{b}^2 R_{0,eff} + \frac{V_{c1}^2}{R_1} + \frac{V_{c2}^2}{R_2}
Discrete Temperature Update:
T_{b}[k] = T_{b}[k-1] + \Delta t \cdot \frac{1}{m \cdot c_p} \left[ P_{heat} - \frac{T_{b}[k-1] - T_{amb}}{R_{th}} \right]
2. BESS DC Line
Purely resistive cable between Battery Array and PCS.
- $R_{dc_line}$: Lumped cable resistance. Default: 0.005 $\Omega$
V_{dc\_pcs} = V_{b} + I_{b} \cdot R_{dc\_line}
(During charging $I_b > 0$, so $V_{dc_pcs} > V_b$. During discharging $I_b < 0$, so $V_{dc_pcs} < V_b$.)
3. BESS AC Line
Phasor average model of the cable between PCS and PCC grid bus.
Parameters: - $V_{ac_bus}$: Grid bus RMS voltage. Default: 400 V - $R_{ac_line}, L_{ac_line}$: Line R and L. Defaults: 0.01 $\Omega$, 0.0001 H - $X_{ac_line} = \omega L_{ac_line}$ where $\omega = 2\pi \cdot 50\ \text{Hz}$. Default: $\approx 0.0314\ \Omega$ - $\delta$: PCS–bus phase angle (radians). Controlled variable.
P_{ac\_line} = \frac{V_{ac\_pcs} \cdot V_{ac\_bus}}{X_{ac\_line}} \sin(\delta)
Q_{ac\_line} = \frac{V_{ac\_pcs}^2 - V_{ac\_pcs} \cdot V_{ac\_bus} \cos(\delta)}{X_{ac\_line}}
4. BESS PCS (Power Conversion System)
First-order lag model of the inverter/rectifier control loop.
Variables & States: - $P_{req}, Q_{req}$: EMS power setpoints (W, VAr). Positive = Charge. - $P_{out}, Q_{out}$: Actual PCS output power states. Initial: 0.0
Parameters: - $\tau_p, \tau_q$: Response time constants (s). Default: 0.05 s - $\eta_{pcs}$: Conversion efficiency. Default: 0.98 - $S_{max}$: Apparent power limit (VA). Default: 1,000,000 VA
Step 1 — Clamp setpoint to BMS limits:
P_{req\_limited} = \mathrm{clamp}(P_{req},\ -P_{limit\_discharge},\ +P_{limit\_charge})
P_{req\_limited} = \mathrm{clamp}(P_{req\_limited},\ -S_{max},\ +S_{max})
Step 2 — Discrete power tracking:
P_{out}[k] = P_{out}[k-1] + \Delta t \cdot \frac{P_{req\_limited} - P_{out}[k-1]}{\tau_p}
Q_{out}[k] = Q_{out}[k-1] + \Delta t \cdot \frac{Q_{req\_limited} - Q_{out}[k-1]}{\tau_q}
Step 3 — DC/AC power balance:
P_{dc\_pcs} = \begin{cases}
P_{out} \cdot \eta_{pcs} & \text{if } P_{out} \geq 0\ \text{(charge / rectifier)} \\
P_{out}\ /\ \eta_{pcs} & \text{if } P_{out} < 0\ \text{(discharge / inverter)}
\end{cases}
I_{b} = \frac{P_{dc\_pcs}}{V_{dc\_pcs}[k-1]}
5. BMS (Battery Management System) Signals
5.1 Power Limits
Current limits (structural + electrochemical voltage margin):
I_{limit\_charge} = \min\!\left(I_{max\_charge},\ \frac{V_{cell\_max} - OCV(SoC)}{R_{0,eff}}\right)
I_{limit\_discharge} = \min\!\left(I_{max\_discharge},\ \frac{OCV(SoC) - V_{cell\_min}}{R_{0,eff}}\right)
Power limits:
P_{limit\_charge} = I_{limit\_charge} \cdot V_b
P_{limit\_discharge} = I_{limit\_discharge} \cdot V_b
5.2 State of Health and State of Resistance
Updated once per full charge/discharge cycle (not per time step):
SoH_{n+1} = SoH_{n} - \delta_{SoH}
SoR = 1.0 + (1.0 - SoH) \cdot k_{aging}
Parameters: - $\delta_{SoH}$: Capacity fade per cycle. Typical LMFP: 0.0002 (0.02%/cycle) - $k_{aging}$: Resistance aging factor. Default: 0.5
Application: At each step, compute effective resistance as $R_{0,eff} = R_0 \cdot SoR$.
5.3 Calculated Signals
SoC: From Battery Array state.SoH: Updated per cycle.SoR: Updated per cycle, drives $R_{0,eff}$.P_limit_charge,P_limit_discharge: Updated each step, sent to PCS.
5.4 Alarms
IF SoC < SoC_min_threshold THEN Alarm_Min_SoC = TRUE // Default: 0.1
IF SoC > SoC_max_threshold THEN Alarm_Max_SoC = TRUE // Default: 0.9
IF (V_b / N_s) < V_cell_min THEN Alarm_Min_CellVolt = TRUE // Default: 2.8 V
IF (V_b / N_s) > V_cell_max THEN Alarm_Max_CellVolt = TRUE // Default: 4.0 V
IF T_b > T_max THEN Alarm_Max_Temp = TRUE // Default: 60 C
6. Model Calibratable Parameters
6.1 Battery Scaling Matrix
Specify any two of the three; the third is auto-computed:
- $V_{BESS_nom}$: Nominal DC bus voltage. Default: 1000 V
- $V_{cell_nom}$: Nominal cell voltage (LMFP $\approx 3.8$ V). Default: 3.8 V
- $N_s$: Cells in series. Default: 263
Scaling rules from per-cell CSV to array:
| Quantity | Array Value |
|---|---|
| OCV, Vb | $\times N_s$ |
| $R_0, R_1, R_2$ | $\times N_s$ |
| $C_1, C_2$ | $\div N_s$ |
| $Q_{cap}$ | Ah $\times$ 3600 $\times$ parallel strings |
6.2 Initial States
| Variable | Default |
|---|---|
| $SoC_0$ | 0.5 |
| $SoH_0$ | 1.0 |
| $SoR_0$ | 1.0 |
| $T_{b,0}$ | 25.0 °C |
| $V_{c1,0}, V_{c2,0}$ | 0.0 V |
| $P_{out,0}, Q_{out,0}$ | 0.0 W |
6.3 Runtime Fault Flags
Fault_PCS_Trip: Set $P_{out} = Q_{out} = 0$ (trip contactor).Fault_BMS_SensorLoss: Override SoC or $T_b$ with stale/incorrect value.Fault_ShortCircuit: Set $V_{ac_bus} = 0$ (FRT / grid collapse test).
7. PCS Calibratable Parameters
| Parameter | Symbol | Default |
|---|---|---|
| Max apparent power | $S_{max}$ | 1,000,000 VA |
| Efficiency | $\eta_{pcs}$ | 0.98 |
| Active power time constant | $\tau_p$ | 0.05 s |
| Reactive power time constant | $\tau_q$ | 0.05 s |
| Min DC voltage trip | $V_{dc_min}$ | 850 V |
| Max DC voltage trip | $V_{dc_max}$ | 1200 V |