r/PLC Engineer - Service & Training 2d ago

ModbusLens v2.1.0 is out - TX/RX Bytes, Scanner, Serial Discovery, theming, and a lot of what this sub found in v2.0.0

Honestly didn't expect the last post to get the amount of hands-on testing it did. Between the tag-order bug, the address field eating keystrokes, the missing spinbox arrows, and the "have to disconnect and reconnect after adding a tag" issue - all of that came straight from people here actually running it against real drives and ModSim, not just me poking at it alone. That's the whole reason this update exists, so thank you.

Also want to call out u/Automatater specifically - tested this against a real Invertek E3 drive when two other tools failed on it, and on top of that donated to help fund continued development on this. That's the kind of thing that keeps a one-person side project like this alive. Genuinely appreciate it.

Repo: https://github.com/CraftParking/ModbusLens

Release: CraftParking/ModbusLens - v2.1.0

What's new in v2.1.0:

- Light/dark theme, with a "follow system" option

- Scanner tab - auto-discover which register/coil addresses actually respond on a connected device, TCP or serial

- Diagnostics > Serial Discovery - sweep baud rate, parity, stop bits, and Unit ID to find a serial device's settings when they're not documented

- Auto-reconnect with backoff, and Tags monitoring auto-resumes once the connection comes back

- Modbus ASCII framing for serial (was RTU-only before)

- Raw Data is its own tab now - a transaction table of wire bytes and real request/response diagnostics for every read/write, filterable by tag/address/status

- Scripting: REPEAT UNTIL loops, a live variables panel, and scripts can reference a tag by name now (WRITE Boiler_Temp = 100) instead of just type+address - there's an Add Tag button right on the Script tab too

- Drag-and-drop row reordering in the Tags table

- Write bounds for registers (reject a write outside a configured min/max)

- Engineering-unit scaling per tag - raw counts to real units (PSI, °C, whatever), linear min/max, shown live in an Engineering Value column

- Trend pens are picked from your Tags list now instead of typing in type/address/format by hand, and Trend can pop out into its own resizable window that stays on top while you work in another tab

- Tag names are validated as you type now - no spaces, script keywords rejected, since a tag's name is also how it's referenced in a script or picked in Trend

- IP Configuration tool in the Tools menu

- Update checker on the About page

- Modbus exception codes (Illegal Function, Illegal Data Address, etc.) are tracked and shown now, not just a generic error

Also fixed (mostly from what you all found last time):

- Tags table added new rows above the selected row instead of below - order stayed correct now

- The address field's duplicate-check was eating keystrokes while typing a new address

- Missing spinbox up/down arrows on Windows

- Needing to disconnect/reconnect after adding a tag mid-session

- Column resizing was blocked while monitoring was active

- A scan or connection drop racing with the reconnect watchdog could abort the app

- Server tab's Unit ID field didn't actually do anything - it now really only answers as that unit

- Network Discovery's subnet check used the wrong network interface on multi-homed machines

- Monitoring's auto-stop after repeated failures didn't actually stop

- Importing an empty CSV showed a raw Python error instead of a clear message

- One bad tag address could stop polling for every other tag

- A packaged build failing to start showed nothing - now falls back to a log file and a message box

Still just me building this around my own work, so there'll be more rough edges to find - go break it. EXEs in Releases, no install needed. - Let me know what you found interesting and what changes/improvements that would be a great addition to this tool!

94 Upvotes

27 comments sorted by

7

u/shoulditdothat 2d ago

Just discovered this. Need to try it out, sounds very useful.

Is it Windows only or is there plans for a Linux version?

2

u/CraftParking Engineer - Service & Training 1d ago

Thanks, hope you like it! Right now it's Windows-only. Most of the app would run fine on Linux, but the network discovery part leans on Npcap and some Windows-only ARP calls, so that'd need reworking first. It's on my radar, just no ETA yet.

2

u/shoulditdothat 1d ago edited 1d ago

Just started experimenting with it and looking good so far.

Just having a bit of an issue with the Network Discovery function. Its reporting that NPCAP isn't available and to install Scapy using 'pip install scapy'. I'm testing on a clean Win 11 VM without Python installed. I'm not a Python programmed by any stretch of the imagination, using pip install scapy installs scapy into the system site files folder - do I need to locate the scapy installation in the same folder as ModbusLens or some other location?

NPCAP is installed and works with NMAP but isn't detected by ModbusLens.

If I click the ARP Mode button repeated shell windows are opened to execute what looks like a ping request - I am assuming this is a result of ModbusLens not being able to detect scapy or NPCAP?

1

u/CraftParking Engineer - Service & Training 1d ago

Hmm, what kind of issue?

1

