Network simulation DSL and discrete-event simulator
Project description
SMOLPy
Python rewrite of SMOL — a Network Description Language and Discrete-Event Simulator for industrial Measurement-Diagnostics-Control (MDC) networks.
SMOLPy lets you describe a network topology in pure Python, define traffic flows, and run a discrete-event simulation (powered by SimPy) that produces real metric time-series. A built-in Dear PyGui desktop dashboard shows the topology and live metric charts as the simulation runs.
Install
uv sync # installs all runtime + dev dependencies
Quick start
from smolpy import Network
net = Network("office-net")
host_a = net.adapter("host-A", ip="10.0.0.1")
server = net.adapter("server", ip="10.0.0.10")
sw1 = net.switch("sw1", ports=8, mode="store-and-forward")
net.link(host_a, sw1, speed=1_000, length=5) # speed in Mb/s, length in metres
net.link(server, sw1, speed=10_000, length=2)
host_a.sends(to=server, rate=8_000, size=1_518, pattern="constant")
net.observe("throughput", on=server, every=100) # sample every 100 ms
net.observe("queue_depth", on=sw1, every=50)
result = net.simulate(duration=30_000, live=True) # 30 s simulation with live dashboard
result.report() # print summary table to terminal
Run it:
uv run smolpy run my_script.py
Dashboard
When live=True the simulation runs in a background thread while a full-screen Dear PyGui window opens immediately.
Topology panel (left)
Each node is drawn as a coloured circle:
| Colour | Node type |
|---|---|
| Blue | Adapter (host / server / NIC) |
| Green | Switch |
| Orange | Hub |
Node fill changes dynamically during the simulation:
| Appearance | Meaning |
|---|---|
| Dim (faded) | Node idle — no traffic yet |
| Pulsing bright fill | Node actively transmitting (bytes_sent > 0) |
| Solid bright fill | Node forwarding traffic (switch / hub) |
| Pulsing amber outer ring | Node actively receiving data (bytes_received > 0) |
Animated particles flow along every link to show live traffic direction.
Metrics panel (right)
One chart per observed metric. All series update in real time. Axes auto-scale to fit the data.
Simulation controls
Three controls appear in the title bar during a live simulation:
| Control | Effect |
|---|---|
| ⏸ Pause | Freezes simulation time; dashboard stays interactive. Click again to resume. |
| ▶ Resume | Continues from the exact pause point. |
| ⏹ Stop | Ends the simulation early; plots freeze at the last collected sample. |
Status indicator:
- ● Simulating… — running
- ● Paused — paused by user
- ● Done — completed normally
- ● Stopped — ended by user
DSL reference
Topology builders
adapter = net.adapter("name", ip="10.0.0.1") # NIC / host / server
switch = net.switch("name", ports=16, mode="store-and-forward")
hub = net.hub("name", ports=8)
broker = net.mqtt_broker("name", ip="10.0.2.1") # MQTT message broker
net.link(a, b, speed=1_000, length=10) # Mb/s and metres
Multiple switches can be chained to model hierarchical topologies:
core_sw = net.switch("core-sw", ports=16, mode="store-and-forward")
edge_sw = net.switch("edge-sw", ports=8, mode="store-and-forward")
net.link(edge_sw, core_sw, speed=1_000, length=5) # inter-switch uplink
Traffic
# Basic Ethernet send
src.sends(to=dst, rate=8_000, size=1_518, pattern="constant")
# Delayed start (useful for staggered scenarios)
src.sends(to=dst, rate=8_000, size=1_518, pattern="constant", delay_ms=5_000)
# MQTT publish (sensor-style, constant-rate)
sensor.publishes(to=broker, topic="plant/temp", rate=1.0, payload=20, qos=1)
sensor.publishes(to=broker, topic="plant/temp", rate=1.0, payload=20, qos=0, delay_ms=2_000)
# Broker topic routing — must be called before simulate()
broker.routes("plant/temp", to=[server])
| Parameter | Type | Description |
|---|---|---|
to |
Adapter | Destination adapter |
rate |
float | Frames per second |
size |
int | "imix" |
Frame size in bytes, or Internet Mix distribution |
pattern |
str | "constant", "poisson", or "bursty" |
delay_ms |
float | Simulation time before this flow starts (default 0) |
publishes() parameters
| Parameter | Type | Description |
|---|---|---|
to |
MQTTBroker | Target broker |
topic |
str | MQTT topic string |
rate |
float | Messages per second (default 1.0) |
payload |
int | Payload bytes (default 20) |
qos |
int | 0 = fire-and-forget, 1 = PUBACK acknowledgement |
delay_ms |
float | Simulation time before publishing starts (default 0) |
Traffic patterns
| Pattern | Description |
|---|---|
"constant" |
Fixed inter-frame gap — models a saturated link |
"poisson" |
Exponentially distributed gaps — models random/bursty traffic |
"bursty" |
Pareto-distributed burst lengths — models ON/OFF sources |
Frame sizes
| Value | Description |
|---|---|
| integer | Fixed size in bytes (e.g. 512, 1_518) |
"imix" |
40 % × 64 B, 57 % × 594 B, 3 % × 1 518 B |
Observations
net.observe(metric, on=node, every=interval_ms)
| Metric | Unit | Observed on |
|---|---|---|
throughput |
Mb/s | Adapter |
latency |
µs | Adapter |
frame_loss |
% | Adapter |
bytes_sent |
MB | Adapter (sender) |
bytes_received |
MB | Adapter (receiver) |
queue_depth |
frames | Switch |
utilization |
% | Any node |
collision_rate |
/s | Hub |
broker_queue |
msgs | MQTTBroker |
Simulation
result = net.simulate(duration=30_000) # headless — silent, fastest
result = net.simulate(duration=30_000, text=True) # rich text dashboard in terminal
result = net.simulate(duration=30_000, live=True) # full Dear PyGui desktop window
result.report() # print summary table (avg / min / max per metric)
result.plot() # open static dashboard for a completed result
# Export metric time-series (format inferred from extension)
result.export("results.csv") # long CSV: time_ms, metric, value
result.export("results.json") # JSON dict of lists-of-pairs
result.export("out.csv", format="csv") # explicit format override
Text mode (text=True) displays a live updating table in the terminal — no display server or GUI toolkit required. Ideal for headless servers, SSH sessions, and CI environments.
Quick demo
smolpy demo # built-in 3-client scenario, text mode, no script needed
MQTT publish-subscribe
SMOLPy models application-layer MQTT traffic on top of the standard Ethernet/IP/TCP wire model.
What is modelled
- Publisher adapters call
publishes()to emit periodic MQTT PUBLISH frames at a fixed rate toward anMQTTBrokernode. - The broker receives PUBLISH frames and fans out one copy to each registered subscriber per topic (
routes()). QoS 0 delivers silently; QoS 1 additionally sends a PUBACK frame (58 bytes) back toward the publisher. - Subscriber adapters receive forwarded copies just like normal Ethernet frames; all standard metrics (
throughput,latency,bytes_received) apply. broker_queuesamples the broker's inbound store depth — unprocessed PUBLISH frames waiting to be forwarded. A non-zero and rising queue indicates the broker or its downstream link is becoming a bottleneck.
Frame size formula
frame_size = 54 (Ethernet+IPv4+TCP) + 2 (MQTT fixed header) + 2 (topic-length field) + len(topic) + (2 if qos > 0 else 0) + payload_bytes
A typical small sensor message (topic="plant/temperature", payload=20, qos=1) produces a 96-byte frame, roughly 16× smaller than a maximum-size bulk frame (1 518 B).
Dashboard
MQTTBroker nodes appear as purple circles in the topology panel.
Simulation engine
- MAC-learning switch — each switch pre-seeds its forwarding table from the topology wiring, eliminating spurious flooding toward silent endpoints (e.g. a server that only receives). Dynamic learning still operates for traffic through intermediate switches.
- Store-and-forward model — transmission delay + propagation delay per hop.
- Queuing — each link direction is an independent SimPy Store;
queue_depthreports buffered frames at the switch's outbound ports. - Traffic shaping — constant, Poisson, and Pareto-burst patterns; IMIX frame-size distribution.
- Live mode — simulation runs in 200 chunks (~8 s total wall time); the dashboard reads shared metric arrays between chunks via Python's GIL.
Examples
See examples/README.md for eight ready-to-run scenarios covering single-switch saturation, oversubscription, two-tier access bottlenecks, and MQTT publish-subscribe.
uv run smolpy run examples/example.py
uv run smolpy run examples/example_two_tier.py
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smolpy-0.2.3.tar.gz.
File metadata
- Download URL: smolpy-0.2.3.tar.gz
- Upload date:
- Size: 113.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb03504ebf8a878d4018233763197b4c5c8b5ae255dfa807ebe515ddf670d494
|
|
| MD5 |
a67c706b435e111288ce4c97a4289fd0
|
|
| BLAKE2b-256 |
79dbcf449bfe73879187a54e6cb89d2f7486699440da71a06de21b3b9adf664f
|
File details
Details for the file smolpy-0.2.3-py3-none-any.whl.
File metadata
- Download URL: smolpy-0.2.3-py3-none-any.whl
- Upload date:
- Size: 26.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e5e8929306ebcede25e01ab8e8d929068a3d33de37e0379814aba5b113a1cff
|
|
| MD5 |
85894f2cb7fcfdaabadc006e903ee86a
|
|
| BLAKE2b-256 |
c9e91aeed93f3c74d11a5d0d0fae4e417d073329052e04db1f0a683fcce17e32
|