Wind farm monitoring and digital twin platform with:
- physics-based wind turbine simulation
- 104 SCADA tags aligned to Bachmann Z72 definitions
- fault injection and degradation scenarios
- wind and grid condition control
- Modbus TCP simulation
- FastAPI backend with WebSocket streaming
- React frontend for dashboard, detail, history, and settings
# Backend + simulator + Modbus TCP (default port 8100)
pip install -r requirements.txt
python run.py
# Frontend (default port 3100, another terminal)
cd frontend
npm install
npm run devOpen http://localhost:3100.
docker compose up --buildOpen http://localhost:3100. Backend runs on port 8100, Modbus TCP on 5020.
Ports are configured in .env (copy from .env.example):
- Backend:
BACKEND_PORT=8100 - Frontend:
VITE_PORT=3100 - Modbus:
MODBUS_PORT=5020
Use python run.py --auto-port to auto-find an available port if the default is busy.
In Settings, select Physics Simulation (Backend) to use backend realtime simulation data.
Implemented and usable today:
- turbine startup, synchronization, production, normal stop, and emergency stop
- separated rotor and generator speeds with first drivetrain torsion model, gearbox oil temperature/viscosity effects, and gear tooth contact-ratio mesh stiffness ripple + tooth wear index
- 10-point thermal model with residual heat behavior
- vibration, yaw, wind field, wake, and per-turbine individuality
- 7 fault scenarios with fault-to-physics coupling
- grid frequency and voltage events with per-turbine ride-through differences
- sensor noise, drift, stuck values, and quantization
- history page with event markers, event details, focus windows, and CSV export
- electrical response model (frequency-watt, reactive power, power factor, ride-through)
- spectral vibration model (1P/3P/gear/HF/broadband bands, crest factor, kurtosis, BPFO/BPFI bearing defect frequencies, gear mesh sideband analysis)
- fatigue/load model (tower/blade moments, rainflow cycle counting, DEL, Miner's damage, alarm thresholds, RUL estimation, tower SDOF dynamic response, tower shadow 3P blade load modulation, wind shear 1P azimuth-dependent blade loading, blade mass imbalance ω² force coupling)
- fault lifecycle tracking with start/end duration events
- event export API (JSON/CSV) with severity grouping
- Docker Compose deployment (backend + frontend with nginx reverse proxy)
Physics model tracking:
Physics / OPC Data Source
-> DataBroker
-> FastAPI REST + WebSocket
-> SQLite history storage
-> React dashboard / detail / history / settings
-> Modbus TCP simulator
Main modules:
simulator/physics/turbine_physics.pypower_curve.pythermal_model.pyvibration_model.pyyaw_model.pywind_field.pyfault_engine.pyelectrical_model.pyvibration_spectral.pyfatigue_model.pyscada_registry.py
simulator/grid_model.pyserver/frontend/
- turbine state mapping
1-9 - cut-in / startup / sync / production / stop / cut-out behavior
- distinct normal-stop and emergency-stop curves
- curtailment, service mode, and operator commands
bearing_weargearbox_overheatpitch_motor_faultconverter_cooling_faultyaw_misalignmentgenerator_overspeedtransformer_overheat
- profiles:
nominal,low_freq,high_freq,undervoltage,overvoltage,weak_grid,recovery - manual frequency and voltage override
- per-turbine derate, trip, and reconnect differences
GET /api/turbines/{id}/historyGET /api/export/history- backend history events stored in SQLite
- history events include operator, fault, grid, wind, and state transitions
gridandwindconfig events support start/end durations- frontend
Historypage supports:- turbine, time range, and sample-size controls
- custom tag selection
- event filters and event search
- event detail panel with payload
- focus windows around selected events
- CSV export for current range or focused window
GET /api/health
GET /api/turbinesGET /api/turbines/{id}GET /api/turbines/{id}/historyGET /api/turbines/{id}/trendGET /api/turbines/farm-statusGET /api/turbines/farm-trend
GET /api/configPOST /api/config/datasourcePOST /api/config/simulationGET /api/config/windPOST /api/config/windPOST /api/config/wind/clearGET /api/config/simulation/time-scalePOST /api/config/simulation/time-scalePOST /api/config/simulation/generate-bulkGET /api/config/gridPOST /api/config/gridPOST /api/config/grid/clearGET /api/config/storage/statsGET /api/config/sessionsPOST /api/config/storage/maintenanceGET /api/config/turbine-specPOST /api/config/turbine-specGET /api/config/turbine-spec/presets
POST /api/control/commandPOST /api/control/curtailGET /api/control/{id}/status
GET /api/faults/scenariosPOST /api/faults/injectGET /api/faults/activePOST /api/faults/clearGET /api/faults/test-plansPOST /api/faults/test-plans/{plan_id}/run
GET /api/maintenance/work-ordersPOST /api/maintenance/work-ordersGET /api/maintenance/work-orders/{id}PATCH /api/maintenance/work-orders/{id}GET /api/maintenance/techniciansPOST /api/maintenance/techniciansPATCH /api/maintenance/technicians/{id}/statusGET /api/maintenance/events/compare?turbine_ids=WT001,WT002
GET /api/i18n/tagsGET /api/i18n/tags/allGET /api/i18n/tags/registry
GET /api/modbus/statusPOST /api/modbus/startPOST /api/modbus/stopGET /api/modbus/registers
GET /api/export/snapshotGET /api/export/history?format=csvGET /api/export/events?format=json(also supportscsv, with severity grouping)ws://localhost:8100/ws/realtime
DashboardTurbine DetailHistorySettingsMaintenanceHub
Note:
- maintenance work order backend and technician management are implemented
- the frontend MaintenanceHub uses real API backend (SQLite-backed)
- realtime data: in-memory trend buffer
- historical data: SQLite
- historical events: SQLite
history_events - database file:
wind_farm_data.db
Historical storage currently grows continuously and does not yet have a cleanup policy.
- roadmap:
TODO.md - physics model status:
docs/physics_model_status.md - original Z72 tag reference:
docs/1040610-Z72_PLC_OPC_TAG_1040510.xlsx
- deployment hardening: JWT, RBAC, HTTPS not yet implemented (Docker Compose is available)
- spectral alarm threshold curves not yet implemented; BPFO/BPFI, gear mesh sideband analysis, and crest factor/kurtosis anomaly alarms completed
- coolant level / leak detection implemented (level tracking, pump cavitation, fault coupling) — see #75
- ambient humidity effect on air cooling implemented (moist-air density + dew-point condensation penalty) — see #89
- localized turbulence pockets implemented (Gaussian spatial TI boost pockets, per-turbine TI multiplier,
WMET_LocalTitag) — see #91 - wake model upgraded to Bastankhah-Porté-Agel Gaussian (TI-dependent expansion, Ct-coupled deficit, sum-of-squares superposition,
WMET_WakeDeftag) — see #93 - dynamic wake meandering implemented (Larsen-DWM lateral AR(1) oscillation of wake centerline, σ_θ=0.3·TI, τ≈25 s, new
WMET_WakeMndrSCADA tag) — see #95 - yaw-induced wake deflection implemented (Bastankhah 2016 θ_c initial skew, per-source δ_y(x)=tan(θ_c)·x coupled to yaw_error, new
WMET_WakeDeflSCADA tag; driven by yaw_misalignment fault and transient yaw lag) — see #97 - atmospheric stability / diurnal shear-TI coupling implemented (Monin-Obukhov-simplified continuous stability score s ∈ [−1, +1] from solar time × wind mechanical mixing × cloud damping; drives wind shear exponent α ∈ [0.04, 0.30] and turbulence intensity multiplier ∈ [0.5, 1.6]; new
WMET_ShearAlpha+WMET_AtmStabSCADA tags) — see #99 - air density coupling implemented (moist-air ρ from ideal gas law + Buck/Magnus vapor correction; updated every step from ambient temp + humidity and injected into
PowerCurveModel.air_densityso aerodynamic power P ∝ ρ·V³ and thrust F ∝ ρ·V² both respond; ±10% swing between cold-winter and hot-humid days; newWMET_AirDensitySCADA tag) — see #101 - wake-added turbulence intensity implemented (Crespo-Hernández 1996: TI_w(x, r=0) = 0.73·a^0.8325·TI_∞^0.0325·(x/D)^-0.32 with a = 0.5·(1−√(1−Ct)), near-field capped at x/D=5; shared Bastankhah Gaussian σ for radial decay; Frandsen sum-of-squares across upstream sources; combined with pocket TI in quadrature before AR(1) generator so downstream σ_v observably rises — T1 at ~7D sees 12% wake-added TI and +36% wind-speed std vs free-stream T0 in self-test; new
WMET_WakeTiSCADA tag) — see #103 - dynamic atmospheric pressure P(t) implemented (synoptic
_pressure_statecontinuous score in [−1, +1] mapped to ±1500 Pa around ISA 101325 Pa, covering typical mid-latitude frontal amplitude 1013±15 hPa; fed throughWindEnvironmentModel.get_air_density(ts, ..., pressure_pa=...)so ρ gains another ±1.5% time variability from weather fronts on top of #101's T/RH coupling; manual override locks P at ISA reference; newWMET_AmbPressureSCADA tag, hPa) — see #106 - atmospheric-stability × wake-expansion coupling implemented (Bastankhah
k* = k_neutral · clamp(1 + 0.30·s, 0.55, 1.45)following Abkar & Porté-Agel 2015 / Peña et al. 2016; stable ABL slows wake recovery, convective ABL speeds it up; at 6 D / V=10 m/s / TI=8 % the self-test shows +33.8 % wake deficit at s=−1 and −22.0 % at s=+1; no new SCADA tag — observable viaWMET_WakeDef × WMET_AtmStabcorrelation) — see #109 - atmospheric-stability × wind-veer coupling implemented (per-turbine
wind_veer_rate(#79) is multiplied byclamp(1 − s, 0.3, 2.5), following Holton §5.3 / Stull §8.5 / van der Laan et al. 2017; stable nocturnal ABL preserves the Ekman spiral → ~0.20 °/m and +37 % tower side-side moment vs neutral, convective afternoon mixes it out → ~0.03 °/m and −26 %; effective veer rate is shared between the aero power-loss block andfatigue_model.step()so structural loads and aero power stay consistent; no new SCADA tag — observable viaWMET_AtmStab × WLOD_TwrSsMomcorrelation) — see #111 - atmospheric-stability × wake-meander τ_m coupling implemented (the Larsen-DWM atmospheric integral timescale is now
τ_m = 25 · clamp(1 − 0.6·s, 0.4, 2.0)s following Counihan 1975 / Larsen DWM 2008 / Peña & Hahmann 2012; stable ABL → 40 s slow meander, neutral → 25 s baseline, convective → 10 s fast turnover; lateral amplitude σ_θ stays at 0.3·TI so the amplitude path is owned by #99 TI-mult while τ_m owns the timescale path; self-test on a 4000 s AR(1) series shows lag-25 s autocorrelation 0.45 (stable) vs 0.28 (neutral) vs 0.01 (convective) and zero-crossing rate 0.082 vs 0.140 — slower meander under stable conditions and faster turnover under convection; no new SCADA tag — observable viaWMET_WakeMndr × WMET_AtmStabautocorrelation) — see #113 - atmospheric-stability × turbulence integral length scale L_u coupling implemented (
TurbulenceGenerator.step()appliesL_u_eff = 340 · clamp(1 − 0.6·s, 0.4, 2.0)m on top of the IEC 61400-1 neutral baseline so the AR(1) wind-speed autocorrelation timescaleτ = L_u/Vis stability-modulated. Stable nocturnal ABL → L_u=544 m, τ ≈ 54 s @ 10 m/s; neutral → 340 m, 34 s; convective afternoon → 136 m, 14 s. Validated on a 4000 s series at TI=0.10: observed lag-30 s autocorrelation 0.574 / 0.401 / 0.097 vs analytical 0.576 / 0.414 / 0.110 (stable / neutral / convective); steady-state σ_v stays at TI·V ≈ 1.0 m/s in all cases — the amplitude path is owned by #99's TI multiplier, this PR adds only the orthogonal timescale path. The same s ∈ [−1, +1] feeds both the farm-wide_turbulence_genand every per-turbine_turb_gens[i].step(...)so the whole farm shares one ABL timescale. No new SCADA tag — observable viaWMET_AtmStab × WROT_RotSpdlow-frequency autocorrelation. References: Counihan 1975 / Kaimal & Finnigan 1994 / Peña & Hahmann 2012 / IEC 61400-1 ed.4 Annex C — see #115) - nacelle anemometer transfer function (NTF) implemented (IEC 61400-12-1 Annex D: real cup/sonic anemometer sits ~1.5R behind hub on top of nacelle, reads systematically below free-stream because of axial induction. NTF formula
V_raw = V_∞ · (1 − 0.55·a)witha = 0.5·(1 − √(1 − Ct))derived from the 1-D momentum theory and the existingaero_out.ct. Stopped/parked rotor: bluff-body speed-up1.04·V_∞instead of induction. Self-test (7/7 + monotonicity): Region 2 Ct≈0.82 → 0.84·V_∞; Region 2.5 → 0.89·V_∞; Region 3 Ct≈0.30 → 0.96·V_∞; cut-out → 1.04·V_∞. Backwards compatible —WMET_WSpeedNackeeps free-stream semantics soexamples/data_quality_analysis.pyand OPC adapter consumers still get the same number; newWMET_WSpeedRawSCADA tag exposes the as-measured anemometer reading for power-curve verification studies. 103 SCADA tags total — see #117) - nacelle wind vane transfer function (WVTF) implemented (IEC 61400-12-2 Annex E: real wind vane co-located with the nacelle anemometer reads systematic swirl bias from rotor wake, since the rotor converts linear inflow into linear+rotational outflow (angular-momentum conservation). Closed form
θ_s ≈ Ct/(2·λ) [rad](Burton, Sharpe, Jenkins & Bossanyi 2011 Wind Energy Handbook 2nd ed. §3.7) reusesaero_out.ctandaero_out.tsralready computed bypower_curve.get_power_cp, so there is zero extra cost and no new RNG mutation. Right-handed rotor (industry standard, clockwise from upwind) → +bias on the downstream vane. Operating-state branching: producing/starting withrotor_speed > 1 RPMandtsr > 1applies the swirl bias; otherwise (stopped/parked, no rotor swirl) bias = 0°. Final clamp±8°keeps the result inside physically observed bounds. Self-test (8/8 + Ct↑→bias↑ + λ↑→bias↓ monotonicity): Region 2 (Ct≈0.82, λ≈7) → +3.36°; Region 2.5 (Ct≈0.65, λ≈6) → +3.10°; Region 3 (Ct≈0.30, λ≈5) → +1.72°; starting (Ct≈0.55, λ≈6) → +2.63°; stopped/cut-out → 0°; extreme Ct=0.95/λ=2 clamps at +8°; 360° wrap-around verified (358° + 3.4° → 1.36°). Backwards compatible —WMET_WDirAbsintentionally keeps free-stream semantics so the wake-model upstream indexing, yaw controller (yaw_model.py), frontend wind rose, and OPC adapter consumers are all unchanged; newWMET_WDirRaw(REAL32, °, 0–360) exposes the as-measured vane reading for IEC 61400-12-2 nacelle power-curve studies, vane-miscalibration fault simulation, and theWMET_WDirRaw - yaw_angleobservation channel that mirrors how field engineers diagnose systematic yaw misalignment. Pairs with #117 NTF to form a complete IEC 61400-12-1/2 nacelle sensor transfer function chain. 104 SCADA tags total (was 103): +1 raw nacelle wind vane tag (WMET_WDirRaw) — see #119 - yaw-skew Glauert correction for NTF + WVTF implemented (#125, IEC 61400-12-1/2 / Glauert 1935 / Burton et al. 2011 §3.10 / Castillo-Negro 2008): closes the IEC 61400-12-1/2 nacelle sensor chain under non-zero yaw error. Under yaw misalignment γ the rotor's axial induction at the anemometer position reduces to
a · cos²(γ)(Glauert combined-momentum / Coleman skewed-wake), and the swirl-plane angle projects onto the nacelle plane asθ_s · cos(γ). Both reuseyaw_out["yaw_error"]already instep()(no extra cost, no new RNG). γ clamped ±45°; γ=0 reproduces #117/#119 baseline exactly. Self-test 14/14 PASS: Region 2 / γ=0° → 0.842, γ=15° → 0.852 (NTF correction shrinks 6.7% per cos² law), γ=30° → 0.881 (shrinks 25%), γ=45° → 0.921 (correction halved); WVTF γ=15° → +3.24°, γ=30° → +2.91° (swirl projection cos γ); γ symmetry, γ↑ → factor↑/bias↓ monotonicity, ±45° clamp all verified. Pairs with theyaw_misalignmentfault scenario so the as-measuredWMET_WSpeedRawandWMET_WDirRawnow respond physically to operational yaw error and to vane-miscalibration faults. Backwards compatible —WMET_WSpeedNacandWMET_WDirAbskeep free-stream semantics. No new SCADA tag — observable via(WMET_WSpeedRaw / WMET_WSpeedNac − 1) × WYAW_YwVn1AlgnAvg5sand(WMET_WDirRaw − WMET_WDirAbs) × WYAW_YwVn1AlgnAvg5scorrelations. Also cleaned up duplicateWMET_WDirRawregistration left by the #119 merge in bothturbine_physics.py::step()output dict andscada_registry.py_TAGSlist (104 physics tags / 110 total registry entries unchanged). - full protection relay coordination not yet implemented
- frontend RUL visualization pending (fatigue alarm thresholds, RUL estimation, and alarm event integration implemented — see #57)
- dependency security vulnerabilities pending upgrade (see #48)
- use the status and roadmap docs as the source of truth for current implementation state