NexIoT Simulator: Multi-protocol IoT device simulator
240
English | 中文
A single-process device simulator covering 4 industrial protocols, for integration development and testing against host systems such as ThingsBoard Gateway.
python -m app starts everything at once: four protocol servers (Modbus TCP, OPC UA, BACnet, Siemens S7) plus a FastAPI management interface, with a Vue 3 frontend point configuration UI. All point data can be configured and modified via the REST API and takes effect on the protocol wire side within at most 1 second.
Want technical details and internals? Read the docs under
doc/.
doc/03-data-flow.md)../data/points.json) and restored automatically after a container restart/recreate — no need to reconfigure.Web management UI — point list, filter by protocol, stats and actions:

Modbus TCP client reads (coils / discrete inputs / holding registers):

OPC UA client reads (MyDevice / MyDevice1 nodes):

S7 client reads (I / Q / M areas) and DB1 data block read:

| Protocol | Port | Transport | Description |
|---|---|---|---|
| Modbus TCP | 502 | TCP | 2 slave devices (unit 1 / 2), with coils, discrete inputs, input/holding registers |
| OPC UA | 53530 | TCP | opc.tcp://0.0.0.0:53530/OPCUA/SimulationServer, 2 devices |
| BACnet | 47808 | UDP | device instance 1234 (SimBACnetDevice), 15 objects |
| S7 | 102 | TCP | Siemens S7 simulator, I / Q / M / DB1 memory areas |
| HTTP | 8000 | TCP | FastAPI management API + frontend (override with the API_PORT environment variable) |
Most ports are privileged 0–1024 ports (502 / 102); running locally on Linux requires root. It is recommended to use the Docker method.
Run the prebuilt Docker Hub image directly — no local build required:
docker run -d --name nexiot-simulator --restart unless-stopped \
-p 502:502 \
-p 53530:53530 \
-p 47808:47808/udp \
-p 102:102 \
-p 8000:8000 \
-v nexiot-data:/app/data \
jettzhan/nexiot-simulator
--restart unless-stopped: the container is restarted automatically if it exits and starts on boot.-v nexiot-data:/app/data: named-volume persistence. Point configs and current values are stored in points.json and restored automatically on container restart/recreate; on first boot with no file, 49 seed points are written.-e VAR=value (e.g. -e "MB_FLOAT_0=42.5"); see Environment Variable Overrides.After startup:
| Service | Address |
|---|---|
| Web management UI | http://localhost:8000 |
| Swagger API docs | http://localhost:8000/docs |
| Modbus TCP | localhost:502 |
| OPC UA | opc.tcp://localhost:53530/OPCUA/SimulationServer |
| BACnet | localhost:47808 (UDP) |
| S7 | localhost:102 |
For development/debugging: build the image locally and orchestrate with Compose. docker-compose.yml already configures the port mappings and the named volume nexiot-data.
docker compose up -d --build
Requires Python 3.11+ and Node.js 18+.
# 1. Install backend dependencies
pip install -r requirements.txt
# 2. Start the simulator (default API port 8000)
python -m app
# or with a custom API port: API_PORT=8001 python -m app
The frontend build is optional (otherwise there is no web UI; the API is unaffected):
cd frontend
npm install
npx vite build # build output goes to ../app/static, served by FastAPI after restart
# dev mode (hot reload, proxies /api → localhost:8000):
npm run dev
doc/02-data-model.md.Base URL: http://<host>:8000, interactive docs: http://<host>:8000/docs.
| Method | Path | Description |
|---|---|---|
GET | /api/points | point list + stats |
POST | /api/points | create a point |
GET | /api/points/{id} | point detail + current value |
PUT | /api/points/{id} | update a point |
DELETE | /api/points/{id} | delete a point |
DELETE | /api/points | clear all points |
GET | /api/points/{id}/value | read the current value |
PUT | /api/points/{id}/value | write the current value (RPC) |
GET | /api/points/export | export all point configs |
POST | /api/points/import | full import of point configs |
GET | /api/stats | point stats |
Quick start:
# List points
curl http://localhost:8000/api/points
# Write an OPC UA point value (syncs to the UA node within 1s)
curl -X PUT http://localhost:8000/api/points/16/value \
-H "Content-Type: application/json" -d '{"value":200.5}'
# Create a Modbus point
curl -X POST http://localhost:8000/api/points \
-H "Content-Type: application/json" \
-d '{"name":"MB_Test","protocol":"modbus","modbus":{"unitId":1,"functionCode":"03","address":30,"dataType":"uint16","byteOrder":"AB"},"initialValue":777,"changeMode":"fixed"}'
For the full API reference with request/response examples, see doc/05-api.md.
System design, data model, effect mechanism, and protocol implementation details are documented in the doc/ directory:
| Doc | Content |
|---|---|
| doc/README.md | technical doc index & 30-second architecture overview |
| 01-architecture.md | system architecture, tech stack, process/thread model, port allocation |
| 02-data-model.md | point data model, Registry design, 49 seed points |
| 03-data-flow.md | Core: data flow & the "configuration takes effect" mechanism |
| 04-protocol-implementation.md | implementation details of the 4 protocol servers |
| 05-api.md | REST API reference (with request/response examples) |
| 06-run-deploy.md | running, frontend build, Docker deployment, environment variable overrides |
| 07-verification.md | verification & testing methods (three-layer verification system) |
Chinese versions of these documents are available under doc/.
.
├── app/ # backend (single process)
│ ├── __main__.py # entry point: load persisted config / seed + 4 server threads + uvicorn
│ ├── main.py # FastAPI instance, mounts /api and static assets
│ ├── api.py # REST routes (point CRUD / value read-write / import-export / stats)
│ ├── registry.py # PointRegistry: in-memory point configs + value store (thread-safe)
│ ├── persistence.py # JSON persistence of point configs + current values
│ ├── seed.py # 49 seed points
│ ├── static/ # frontend build output (served by FastAPI at /)
│ └── servers/ # 4 protocol servers
│ ├── modbus_server.py
│ ├── opcua_server.py
│ ├── bacnet_server.py
│ └── s7_server.py
├── frontend/ # Vue 3 + Pinia + Vite + TypeScript frontend
│ └── src/
│ ├── api/ # REST wrapper (fetch)
│ ├── i18n.ts # Chinese/English translations & language switcher
│ ├── stores/ # Pinia store (point list/filter/detail/actions)
│ ├── types/ # TypeScript types (mirror backend point structure)
│ └── components/ # StatsBar / PointList / PointDetail / PointModal
├── doc/ # technical documentation (Chinese + English under doc/en/)
├── img/ # README screenshots
├── tests/ # unit / integration tests (pytest)
├── verify_point_config.py # point-config effect verification script (run after starting the service)
├── Dockerfile
└── docker-compose.yml
The project uses a three-layer verification system (see doc/07-verification.md):
# 1. Unit tests (37 checks, no service required)
python -m pytest tests -v -p no:cacheprovider
# 2. Wire-level integration tests (start the simulator first, 162 checks)
python tests/test_all_protocols.py
# 3. Point-config effect verification (start the simulator first, 10 checks)
python verify_point_config.py
The four protocols have all been verified for "API config syncs to the wire within ≤1s" (creating points, changing values, RPC writes, etc.); see doc/07-verification.md.
Override each protocol's initial values in docker-compose.yml or the shell without modifying code:
| Prefix | Description | Example |
|---|---|---|
MB_* | Modbus initial values | MB_FLOAT_0=42.5, MB_COIL_BITS=1023 |
UA_* | OPC UA variable values | UA_MyDevice_Temperature=30.0 |
BAC_* | BACnet object presentValue | BAC_AV_2=70.0, BAC_BV_1=inactive |
S7_* | S7 memory area bytes | S7_DB1_DBD0=99999, S7_M_0=255 |
API_PORT | FastAPI port (default 8000) | API_PORT=8001 |
# docker-compose.yml example
environment:
- MB_FLOAT_0=42.5 # Modbus 1st float → 42.5
- UA_MyDevice_Count=999 # OPC UA MyDevice.Count → 999
- BAC_AV_2=70.0 # BACnet HumiditySetpoint → 70%
- S7_DB1_DBD0=88888 # S7 DB1.DBD0 → 88888
The full variable list is in doc/en/06-run-deploy.md.
Q: Modbus / S7 port startup fails (Permission denied)? Ports 502 / 102 are privileged ports; on Linux use root or Docker (the container runs as root).
Q: Docker build fails to pull the base image?
If your network is restricted and python:3.11-slim is unreachable, replace the base image in the Dockerfile with an accessible registry and rebuild.
Q: The wire side didn't change after modifying a point value?
doc/03-data-flow.md).Q: The data read during verification/integration is stale? A leftover old simulator process may still occupy the port; clean it up and restart.
Issues and Pull Requests are welcome.
python -m pytest tests after changes to ensure tests pass.doc/en/ (and the Chinese versions under doc/).This project is open-sourced under the Apache License 2.0.
Content type
Image
Digest
sha256:10b9dd435…
Size
62.6 MB
Last updated
28 days ago
docker pull jettzhan/nexiot-simulator