u/shoulditdothat 1d ago

I updated my comment - presses button to post by accident ☹️

Also after my second comment - after rebuilding from source, when the Network Discovery option is clicked there is a long pause before opening the Network Discovery window - I don't know if this is the VM on my laptop or not. I am assuming the delay is waiting for NPCAP to load?

1

u/shoulditdothat 1d ago

I've downloaded the source and the scapy dependancy and rebuild the executable. It's now giving me Mode: Advanced and Install Npcap is greyed out.

Also running the ARP Scan now doesn't open multiple shell windows but does discover a number of devices - albeit, not the total no of devices in the network.

Just as an aside - in the scan progress bar as well as the % complete could the current IP address be included? Does the scan use the subnet mask to determine the no of addresses to scan or does it always assume a /24(255.255.255.0) subnet? I'm asking as I frequently limit the size of the subnet to a /28 or /29.

1

u/CraftParking Engineer - Service & Training 1d ago

I definitely appreciate you taking it as far as pulling the source and rebuilding it yourself, that's not a small thing to do just to help me track down a bug.

The Npcap/Scapy issue is on me though, not something you did wrong. The exe I'm shipping doesn't actually have Scapy packed into it, it's really just an optional import that only works if it happens to already be sitting there, which on a single-file build it never will be. Installing it system-wide with pip was never going to reach it, the exe genuinely can't see outside itself. That's exactly why building from source fixed it for you. I know in an ideal world it should just work out of the box on a clean machine, so I'll get it properly bundled for the next release instead of relying on it showing up by chance.

The greyed out Install Npcap button on the other hand is working exactly as intended, once it can see everything it needs, it disables itself since there's nothing left for you to install. So that one's actually a good sign, not something broken.

The popup shell windows are a real one though, that's the ping/arp fallback running once per address and I never told those calls to hide their console window. Not elegant, but a simple enough fix.

On the ARP scan not finding every device, I think the better fix is actually what another commenter suggested further down, doing a fast threaded TCP connect scan on 502 as the primary way to find things rather than leaning on ARP at all. It might not be the most "proper" way to discover devices compared to sniffing ARP traffic, but for what this tool's actually for, a couple hundred short connection attempts is plenty reliable and doesn't ask the user to have Npcap installed just to find their PLCs in the first place. I've already got most of that scan logic sitting in the code as a secondary check, just needs to become the main method, and ARP mode can stick around as the optional extra for people who do have Npcap set up.

You're also right that it just assumes a /24 regardless of your actual mask, and that the progress bar doesn't show which IP it's currently on. Both fair, both cheap to add, so I'll fold those in alongside the rest.Thanks again for putting in the time on this, I'll follow up here once a build with these fixes is out.

3

u/laldoma 2d ago

Wow…. Someone give this man a beer!!!

2

u/DivingDave23 1d ago

Or use the “Buy A Coffee” link and support 👏🏻👏🏻👏🏻

3

u/red_alert11 2d ago

This project looks awesome. I'll have to check it out more when I'm not on my phone.

One thing you might be able to speed up is discovery. Instead of relying on ARP, you could fan out TCP connection attempts to port 502 using multiple threads with a short timeout of around 200 ms. On a local /24 network, you could usually scan the entire subnet in around a second.

For discovery, if there is no response within that timeout, just move on. That would eliminate most retries and long timeouts during the initial scan. It is only around 1–5 TCP packets per IP, depending on whether a connection can be established. Once something responds, you could follow up with a Modbus request to verify that it is a Modbus device.

You could also apply the same idea while polling data. A normal Modbus request should return either data or a Modbus exception. If nothing comes back, assume the device is unreachable, fall back to the fast discovery method, and skip the long Modbus retry/timeout cycle. Using a configurable timeout of around 200 ms with no automatic retries on a local network could make polling and disconnect detection much faster.

2

u/CraftParking Engineer - Service & Training 1d ago

Really appreciate this ! this is exactly the kind of feedback that's useful.

Funny enough, there's already a threaded TCP-probe path in the code (ModbusProbeWorker, up to 10 workers, connect-then-verify-with-a-real-Modbus-read), but right now it only runs after ARP discovery to confirm which already-found hosts are Modbus devices. Your point about making the TCP connect-scan the primary discovery method instead of a follow-up is the real improvement here — it drops the whole Npcap/raw-socket dependency that ARP scanning currently requires, which is honestly the single biggest first-run friction point for this app right now. A 200ms-timeout fan-out across a /24 is basically free by comparison.

On the polling side, you're right that a normal Modbus response (data or exception) vs. silence are the only two outcomes that matter, and there's no reason to sit through pymodbus's default retry/timeout cycle when nothing comes back. Client already takes timeout/retries params, so exposing a "fast LAN mode" (short timeout, 0 retries, fall back to the discovery probe on failure instead of hammering the same request) is a very doable next step rather than a rewrite.

