A clean-room Modbus Poll reimplementation with a SCADA mode on top.
Poll Modbus/TCP & RTU devices into live value tables, import real Modbus Poll .mbp/.mbw files byte-exactly,
style any row from a 32-branch rule tree and build mimic diagrams —
all in a ~6 MB single executable. No Modbus Poll sources were read, ever.
English · Русский 🌐 Project site: kostyk348.github.io/mbscada-rs
- 🖥 Modbus Poll, but better. Polls 01/02/03/04, writes 05/06/0F/10, per-poll slave / base-0·1 address / scaling / engineering units — and every connection gets a live traffic log with per-response turnaround.
- 🤝 Drop-in for your existing configs. Real Modbus Poll
.mbp/.mbwfiles import by folder — and a byte-exact round-trip layer (write(parse(bytes)) == bytes, proven on real 10.5 files) keeps them intact. - 🎨 32 styled branches per row. The original gives you two colour rules; here every row gets a first-match rule tree with per-branch dynamic row text («0 → Закрыт, 1 → Открыт»).
- 📈 Trends, alarms, CSV. Per-row sparklines, hi/hi‑hi/lo/lo‑lo alarms, blink styles — and a one-click CSV value log.
- 👁 Parameter watch window. Pin the registers you actually watch — or computed tags — into a floating window with big live values, alarm colours and session min/max (v1.2).
- 🧪 Slave simulator + typed values. The app can answer as a Modbus device — poll it with any master (or with itself): editable register grid, sine/ramp/noise auto-generation, TCP, RTU-over-TCP and — since v1.4 — a physical COM/RS-485 line (USB-RS485 adapter, 9600 8E1 and friends; line garbage never wedges the slave). Values display and write as real / int / uint mixed — float32/64 with all four word orders, 32/64-bit ints, hex/bin — with typed write modals and a per-cell dec → hex → bin display cycle (v1.3).
- 🧮 Computed tags. Arithmetic on your tags —
{T2 jacket} - {T1 reactor},min(...),pow(...)— evaluated by the engine every scan and shown in their own table (v1.1). - 🗄 Journal + archive + diagnostics. Engine events (RX/WR/ERR/SYS) appended to a log file, configurable CSV historian (change-only or interval mode), per-connection/per-poll diagnostics with reconnect counters (v1.1).
- 🌐 RU/EN built in. One switch in the toolbar flips the entire UI; docs ship in both languages.
- ⚡ Fast & local. A multithreaded engine (
mb-engine) drives all connections concurrently; no telemetry, no cloud, nothing leaves the machine. - 🎓 Tutorial + demo project. An interactive tour that highlights real UI elements and auto-advances once you've done the step — plus a "🚀 Demo project" button: the app raises its own slave simulator and polls itself, so live tables, rules, a computed tag and a dressed-up mimic come alive in 10 seconds (v1.5).
- 🎛 Rules on the mimic + new widgets. Lamps, gauges, bars and values are colored by the fired rule branch (color/text/blink) — exactly like table rows; the "State" widget shows the branch text ("OPEN/CLOSED"), trends plot the history with min/max spans, sliders write a value into a register, computed tags became a widget source, and a "Rule for register…" button sits right in the widget settings (v1.5).
Prebuilt installers for every platform — no Rust toolchain needed. Grab them from Releases:
| Platform | Files |
|---|---|
| Windows 10/11 x64 | NSIS installer .exe · .msi |
| Linux x86_64 & aarch64 | .AppImage (runs anywhere) · .deb |
| macOS Apple Silicon & Intel | .dmg |
Windows: WebView2 is preinstalled on Win10/11. Linux AppImage bundles its own WebKit. Every release is built automatically by GitHub Actions from a
v*tag — five runners, one commit, all platforms.
- Every poll is an independent view of the address space: own function, own register window, own scaling
×mult ÷div +offset, own 0/1 address base. - Values render in Modbus-Poll-style formats:
u16 / i16 / hex16 / bin16 / ascii16 / u16(swapped)·u32 / i32 / hex32·float32in word orders ABCD / CDAB / BADC / DCBA · 64-bitu64 / i64 / f64. - Per-row sparklines keep the last 40 samples as inline SVG — the last buffer change blinks.
- Click a row to write a value (FC 05/06 for bits, FC 16 for word arrays).
- Threshold alarms exactly like Modbus Poll: hi / hi‑hi / lo / lo‑lo with messages.
- Styling rules with up to 32 branches per row and operators
> ≥ < ≤ = ≠, in-range / out-of-range, bit test. - First matching branch wins — the classic «Rule 1 over Rule 2» becomes a full priority list.
- Dynamic row text per branch: the row label is replaced when a branch fires (state machines live in the table header).
- Alarm side panel: active alarms surface as cards — severity, fired-at, live value, message — acknowledge one or all, cleared alarms drop to history with durations, optional beep, and a click jumps to the table row.
- Pin the parameters you actually watch — any register from any poll, or computed tags — into a floating window over the workspace (👁 Watch in the toolbar).
- Big live values in engineering units, coloured by the rule tree (alarm fg/bg carry over from the table row), session min / max per card, quality and «no link» markers.
- The pinned set is stored in the project (shared with Save/Open); the window position is remembered per workstation. Double-click a card to give it a custom caption, ✕ unpins, ⟲ resets min/max, clicking a value jumps to the table row.
📖 Full simulator guide (RU):
docs/guide-slave-ru.md— quick start, connecting masters, data types, dec/hex/bin display, recipes and troubleshooting. An English version is planned.
- Both sides of the wire in one app. The new Slave simulator tab serves coils / discrete / holding / input registers over Modbus TCP, RTU-over-TCP and, since v1.4, a physical serial line (USB-RS485 adapter: port, baud, parity, data/stop bits) — point any master at
localhost:10502, wire it to an RS-485 bus, or poll it with mbSCADA itself. - Live register grid with a typed view (u16 / i16 / hex / u32 / i32 / float CDAB·ABCD): edit a cell and the slave’s data model changes immediately; checkboxes for bit areas; the raw register bits are shown next to the typed value.
- Auto-generation (sine / ramp / noise with amplitude, base and period) animates the selected range, so alarm rules, trends and mimics have something realistic to chew on.
- Typed values everywhere. Every poll carries a data type — the quick selector sits right on the table card (one click after the
.mbpimport). Typed write modals encode the value into registers for you: float → two words with the right word order, i16 → two’s complement, hex/bin inputs accepted as0x…/0b…. - Per-cell display: right-click any value to cycle dec → hex → bin (per row), or set the default for the whole poll.
- UI overhaul under the hood: tables, mimic and the journal now update in place instead of rebuilding the DOM every 300 ms — no more lag and no more double-clicks torn apart by a re-render.
![]() |
Editor with lamps, gauges, bars, live values and toggle-write buttons bound to registers — every widget configurable by double-click… plus the alarm panel and traffic log on the right |
Modbus Poll has no SCADA mode. That is the point of mbSCADA.
- Computed (virtual) tags: a safe arithmetic evaluator in the engine —
{tag} references,p<poll>:<addr>aliases,+ - * / % ^, functionsmin / max / avg / abs / sqrt / round / floor / ceil / pow. Results appear in their own table above the polls (with units and error messages in place), and are archived to CSV together with the live tags. - Event journal to file: the engine appends every RX / WR / ERR / SYS line (with full timestamps) to the log file configured in «Журнал и архив» — a permanent record of the exchange alongside the in-app log.
- Configurable CSV archive: change-only mode (a row when a value changes) or «all tags by period» mode; the file is stored in the project and automatically resumes on start.
- Diagnostics panel: per connection — TX/RX, errors/timeouts, reconnects, last/avg turnaround, time since the last successful exchange, last error; per poll — ok/err counters and its own turnaround.
- Import of
.mbp(single window) and.mbw(workspace with the window list) — one by one or by folder. - Byte-exact round-trip (
mbp_doc): the file is segmented (head/ row records / closing marker) and reassembled bit-for-bit; editing an alias touches nothing else. Verified byte-exactly against a private real-file corpus produced by Modbus Poll 10.5 (7 files, RU aliases, UTF‑16 markers); CI runs the same test on a public synthetic corpus. - Format, multiplier, address base, aliases and colours carry over into polls; imported polls bind to your own connections (host:port match first, else the first one) — no connections are silently pulled from the files. Each connection carries a default slave ID that its polls inherit (a poll may still override it).
📐 Real corpus layout (reverse-engineered)
[head] magic v4=7000 (v5=10010), IP marker (v5), [144][func][addr][qty]
[row record] × qty marker FF FE FF + len + UTF-16LE alias → PAD 55555555
+ 6× colour u32 (FF000000 AVG) + zeros
[last row + settings] the last row's gap holds the settings blob & tail
[period][slave][1][0][0][…]
[closing marker] final empty marker FF FE FF 00 + trailer bytes
Download an installer for your OS from Releases (see the table above), install — and run mbSCADA.
Requirements: Rust 1.85+, Windows (WebView2 is preinstalled on Win10/11), or Linux with libudev-dev pkg-config libwebkit2gtk-4.1-dev for GUI work.
git clone https://github.com/kostyk348/mbscada-rs.git
cd mbscada-rs
cargo test # 68 engine/proto/format tests
# demo slave (no hardware needed): serves registers on 127.0.0.1:502
cargo run -p mb-sim --example demo_server -- 502
# the app (Tauri 2), raw binary
cargo build --release -p mbscada
# → target/release/mbscada
# or full installers for your current OS
cargo install tauri-cli --version '^2'
cargo tauri buildLaunch mbSCADA, «Импорт папки…» your .mbp folder and hit Старт — real Modbus Poll windows appear as polls and start flowing.
┌────────────────────────────────────────────────────────────────────┐
│ Tauri 2 desktop shell (Rust) │
│ src-tauri: dialogs, project JSON, CSV append, engine restarts │
├────────────────────────────────────────────────────────────────────┤
│ UI (vanilla JS) │
│ tables · rules (32) · SCADA editor · watch window · traffic log │
├────────────────────────────────────────────────────────────────────┤
│ crates/ │
│ mb-proto PDU encode/decode · MBAP · RTU+CRC · ASCII ':'+LRC │
│ mb-format .mbp/.mbw parser → lossless MbpDoc → semantic Table │
│ mb-engine multithreaded poller: TCP · RTU-serial · RtuOverTcp │
│ · Ascii-serial · AsciiOverTcp, rules, alarms, trends │
│ mb-sim slave simulator (+ demo_server example) │
└────────────────────────────────────────────────────────────────────┘
- ✅ Modbus/TCP, RTU, RTU-over-TCP transports with per-connection traffic log (v0.1)
- ✅ Import
.mbp/.mbwby file and by folder (v0.1) - ✅ 32-branch styled rules + dynamic row text — UI and engine (v0.2)
- ✅ Display formats through 64-bit: hex32 / ascii16 / u16‑swapped / u64 / i64 / f64 (v0.3)
- ✅ Byte-exact
.mbpround-trip on a real corpus (v0.4) - ✅ Modbus ASCII framing + ASCII serial & over-TCP transports (v0.4)
- ✅ Per-row sparklines + CSV value log (v0.4)
- ✅ In-place editing of connections / polls / rules; per-connection default slave ID; imports bound to your own connections (v1.0)
- ✅ Prebuilt release bundles: Windows x64, Linux x86_64 + aarch64, macOS Intel + Apple Silicon (v1.0)
- ✅ Alarm side panel (ack, history, sound, jump-to-row) + SCADA gauge / toggle-write widgets (v1.1)
- ✅ One-click RU/EN UI switch, bilingual docs, refreshed screenshots (v1.1)
- ✅ Computed tags: safe expression engine + own table + editor (v1.1)
- ✅ Engine event journal to a log file; configurable CSV historian (change-only / interval) that resumes on start (v1.1)
- ✅ Diagnostics panel: per-poll ok/err stats, reconnect counters, last-success age (v1.1)
- ✅ Fix: SCADA gauge colored arc overflowed the widget border (v1.1)
- ✅ Fix: SCADA gauge arc swept the wrong way above half-scale — SVG large-arc flag on a ≤180° sweep (v1.5.1)
- ✅ Production-grade alarm panel: colored severity chips (CRIT/HIGH/LOW) next to the value, severity-first sorting, re-alert on level escalation, unacknowledged-critical dot pulse, info level is silent (v1.5.2)
- ✅ Fix: Windows shortcut kept the old icon after an in-place upgrade — the installer now flushes the shell icon cache (SHChangeNotify via an NSIS hook) (v1.5.2)
- ✅ Parameter watch window: floating panel of pinned registers / computed tags — big live values, alarm colours, session min/max, custom captions, jump-to-row (v1.2)
- ✅ In-app slave simulator: Modbus TCP / RTU-over-TCP device emulation with an editable register grid, typed view and sine/ramp/noise auto-generation (v1.3)
- ✅ Typed values: quick data-type selector on every table card, typed write modals (float / 32-bit word orders, hex/bin inputs), per-cell dec → hex → bin display (v1.3)
- ✅ Fix: engine
regs_per_valueignored f32 word orders — float polls produced wrong rows (v1.3) - ✅ UI performance: in-place table/mimic/journal updates instead of full DOM rebuilds — double-clicks no longer torn by the 300 ms re-render (v1.3)
- ⬜
.mbpwriter in the UI — «Save as Modbus Poll» (round-trip layer is already engine-side) - ⬜ Per-row trend window (bigger than a sparkline)
Is this a Modbus Poll rip-off?
No. This is a clean-room implementation: the wire protocol is public spec, and the .mbp layout was learned from byte-level analysis of files produced by Modbus Poll 10.5 — no proprietary source code was touched.
What "byte-exact round-trip" means for me? Your Modbus Poll files go in, everything else comes back identical. mbSCADA can edit aliases without regenerating the whole file, so unedited bytes stay exactly as Modbus Poll wrote them.
Does it talk to my devices today? TCP / RTU / RTU-over-TCP are production paths with integration tests. ASCII framing and its serial / over-TCP transports are implemented and unit-tested; wire-side integration testing on real devices is on the roadmap.
Why Rust?
One executable, no runtime, memory-safe poller — and the whole engine is testable headlessly (see cargo test).
Issues and PRs welcome. The engine is test-first: crates/mb-engine, crates/mb-format / crates/mb-proto all have unit suites covering Tauri-free paths — start there.


