- Python 98.8%
- Shell 0.7%
- Inno Setup 0.5%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| docs | ||
| locales | ||
| maps | ||
| packaging | ||
| src | ||
| tests | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| app.py | ||
| CHANGELOG.md | ||
| environment.yml | ||
| LICENSE | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| ROADMAP.md | ||
| THIRD_PARTY_NOTICES.txt | ||
Modbus Logger
A cross-platform (Windows / Linux) tool to read, graph and configure Modbus devices over Modbus RTU (RS-485) and Modbus TCP. Built in: Danfoss EIM 316 and EKD 316 stepper-valve drives, replacing the end-of-life Danfoss MiniLog tool for supporting installed products. Any other Modbus device can be added with a device file (docs/device-files.md). The interface is in English or Danish.
Install: download the newest release from
git.topdrive.dk/thorsten/modbus-logger/releases:
the Windows installer (…-windows-x64-setup.exe) or the Debian/Ubuntu package
(sudo apt install ./modbus-logger_<version>_amd64.deb). The app updates itself from
there (Help → Check for updates).
Free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version (GPL-3.0-or-later). Bundled components: THIRD_PARTY_NOTICES.txt. Changes per version: CHANGELOG.md.
Why this exists
MiniLog's licence key expired at the end of 2025 and Danfoss can no longer issue keys
for it. Instead of touching MiniLog's licence protection, this is an independent
tool. It talks to the drives with the open Modbus RTU protocol, using the drives'
register maps (the plain-text .LG files MiniLog shipped) plus the official Danfoss
parameter documentation. It does not use or need any MiniLog code.
Independent tool, not affiliated with or supported by Danfoss. Verify the behaviour on a real drive before relying on it in the field (see ROADMAP.md, Phase 1).
Features
- Connect through a USB-RS485 adapter, a Modbus TCP device or gateway, or a transparent RS485-to-Ethernet converter (RTU over TCP).
- Scan the bus. It confirms each drive by reading back its own address register (PNU 2008), detects how the drive maps parameter numbers to Modbus addresses, and suggests the drive type.
- Graph: tick any datapoints to plot them as live lines in one plot, with a y axis per unit. Set the sample interval and the visible window, pause, pan or zoom; fix a y axis to a min / max or let it scale automatically.
- Parameters: every writable setting is listed with its current value and with
ranges, defaults and units from the Danfoss documentation.
- Editors are bounded by those ranges (dropdowns for enumerations).
- Changes are staged and only sent after a confirmation that lists old → new values and any warnings.
- Every write is read back to confirm it (except communication settings, see below), and recorded in an audit log.
- Factory, calibration and debug registers are hidden unless you turn on "Show expert settings".
- CSV export of this session, the visible window, the last 24 h / 7 days, or everything recorded.
- History: every sample is stored locally in a SQLite file. Data older than 183 days (~6 months) is deleted automatically.
- Sessions keep the history of different plants apart (New session…).
- Other devices via device files: holding / input registers, 16- and 32-bit integers, floats (File → Import device file).
- Danish or English interface; by default the system language (Settings → Language).
- Updates: checks an update feed, downloads, verifies and installs a new version (Help → Check for updates, see below).
- Demo simulator (Tools menu) to try or train with the tool without hardware.
--self-testmode to verify an installation or a packaged build.
Supported devices
| Device | Map | Factory address | Datapoints | Writable | Ranges / defaults from |
|---|---|---|---|---|---|
| Danfoss EIM 316 | Modbus_A165.LG |
165 (164 without jumper KM7) | 125 | 93 | EIM 316 manual DKRCC.PS.RQ0.A1.02, pp. 13–14 |
| Danfoss EKD 316 | Modbus_A240.LG |
240 | 43 | 35 | MiniLog spreadsheet EKD316 084B8040 MiniLog Modbus Parameter.xls |
| Danfoss EcoFlow | Modbus_A5.LG |
5 | — | — | map not available yet: see Adding a drive (EcoFlow) below |
(The EKD 316 .LG map lists r12 MainSwitch twice; the tool uses it once.)
Default communication: 19200 baud, 8 data bits, even parity, 1 stop bit. An EKD 316 may be set to 38400.
Quick start (from source)
Requires Python 3.11 or newer (tested with 3.13).
git clone https://git.topdrive.dk/thorsten/modbus-logger.git
cd modbus-logger
python -m venv .venv
.venv\Scripts\activate # Windows
source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
python app.py
With conda instead (conda-forge Python, the app packages via pip):
conda env create -f environment.yml
conda activate modbus-logger
python app.py
Without hardware: Tools → Start demo simulator (or the button on the empty Graph tab), then Scan and Connect. Demo data is recorded as a separate "(simulator)" device in the history. The simulated bus has an EIM 316 at address 165 and an EKD 316 at address 240. Their values move like a running valve, and changing e.g. n09 Max SH visibly shifts the simulated superheat.
Connecting to a real drive
- Adapter: any 2-wire (half-duplex) USB-RS485 adapter. FTDI- or CH340-based
adapters work on all three OSes. Note the port:
COMxon Windows,/dev/ttyUSB0on Linux,/dev/tty.usbserial-…on macOS.- Linux: your user needs serial access (
sudo usermod -aG dialout $USER, then log in again). The tool says so if the port can't be opened for this reason. - Ubuntu: the
brlttypackage (braille displays) grabs CH340 adapters, so the port disappears right after plugging in. Unless you use a braille display:sudo apt remove brltty. - The port list shows USB adapters first and refreshes each time it is opened.
- Linux: your user needs serial access (
- Wiring (EIM 316, manual p. 15): RS485 (+) to KM4 pin 2, RS485 (−) to KM4 pin 3. Alternatively use KM5 pins 2 / 3, with pin 1 for the cable shield. On long lines, terminate both bus ends. If nothing answers, try swapping + and −.
- In the connection bar, choose the Port, check Baud / Parity, and click Scan. A quick scan tries the factory addresses at the current baud rate and parity. The arrow next to Scan offers Scan all baud rates and parities (about 10 s, for a drive with unknown settings) and a full scan of addresses 1–247 (about 75 s on an empty bus). The result dialog has a Connect button.
- Connect. The tool checks the drive before it starts polling:
- No answer within about 2 s: the status turns red with a checklist, and nothing is polled.
- The unit-address register (PNU 2008) must echo the address. If it doesn't, the PNU offset is wrong, and Scan detects it. An EIM 316 without jumper KM7 answers on its secondary address (PNU 2009); that counts as a match.
- The PNU offset and Fast reads are under Advanced in the connection bar.
- EIM 316 only: if Modbus translation tables are enabled (PNU 64200, factory default 1), only some registers are accessible. The tool warns you. Switch them off under Parameters → Modbus trans → No, if the drive allows it.
Using it
Graph tab: tick datapoints on the left to poll and plot them. Their current values show in the list. All lines share one plot so they can be compared directly; each unit (bar, °C, K, %, switches) gets its own y axis (the first on the left, the others on the right). Ctrl Status is decoded per the manual's Appendix 2 (e.g. "Inject, S2 error, MOP active"). The mouse wheel or a drag inside the plot moves the time axis (all lines together) and pauses auto-follow until you click Follow live; over a y axis it zooms or moves that axis. Y axes… fixes an axis to a min / max or sets it back to Auto; the same is in the right-click menu (in the plot: every axis, follow live, save as image; on an axis: just that axis). Settings → Graph defaults… sets the default range per unit (preset: °C and K −20 … 30, % 0 … 100, others Auto); what you set on an axis yourself overrides it until you choose Default again. Pause logging stops reading and recording until you resume it.
Modbus TCP: choose Modbus TCP (a Modbus TCP device, or a gateway to RS-485, usually port 502) or RTU over TCP (a transparent serial-to-Ethernet converter) in the connection bar instead of Serial (RS-485), then enter host and port. The address is the unit id: for a gateway, the drive's RS-485 address.
Parameters tab:
- Column widths can be dragged; the tool remembers them.
- Double-click a New value (or select a row and press Enter) to stage a change. Values are typed with a decimal point; a comma is accepted and turned into one.
- Write changes… first checks the values against the documented ranges, then re-reads them from the drive, so the confirmation shows the drive's current values and flags any that changed. While writing, editing is locked; a summary lists what was written and what failed, with the value the drive holds now.
- Writes are read back to confirm them, and every attempt (also failed ones) is recorded in the audit log.
- EIM 316: configuration changes are only accepted while regulation is stopped (r12 Main switch = Off), per manual Appendix 1. If regulation is running, the confirmation offers to stop it for the write and start it again afterwards. r12 is always written first when switching off and last when switching on. A banner shows while regulation is stopped.
- Communication settings (address, baud rate, parity, stop bits) are written on their own. They are not read back, because the drive stops answering on the old settings: the row shows "sent – reconnect to confirm", the connection bar is updated, and Reconnect now checks the drive answers on the new settings. Unit addresses are limited to 1–247 and stop bits to 1 (the tool always uses 1).
- Settings marked ⚠ affect communication or the running plant.
Keyboard: Ctrl+K connect / disconnect, Ctrl+Shift+K scan, F5 read all parameters, Ctrl+S write changes, Ctrl+E export CSV (also in the Drive and File menus).
Fast reads: polling one register per request is the safe default. MiniLog's release notes say the Danfoss implementation "differs from standard Modbus regarding reading of multiple registers". Use Tools → Test fast reads on this drive to compare both methods on the connected drive. Only tick Fast reads if the test reports identical values.
Data, history and CSV
-
Sessions split a drive's history where you say so: New session… on the Graph tab (or File → New session…, Ctrl+N) asks for a name, e.g. the plant or customer. Use it when you move to another plant whose drive has the same type and address; otherwise their data would run together. A session lasts until you start the next one for that drive, also across disconnects; the first one starts automatically. The graph only shows the current session; the CSV export lets you pick a session.
-
History file:
%LOCALAPPDATA%\ModbusLogger\history.sqlite(Windows),~/Library/Application Support/ModbusLogger/(macOS),~/.local/share/ModbusLogger/(Linux). Use File → Open history folder to find it.- It is a per-user app-data folder rather than the OS temp folder, because macOS and Linux empty their temp folders on reboot, which would defeat the 6-month history.
-
Retention: samples and audit entries older than 183 days are deleted on start-up and every hour.
-
Size: about 80 bytes per sample, so 6 datapoints at 1 s is about 41 MB per 24 h of continuous logging.
-
Parameter writes are audited in the
param_writestable: time, drive, PNU, old and new value, result (ok,sent= not read back,failed), read-back value and error. -
CSV: Export CSV… (Ctrl+E) writes a timestamp column plus one column per recorded datapoint, in engineering units, with the unit in the header. Columns follow the drive's register order. An empty cell means the drive did not answer for that sample.
Register maps
maps/*.LG are MiniLog's register maps, unchanged. Each line gives the name, the
PNU (= Modbus holding-register number), read-only or read/write, the scale (÷10 /
÷100) and the enumeration labels. maps/*.enrich.json add what the .LG files lack,
all in raw units: min / max / default, signedness, full enumeration lists, units,
EEPROM flag, warnings and expert flag.
- EIM 316: transcribed from the manual's parameter list (pp. 13–14).
- EKD 316: generated by
tools/extract_ekd316_xls.pyfrom MiniLog's spreadsheet.
The
.LGfiles are the register maps Danfoss published with MiniLog, and the enrichment data comes from Danfoss's public documentation (see THIRD_PARTY_NOTICES.txt).
Note: some EKD 316 enumerations start at 1. For example, o30 Refrigerant has
1 = R12 and 3 = R134a, following the spreadsheet's minimum and matching Danfoss's
refrigerant numbering. The enum_base field handles this.
Adding a drive (EcoFlow)
The EcoFlow profile (address 5, 19200 8E1) is already defined. Put Modbus_A5.LG, in
the same format as the others, into maps/. For a packaged build, put it in a maps
folder next to the executable. The device appears automatically. Optionally add a
Modbus_A5.enrich.json with ranges.
Building packages
PyInstaller does not cross-compile: build the Windows package on Windows and the Linux
package on Linux. In the conda environment (or a venv with requirements-dev.txt):
python packaging/build.py
It runs the tests, builds with PyInstaller, runs --self-test on the build and packs it:
| OS | Output in dist/ |
Install |
|---|---|---|
| Windows | ModbusLogger-<version>-windows-x64.zip |
Unzip anywhere and start ModbusLogger.exe |
Windows, with --installer |
ModbusLogger-<version>-windows-x64-setup.exe |
Per-user installer, no admin rights (needs Inno Setup 6, ISCC.exe on PATH) |
| Linux | ModbusLogger-<version>-linux-x64.tar.gz |
Unpack, run ./install.sh (per user: ~/.local/opt, menu entry, modbus-logger command; --remove uninstalls) |
Linux, with --deb |
modbus-logger_<version>_amd64.deb |
sudo apt install ./modbus-logger_<version>_amd64.deb (/opt/modbus-logger, menu entry, modbus-logger command). Built in an Ubuntu 22.04 container by the pipeline, so it runs on Ubuntu 22.04+ / Debian 12+ |
Each archive gets a .sha256 checksum next to it. The unpacked build is about 240 MB,
the archive about 95 MB (mostly Qt and numpy). The version number comes from
src/version.py.
To check a build by hand: dist/ModbusLogger/ModbusLogger --self-test (exit code 0 =
OK). On Windows the executable is a GUI program, so PowerShell needs
(Start-Process dist\ModbusLogger\ModbusLogger.exe -ArgumentList '--self-test' -Wait -PassThru).ExitCode.
- Linux: Qt needs the xcb libraries (Debian/Ubuntu:
sudo apt install libxcb-cursor0). - macOS (not packaged yet): unsigned apps need right-click → Open the first time.
Releases and updates
Releases are built by Forgejo Actions (.forgejo/workflows):
-
Set the version in
src/version.py, add a section toCHANGELOG.mdand a<release>entry topackaging/linux/dk.topdrive.modbuslogger.metainfo.xml(a test checks it), commit. -
Tag and push:
git tag v0.3.0 && git push origin v0.3.0. -
release.ymlchecks that the tag matchessrc/version.pyand creates a draft release with the changelog section as notes. Then:- the Linux runner (Ubuntu 22.04 container) runs the tests and builds the
tar.gz and the
.deb; - the Windows runner (a Windows 11 VM, provisioned by the proxmox tool) runs the tests and builds the zip and the installer.
Each file is uploaded with a
.sha256checksum. The release is published only when both builds have finished. - the Linux runner (Ubuntu 22.04 container) runs the tests and builds the
tar.gz and the
-
test.ymlruns the tests on every push.
The automatic token of the workflow creates and publishes the release (on
git.topdrive.dk it may). Where it may not, add a repository secret RELEASE_TOKEN (a
token with write:repository). Optional variable: DEB_MAINTAINER (Name <e-mail>).
In the app, Help → Check for updates… reads the newest published release of the
release source (Settings → Updates…, default this repository;
$MODBUS_LOGGER_UPDATE_URL overrides it). How it updates depends on how it was
installed:
| Installed from | Update |
|---|---|
| Windows installer | The new setup runs silently after the program closes, then starts it again |
| Windows zip / Linux tar.gz | The program folder is swapped after the program closes; the new version runs --self-test, and the old folder is restored if that fails |
| Debian package | apt installs the new .deb; the password prompt comes from pkexec |
| Source checkout | Shows that a new version exists; update with git pull |
Every download is checked against its published SHA-256 before anything changes. Optionally the check runs at start-up.
Development
app.py entry point (--self-test)
src/registers.py .LG parser, Datapoint model (16/32-bit, float), enrichment, validation
src/device_profiles.py device profiles (Danfoss built in), maps lookup, drive-type suggestion
src/device_files.py *.device.json: format, validation, template, import
src/modbus_client.py pymodbus RTU / TCP client: reads, verified writes, detect(), probes
src/poller.py background polling thread + prioritized job queue (writes jump ahead)
src/historian.py SQLite history, 183-day pruning, CSV export, write audit
src/status_bits.py Ctrl Status decoding (EIM 316 manual, Appendix 2)
src/simulator.py simulated drives (pymodbus SimDevice), also usable from the CLI
src/i18n.py UI language (tr / N_), translations in locales/
src/updater.py update feed, download + checksum, folder swap
src/version.py version number
src/ui/ PySide6 + pyqtgraph user interface
maps/ register maps + enrichment
locales/ translations (da.json); check with tools/i18n_check.py
packaging/ build.py (packages, .deb), PyInstaller spec, Linux installer,
Inno Setup script, forgejo_release.py (CI), ci/linux-setup.sh
.forgejo/workflows/ test.yml (every push), release.yml (version tags)
tools/ map extraction, i18n_check.py
tests/ pytest suite (unit, client<->simulator integration, offscreen GUI end-to-end)
pip install -r requirements-dev.txt
pytest -q
python src/simulator.py --tcp 127.0.0.1:5020 --devices EIM316,EKD316 # then connect to rtu-tcp://127.0.0.1:5020
python src/simulator.py --tcp 127.0.0.1:5020 --pnu-offset -1 # drive that uses PNU-1 addressing
python src/simulator.py --tcp 127.0.0.1:5020 --translation-table # EIM 316 with translation tables on
python src/simulator.py --tcp 127.0.0.1:5502 --modbus-tcp # Modbus TCP framing (tcp://127.0.0.1:5502)
python tools/i18n_check.py --update # after adding or changing texts
python src/simulator.py --serial COM11 # one end of a virtual COM pair (com0com / socat)
Notes on pymodbus 3.15, which this code is written against:
- The unit-id keyword is
device_id=. - The simulator uses the
SimData/SimDeviceAPI.ModbusDeviceContextand the old data blocks are deprecated shims. SimCoreraisesKeyErrorfor unknown unit ids, so the server answers "device failure" instead of staying silent.simulator._SilentUnknownIdsrestores RS-485 behaviour.
Troubleshooting
| Symptom | Things to check |
|---|---|
| Scan finds nothing | Port; + / − swapped; termination; drive powered; try Scan all baud rates and parities (an EKD 316 may use 38400), then a full scan |
| Port can't be opened (Linux) | Not in the dialout group (the message says so); adapter taken by brltty on Ubuntu; another program has it open |
| "drive reports address X" after connecting | Wrong PNU offset: run Scan, which sets it |
| Many values show — on an EIM 316 | Translation tables on (PNU 64200). With them on, the manual shows the address registers at 40041 / 40042 instead of 2008 / 2009 |
| A write is refused | EIM 316: stop regulation first (r12 Main switch = Off); check the value is in range |
| Link lost after a write | You changed address / baud / parity: the connection bar was updated, so click Connect |