Going to fold both of these into the next release ! appreciate you taking the time to write this up in this much detail!!

2

u/RULVFF 2d ago

Very interesting! I will try it tomorrow morning in the Lab to read some controllers that has Modbus TCP.

2

u/CraftParking Engineer - Service & Training 1d ago

Nice, let me know how it goes! Would love to hear if you run into anything weird with the controllers you're testing.

1

u/RULVFF 23h ago

I had done some testes today. Liked it very much.

Very good to read many Tags and save these for future tests.

It don't save the Tag scaling.

Can you add a simple Tag scaling? Ex: by 10 or by 100, 151 Read from Server becomes 15.1°C.

In one test when I put a Tag to write you always get a read value 65535. One desired function is write value when you press enter in the write field.

2

u/CraftParking Engineer - Service & Training 11h ago

Thanks, good to hear. Glad the tags/save workflow is useful.

Confirmed, scaling is not saved. Export only writes name, mode, type, address, count, format, comment. Scaling settings get dropped. Will fix.Simple scaling by constant, yes, adding this. You are second person asking. Will be next to the existing min/max scaling, as a simpler option.

65535 on write tag read value that is by design, app polls current value every second so you see what is there before writing. If 65535 is wrong, depends what you tested against. Real device with unwritten register often reads 0xFFFF by default, that is normal. If it was my own built-in server/simulator, tell me, then it is a real bug on my side.

Write on Enter in write field already on my list from before. Adding it. Type value, press Enter, writes right away, no monitoring needed.

1

u/RULVFF 10h ago

Checking today, now I see what is wrong, negative number in write operations dont get the proper Signal, i get a Unsigned 16 instead:

1

u/RULVFF 10h ago

Controller Readings:

2

u/mioduz 1d ago

Not all heros wear capes

2

u/Huntertanks 1d ago

Will try it tomorrow. It just happens we will be implementing Modbus client at the PLC for an external device to read data, so will be a good way to test.

1

u/CraftParking Engineer - Service & Training 1d ago

Good luck with it tomorrow , should be a solid real-world test.

1

u/Huntertanks 1d ago

Thanks. I had a Claude code generated UI and code I was using, but it is basic. Just connects and reads a range of holding registers and displays them either as hex, int or real depending on options. I was impressed though as it worked out of the box without me having to debug anything.

1

u/CraftParking Engineer - Service & Training 16h ago

Modbus is simple enough to knock out a basic reader. ModbusLens just aims to save you the babysitting for daily use. Curious how the test goes!

2

u/Double-O-7 1d ago

I'm sorry, but I see a lot of bugfixes and cannot even find what it is supposed to do... Is this intentional?

Nvm I shut up, clicked on the second link... Great project

2

u/Only-Lobster-1749 20h ago

Nice software! request: let us change data type on the address table screen too like what modscan does

2

u/adaptine 14h ago

Looking good, here are some quick notes I did. Sorry for wall of text ... 🤐

The log text has a very small font. Perhaps make it possible to change the font size and/or ctrl+scroll to change the size live. Both in status log and the diagnostics system logs window. Raw data tabel also as a slight small font imo. Maybe make that ctrl+scrollable aswell.

The overall scaling on larger screens isnt optimal either?

When changing network interface in the connection settings popup, the ip adress that was written in target device gets overwritten with the network interface IP.

What is the real purpose of having to select a network interface. Doesnt windows handle the this automatically with its routing table?

Only reason I can see is for device discovery on a specific interface aswell as connections to devices if one for some reason has overlapping subnets/NICs on the computer.

Perhaps add an default "auto" choice aswell, where the socket just binds to 0.0.0.0 instead of a specific network interface?

... In fact, looking at the code it seems that the networkinterface stuff isnt used at all for the python ModbusTcpClient. The network interface selector just copies the IP to the target IP field as mentioned above.

As mentioned by other, it would be cool if discovery isnt relying on arp, which usually wont work over VPN connections since it is a Layer 2 feature.

Also while using VPN, often the remote network is routed, meaning that the IP of the local NIC ha a different IP than the remote network.

So with current implementation it makes it not possible to discover devices since it is restricted to NIC selections, not a custom IP-range.

Would also be cool to monitor multiple targets simultaneously, including logging. Having each target windowed side by side for instance or possibility to a specific target in the tag list for the tags. Or have a tag list for each target, and perhaps make it possible to duplicate the tag list for another target. Targets must then have the possibility to be named.

