Generator Fleet Simulator Community Edition is a free-to-download, MIT-licensed standby generator fleet simulator built for large test scenarios. It scales the original simulator model up to 2,000 generators in one instance and adds a paged fleet dashboard, sub-fleet management, one-line SCADA drilldown, per-unit Modbus inspection, and Socket.IO-driven live updates. Project source code is licensed under MIT. Third-party components retain their own licenses; see THIRD_PARTY_NOTICES.md. The launcher ZIP is a Docker startup bundle; this repository contains the source code.
This repository is intentionally separate from the original smaller simulator so the large-fleet product can evolve independently.
Release candidate 1.1.0-rc.5 contains the launcher and download fixes.
Download the preview.
The release page includes the signed Community Edition ZIP and verification files.
Extract the ZIP and follow the launcher instructions below. GitHub and Docker Hub
accounts are not required to download a published release. Native Windows Docker
Desktop and full clean-machine launcher acceptance remain pending.
The container uses Python 3.11 on Alpine Linux, pinned by image digest. It changes the environment inside Docker, not your computer's operating system. Run this simulator only locally or on an isolated LAN. See release readiness for current limits.
- Simulates up to 2,000 generators in one process
- Exposes all 2,000 generators through Modbus TCP, with 255 generators per port
- Supports generator states
STOPPED,CRANKING,RUNNING,COOLDOWN, andFAULT - Supports transfer, parallel, and island operating modes
- Provides unit, fleet, and sub-fleet command paths
- Includes a browser dashboard for operations, filtering, paging, one-line SCADA navigation, and protocol inspection
- Exposes live per-unit register values, parallel setpoint control, and three alarm words
- Tracks runtime metrics such as tick duration, queue depth, Modbus sync time, and connected clients
After startup, the dashboard is organized into six tabs:
Fleet Status: paged table, filters, bulk actions, scope-level operating mode controls, and pop-out unit detail modalSub-Fleets: create, rename, delete, inspect, and operate named groups with a full-fleet builder workflowOne-Line SCADA: fleet/sub-fleet/size/range/unit hierarchy drilldown with kW rollups, breaker status, fault/alarm visibility, breadcrumbs, and scoped controlsScenario Timelines: run built-in operating timelines and save reusable runbooksModbus Registers: inspect one unit's live register map with raw values, scaling, engineering values, binary alarm words, and decoded active bitsHow To Use: guided operator workflow and Modbus usage notes
- Numbered pagination with
First,Prev,Next,Last - Page-size selector and direct
Go to page - Range display like
Showing 1451-1500 of 1500 - Checkbox-based row selection
- Selection action bar for:
- creating a sub-fleet from the current selection
- assigning selected units to an existing sub-fleet
- removing selected units from sub-fleets
- switching selected units into
TransferorParallelmode
- Fleet-wide top-bar controls for
Transfer Mode AllandParallel Mode All - Row click opens a pop-out generator detail modal
- Designed for large fleets where operators should not have to navigate to a specific page to manage membership
- Search the full fleet by unit ID or generator name
- Filter candidate units by state, size, alarmed status, auto/manual mode, and membership state
- Assign all matching generators to the active sub-fleet in one action
- Paste direct unit IDs or ranges such as
200-240 - Search and filter current members, then remove all matching members in one action
- Group-level controls for
Transfer GroupandParallel Group
Sub-fleets are user-created. A newly started simulator session begins with no sub-fleets until the user creates them. The SCADA tab still groups unassigned units by generator size and unit ranges so large fleets remain navigable before operators create named groups.
Generator run hours and sub-fleet membership are saved in the simulator state volume. Starting a new configured session with a different fleet mix resets the runtime to the selected fleet.
For the free Community Edition package, start with the guided launcher instead of a raw Docker command:
./start.shOn Windows PowerShell:
.\start.ps1The launcher requires license display and security review before it starts the container. It checks Docker, walks through each required setup step in order, and asks whether the simulator should stay local-only or enter trusted LAN integration mode for isolated test networks. For the normal download path, the launcher also asks for a fleet mix, configures the simulator through the startup API, and waits until Modbus is ready. Individual generator units start in a ready/stopped state until the operator issues a start command, scenario, runbook, or Modbus command from the dashboard or SCADA tab.
See CUSTOMER_ONBOARDING.md for the full first-run flow and WEBSITE_STARTUP_INSTRUCTIONS.md for website startup copy. Use LAUNCH_CHECKLIST.md before publishing a Community Edition release.
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --require-hashes -r requirements-dev.lock
python3 -m pip install --no-build-isolation --no-deps -e .
python3 main.pyThen open http://127.0.0.1:5000.
No license acceptance environment variable is required. After the web process starts, the simulator runtime does not start immediately. First:
- Choose how many generators you want at each size in the startup overlay
- Click
Start Simulator - The app will start the Modbus server and transition into the live dashboard
generator-fleet-simLocal development can run python3 main.py, but packaged local or isolated-LAN deployments
should use the Gunicorn entry point in wsgi.py instead of Flask's development
server. The simulator keeps command queues, Socket.IO watches, Modbus context,
and generator state in one process, so production deployments must run exactly
one worker process with threads enabled:
GENSIM_WEB_HOST=0.0.0.0 \
GENSIM_WEB_PORT=5000 \
gunicorn --worker-class gthread --workers 1 --threads 100 --bind 0.0.0.0:5000 wsgi:appThe Docker image uses this Gunicorn configuration by default. Scale by running separate simulator instances with separate ports and persistence files, not by adding Gunicorn workers to one instance.
The Community Edition launcher starts Generator Fleet Simulator only.
- Docker Desktop (includes Docker Compose)
- macOS/Linux launcher: Bash, Python 3 (
python3), andcurl; Windows: PowerShell - Docker access for the current account (
docker infomust succeed) - Internet access to pull images from Docker Hub on first run
- A 64-bit
amd64orarm64host supported by Docker - Available local host ports
5001and5021–5028, or alternate port mappings - A current desktop browser for the dashboard
- The launcher ZIP,
.sha256checksum, and release receipt supplied together
Published release images must be public and anonymously pullable. A Docker Hub
account or docker login is not part of the normal setup journey. Use the exact version and digest in the release receipt.
./start.shThe script will:
- Check that Docker is installed and running
- Help the user install or start Docker when needed
- Display the MIT License
- Display the security notes and require acknowledgement
- Ask for local-only or LAN network exposure
- Ask for a default, custom, or browser-configured fleet mix
- Pull the pinned public Generator Fleet image from Docker Hub
- Start Generator Fleet Simulator
- Configure the selected fleet mix from the terminal
- Wait for simulator readiness, then print the access URLs
If Docker reports pull access denied, the public image or exact release tag
was not published correctly, or Docker Hub is unavailable. Retry after checking
internet and Docker status; if it persists, report the version and pull error
to Studio Seventeen. The use fallback is for a previously verified cached
copy of that exact pinned image.
Before first launch, verify the package checksum using the command in
INSTALL.md, then confirm that VERSION, the image digest in the release
receipt, and the version displayed on the download page agree.
| Service | Endpoint |
|---|---|
| Dashboard | http://localhost:5001 |
| Modbus TCP | localhost:5021–5028 |
# View logs
docker compose logs -f generator
# Stop simulator (data is preserved)
docker compose stop generator
# Start again after stopping
SIM_HOST_BIND=127.0.0.1 docker compose up -d generator
# Stop and delete all data (clean slate)
docker compose down -v
# Pull the pinned image for this package and restart
SIM_HOST_BIND=127.0.0.1 docker compose pull
SIM_HOST_BIND=127.0.0.1 docker compose up -d generatorFor automated environments only, you can bypass the interactive launcher after reviewing the license and security notes:
SIM_HOST_BIND=127.0.0.1 docker compose up -dEvery tagged release candidate is built only after the locked install, test suite, dependency audit, credential guard, source compilation, container vulnerability gate, and 2,000-generator HTTP/Modbus smoke pass. The publish workflow then:
- pushes
linux/amd64andlinux/arm64images with BuildKit SBOM and max-level provenance attestations; - signs the immutable image digest with GitHub Actions OIDC and verifies the signature before packaging;
- creates a deterministic launcher ZIP, SHA-256 checksum, release receipt, and downloadable SPDX JSON SBOM;
- signs and verifies a SHA-256 manifest covering the ZIP, checksum, receipt, and SBOM with a keyless Sigstore bundle;
- runtime-smokes and vulnerability-scans the exact pushed digest on
linux/amd64andlinux/arm64, then promotes it from an unadvertised candidate tag to the official tags; - attaches the assets to a GitHub prerelease and retains them as workflow artifacts.
The receipt ties the Community Edition bundle to its release version, Git commit, image
repository and digest, dependency-lock hash, and workflow run. See
RELEASE_PROCESS.md for maintainer verification and publication steps.
Verify the release-asset manifest before running the launcher:
cosign verify-blob \
--bundle generator-fleet-simulator-community-edition-v<VERSION>-SHA256SUMS.sigstore.json \
--certificate-identity-regexp '^https://github.com/studioxvii/generator-fleet-simulator/.github/workflows/docker-publish.yml@refs/tags/v' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
generator-fleet-simulator-community-edition-v<VERSION>-SHA256SUMS
sha256sum --check generator-fleet-simulator-community-edition-v<VERSION>-SHA256SUMSLocal Python development defaults:
- Dashboard:
127.0.0.1:5000 - Modbus TCP:
127.0.0.1:5020–5027(only the ports needed by the fleet listen)
Community Edition Docker packages publish host ports to 127.0.0.1 by default through
the launcher and Compose files. Choose trusted LAN integration mode only when a
trusted HMI, SCADA workstation, or Modbus client on another machine needs to
connect from an isolated engineering or test network.
The Modbus endpoint is unauthenticated control traffic. Keep it on a trusted
network or use local-only binding. Set GENSIM_MODBUS_HOST=0.0.0.0 only when
the simulator is protected by trusted-LAN firewall or VPN controls.
The dashboard, REST commands, Socket.IO commands, and Modbus commands do not implement application authentication. The supported security boundary is the default local-only host binding or an explicitly isolated trusted LAN. Do not publish the simulator or Modbus port to the public internet.
The simulator runs locally in Docker. It stores generator state, sub-fleets,
and saved runbooks in the local generator-data Docker volume. The application
does not include telemetry, analytics, user accounts, or a Studio
Seventeen-hosted backend. Docker itself contacts Docker Hub to download the
public image, and normal network/DNS logs may be retained by Docker or the
operator's environment under their policies.
Community Edition is supplied without an included support SLA. Use the
feedback/support link published beside the download to report reproducible
issues, and include the release receipt, version, platform, and relevant
container logs. Report security issues through the private process in
SECURITY.md.
| Variable | Default | Purpose |
|---|---|---|
GENSIM_NUM_GENERATORS |
15 |
Initial suggested total before startup configuration |
GENSIM_MAX_GENERATORS |
2000 |
Maximum generators allowed in one simulator instance |
GENSIM_WEB_HOST |
127.0.0.1 |
Web dashboard bind address |
GENSIM_WEB_PORT |
5000 |
Web dashboard port |
GENSIM_MODBUS_HOST |
127.0.0.1 |
Modbus TCP bind address |
GENSIM_MODBUS_PORT |
5020 |
First Modbus TCP port; up to eight consecutive ports |
GENSIM_PUBLIC_WEB_HOST |
request host | Hostname shown in dashboard connection hints |
GENSIM_PUBLIC_WEB_PORT |
request port | Web port shown in dashboard connection hints |
GENSIM_PUBLIC_MODBUS_HOST |
request host | Modbus host shown in dashboard connection hints |
GENSIM_PUBLIC_MODBUS_PORT |
GENSIM_MODBUS_PORT |
Modbus port shown in dashboard connection hints |
GENSIM_STATE_FILE |
generator_state.json |
Persisted runtime state file |
GENSIM_RUNBOOKS_FILE |
generator_runbooks.json |
Persisted saved runbooks file |
GENSIM_SAVE_INTERVAL |
60 |
Seconds between periodic state saves |
GENSIM_TICK_SECONDS |
1.0 |
Simulation tick size |
GENSIM_HTTP_RATE_LIMIT |
120 |
Max mutating HTTP/Socket.IO commands per client per window; 0 disables |
GENSIM_HTTP_RATE_WINDOW |
60 |
Rate-limit window in seconds |
Example:
export GENSIM_MAX_GENERATORS=2000
export GENSIM_WEB_PORT=5001
export GENSIM_MODBUS_PORT=5021
python3 main.pyThe Community Edition launcher can start the selected fleet mix directly from the terminal. If you choose to configure later, the startup overlay validates your selected fleet mix before it attempts to launch the simulator runtime.
If startup fails, the overlay now keeps the page active, restores the button, and shows the server-side error message directly.
The most common startup failure is a stale Python process already holding the Modbus port.
Typical symptom:
- the dashboard loads
- clicking
Start Simulatordoes not transition into the live fleet - the startup overlay shows a port-unavailable error for
127.0.0.1:5020
Fix:
- Stop the old simulator process that is still bound to the Modbus port
- Refresh the dashboard
- Click
Start Simulatoragain
If needed, use:
lsof -nP -iTCP:5020 -sTCP:LISTENThe simulator keeps the large-fleet model in one process while separating responsibilities internally:
main.py: app factory, runtime orchestration, APIs, command routing, state store, metrics, and event publishinggenerator.py: generator state machine, alarms, transfer behavior, and analog simulationmodbus_server.py: Modbus datastore and TCP server wrapper
Runtime flow:
flowchart LR
A["Generator Models"] --> B["Simulation Engine"]
C["Command Router"] --> B
B --> D["State Store"]
D --> E["Paged Fleet APIs"]
D --> F["Unit Detail Updates"]
D --> G["Sub-Fleet Summaries"]
D --> H["Modbus Register Cache"]
H --> I["Modbus TCP Server"]
E --> J["Dashboard"]
F --> J
G --> J
- Dashboard:
GET / - Fleet summary:
GET /api/fleet/summary - Paged fleet list:
GET /api/fleet/generators - Generator detail:
GET /api/generators/<unit_id> - Generator registers:
GET /api/generators/<unit_id>/registers - Sub-fleet management:
GET/POST/PATCH/DELETE /api/subfleets... - Metrics:
GET /api/metrics - Liveness:
GET /api/live - Readiness:
GET /api/ready - SCADA topology:
GET /api/scada/topology - SCADA alarms:
GET /api/scada/alarms - Startup endpoint:
POST /api/startup - Config export:
GET /api/export/config.csv
- Host:
127.0.0.1by default - Base port:
5020by default; up to eight consecutive ports (5020–5027) - Wire unit IDs restart at
1on each port after255 - Generator 256 uses port
5021, unit1; generator 2000 uses port5027, unit215 - Compose publishes ports
5021–5028; the Modbus tab and config export show each address - For Compose: generator 255 = port
5021/ unit255; generator 256 = port5022/ unit1; generator 2000 = port5028/ unit215 - The host IP stays the same; dashboard/API generator IDs remain
1–2000 - Some clients and gateways restrict unit IDs to 1–247; check the target client
For register definitions, alarm bitfields, command values, and client examples, see MODBUS_REFERENCE.md.
Install dependencies and run tests:
python3 -m pip install --require-hashes -r requirements-dev.lock
python3 -m pip install --no-build-isolation --no-deps -e .
python3 -m pytestWhen an abstract dependency range changes, regenerate both reviewed locks with
pip-tools, inspect the resolved diff, and rerun the full suite:
python3 -m pip install pip-tools
pip-compile --generate-hashes --strip-extras --no-emit-index-url --no-emit-trusted-host -o requirements.lock requirements.txt
pip-compile --allow-unsafe --generate-hashes --strip-extras --no-emit-index-url --no-emit-trusted-host -o requirements-dev.lock requirements-dev.txt
pip-compile --allow-unsafe --generate-hashes --strip-extras --no-emit-index-url --no-emit-trusted-host -o requirements-audit.lock requirements-audit.txtIf bytecode cache writes are a problem in a restricted environment:
PYTHONPYCACHEPREFIX=/tmp/pycache python3 -m py_compile main.py generator.py modbus_server.py runbooks.py scenarios.pyIf you change user-visible behavior, update the docs in the same branch:
README.mdfor workflow, startup, UI, and operational behaviorCUSTOMER_ONBOARDING.mdfor the guided launcher flowWEBSITE_STARTUP_INSTRUCTIONS.mdfor website startup copyOPERATIONS.mdfor deployment, readiness, logs, and upgrade proceduresMODBUS_REFERENCE.mdfor register map or protocol changesCONTRIBUTING.mdfor contributor workflow or expectations
This repository is the home of the scaled 2,000-generator simulator. The original generator-simulator repo remains the smaller fleet variant with its own UI and operating assumptions.
See release readiness and the
preview acceptance record.
Public source and signed 1.1.0-rc.5 preview downloads are available.
Host validation accepts literal IP addresses and configured web host names.
Set GENSIM_TRUSTED_HOSTS to a comma-separated list of other exact DNS names
needed for an isolated LAN or same-origin proxy. This does not add authentication.