Sign inSign up

jettzhan/nexiot-simulator

By jettzhan

Updated 28 days ago

NexIoT Simulator: Multi-protocol IoT device simulator

Image
0

240

jettzhan/nexiot-simulator repository overview

NexIoT Simulator(物联网协议模拟器)

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.

Python FastAPI Vue License

Want technical details and internals? Read the docs under doc/.


Table of Contents


Features

  • Four industrial protocols: simultaneously simulates Modbus TCP (2 slaves), OPC UA (2 devices), BACnet (15 objects), and Siemens S7 (4 memory areas) devices.
  • Config immediately effective: after creating, modifying, or deleting a point via the REST API / frontend, the sync loop writes the value to the protocol wire side within ≤1 second (see doc/03-data-flow.md).
  • RPC writes: all protocols support native writes (FC06/FC16, UA Write, BACnet WriteProperty, S7 write_area) plus a unified REST API write.
  • Web management UI: Vue 3 + Pinia frontend with point list (filter by protocol), detail, create/edit/delete, and JSON import/export.
  • Full REST API: point CRUD, value read/write, import/export, stats, plus interactive Swagger docs.
  • Flexible configuration: environment variables override each protocol's initial values without rebuilding the image.
  • Configuration persistence: point configs and current values are auto-saved to disk (default ./data/points.json) and restored automatically after a container restart/recreate — no need to reconfigure.
  • Out of the box: 49 built-in seed points covering multiple protocols and data types (bit/uint16/int32/float32/double/string/boolean, etc.).

UI Preview

Web management UI — point list, filter by protocol, stats and actions:

Web management UI

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

Modbus client

OPC UA client reads (MyDevice / MyDevice1 nodes):

OPC UA client

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

S7 DB1 read

Supported Protocols & Ports

ProtocolPortTransportDescription
Modbus TCP502TCP2 slave devices (unit 1 / 2), with coils, discrete inputs, input/holding registers
OPC UA53530TCPopc.tcp://0.0.0.0:53530/OPCUA/SimulationServer, 2 devices
BACnet47808UDPdevice instance 1234 (SimBACnetDevice), 15 objects
S7102TCPSiemens S7 simulator, I / Q / M / DB1 memory areas
HTTP8000TCPFastAPI 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.

Quick Start

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.
  • To override initial values, append -e VAR=value (e.g. -e "MB_FLOAT_0=42.5"); see Environment Variable Overrides.

After startup:

ServiceAddress
Web management UIhttp://localhost:8000
Swagger API docshttp://localhost:8000/docs
Modbus TCPlocalhost:502
OPC UAopc.tcp://localhost:53530/OPCUA/SimulationServer
BACnetlocalhost:47808 (UDP)
S7localhost:102
Docker Compose (Development)

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
Local Run (Python + Frontend)

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

Usage

  1. Open http://localhost:8000 to view the point list and stats, and filter points by protocol.
  2. Create / edit / delete points in the UI, or import/export JSON configuration (all changes are saved automatically).
  3. After changing a point value, OPC UA / BACnet / S7 / Modbus sync to the protocol wire side within ≤1 second and can be read directly by clients such as ThingsBoard Gateway.
  4. On the next startup / container restart, previously configured points and current values are restored automatically.
  5. The detailed point table (address and initial value of each register/node) is in doc/02-data-model.md.

API Overview

Base URL: http://<host>:8000, interactive docs: http://<host>:8000/docs.

MethodPathDescription
GET/api/pointspoint list + stats
POST/api/pointscreate 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/pointsclear all points
GET/api/points/{id}/valueread the current value
PUT/api/points/{id}/valuewrite the current value (RPC)
GET/api/points/exportexport all point configs
POST/api/points/importfull import of point configs
GET/api/statspoint 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.

Technical Documentation

System design, data model, effect mechanism, and protocol implementation details are documented in the doc/ directory:

DocContent
doc/README.mdtechnical doc index & 30-second architecture overview
01-architecture.mdsystem architecture, tech stack, process/thread model, port allocation
02-data-model.mdpoint data model, Registry design, 49 seed points
03-data-flow.mdCore: data flow & the "configuration takes effect" mechanism
04-protocol-implementation.mdimplementation details of the 4 protocol servers
05-api.mdREST API reference (with request/response examples)
06-run-deploy.mdrunning, frontend build, Docker deployment, environment variable overrides
07-verification.mdverification & testing methods (three-layer verification system)

Chinese versions of these documents are available under doc/.

Project Structure

.
├── 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

Testing & Verification

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.

Environment Variable Overrides

Override each protocol's initial values in docker-compose.yml or the shell without modifying code:

PrefixDescriptionExample
MB_*Modbus initial valuesMB_FLOAT_0=42.5, MB_COIL_BITS=1023
UA_*OPC UA variable valuesUA_MyDevice_Temperature=30.0
BAC_*BACnet object presentValueBAC_AV_2=70.0, BAC_BV_1=inactive
S7_*S7 memory area bytesS7_DB1_DBD0=99999, S7_M_0=255
API_PORTFastAPI 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.

FAQ

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?

  • OPC UA / BACnet / S7 / Modbus all pull values from the Registry via a per-second sync loop, so in theory they take effect within ≤1s.
  • Values written natively by external tools are overwritten by the sync loop with Registry values; to persist, update via the REST API accordingly (see 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.

Contributing

Issues and Pull Requests are welcome.

  • When reporting a bug, please attach the protocol, port, reproduction steps, and logs.
  • Follow PEP 8; run python -m pytest tests after changes to ensure tests pass.
  • When adding or changing protocol behavior, please update the corresponding English technical docs under doc/en/ (and the Chinese versions under doc/).

License

This project is open-sourced under the Apache License 2.0.

Tag summary

Content type

Image

Digest

sha256:10b9dd435

Size

62.6 MB

Last updated

28 days ago

docker pull jettzhan/nexiot-simulator