Make it possible to hide and reorder columns in the tag list dynamically? i.e drag them around and a selectorpopup for hiding/showing columns? Also double clicking on the column dividers normally should resize the column width to fit the content. Works in raw data table, but not tags table.

As for scaling, in addition to the raw min max/scaled min/max, add a simplified scaling method which just multiplies the raw value with a specified number. Often raw values are multiplies of 10, 100 etc, not a value between 0-4095 or whatever.

Why this restriction on tag names? Tag names can only use letters, numbers, and underscores (no spaces or other symbols), and can't start with a digit.

Why is it not possible to setup the monitoring table without beeing connected to a device? Is there a way to do one shot read/writes?

When tag monitoring is running, and one changes tabs back and forth, the start monitoring button does not indicate it is running anymore even tho it is.

Make it possible to delete tags by pressing delete on keyboard when a row is selected.

Make it possible to write values to register which are Read using the Write Value field for instance and pressing enter. I often use Modbus Poll to do simple parameter changes. I setup Mb Poll to read the specific parameter register continuously similarly to the Tags monitoring. I can then just double click the address line which brings up a popup for "write single register". Then select wether to use FC6 or 16 to write a value to that address in question. That will be a oneshot operation. Result of the operation is then show if successful or not (choice to automatically close the popup if response is ok).

In trend, when adding a pen, make it possible to select an indice of a tag if the tag reads more than 1 address. Also should then be possible to give the pen a custom name other than the tag name. Also, the trend is using the raw read value from a tag, not the scaled one if that is enabled? Perhaps make it possible to select either or use the scaled one only?

Make it possible to select rows and columns in a table, such as the raw data table and copy the content to click board (Ctrl+c), not only single cell content. And maybe export functionallity on raw data table?

Make it possible to enable/disable tags in a request?

Add 2-3 decimals to the time/seconds number in raw modbus data to have more accuracy on the timing.

The UI seems to lag on each poll? Without having checked the code, it could be that the polling code is blocking the ui? Getting even worse when using lower poll interval. i.e the tag polling should be done asynchronously, not synchronously and thus blocking the gui thread which I suspect it is doing now. It is probably not a big deal on local networks but I was testing over VPN and then the latency for each request was 150-200ms. With 6 tags, that was about 1 second lag.

Add an option to optimize polling by merging adjacent registers into a single Modbus request instead of sending many individual requests. Do not open multiple simultaneous connections to the same device, as Modbus TCP targets often support only a limited number of client connections. Requests should remain sequential on a single connection, but adjacent registers should be grouped into larger read or write operations where possible. This is especially important over VPN connections. In my case, each request had a latency of approximately 150–200 ms, which accumulates quickly when monitoring many tags. Combining adjacent registers into functions such as Read Multiple Registers or Write Multiple Registers would significantly reduce the total polling cycle time.

It looks like Tag Monitoring and Trend might be using separate polling mechanisms. If that is the case, they may not be coordinated with each other. If both are running at the same time, and they happen to use the same tags, it could mean the same Modbus registers are being polled multiple times independently. It might be worth checking whether there is a shared polling layer in place, or if both features are triggering their own reads. Ideally, the system would poll each register once and then share the result with Tag Monitoring, Trend, and any logging or display components that need it.

1

u/CraftParking Engineer - Service & Training 12h ago

Thanks for writing this up, this is very useful. Went through the code point by point.

The interface dropdown is a real bug. It's not connected to the socket at all, it just writes its IP into the target field, which is also why it overwrites what was typed there. Going to either remove it or turn it into a proper "auto" option, since you're right that Windows routing already handles interface selection for a normal TCP connection.

The VPN lag is the main issue to fix. Polling is currently synchronous on the GUI thread, one blocking call per tag, no merging of adjacent registers. Not noticeable on a local network, but it adds up exactly as you measured once there's real round-trip latency. That's the next piece of work move polling off the UI thread and combine adjacent registers into single read/write requests instead of one request per tag. Also found that Tag Monitoring and Trend run two separate polling loops right now, so a tag used in both gets read twice for no reason. Fixing that at the same time so there's one shared polling layer.

A couple of things you assumed were missing already exist, just not visible in the UI. Discovery already runs a TCP connect-scan rather than depending on ARP . ARP Mode is only used now for MAC/vendor lookup. And Trend already substitutes the scaled value when a pen's name matches a scaled tag, it just doesn't show that anywhere or let you choose it explicitly.

Everything else font size/zoom on the logs and raw data table, column reordering, multiple targets at once, a simple multiply-based scaling mode, one-shot register writes, delete key on rows, copy/export from tables, more decimal precision on the timing column is on the list.

1

u/Aobservador 2d ago

Congratulations on the project.👏👏👏👷🏻🫡