Skip to main content

ModScanner

A safety-first toolkit for discovering, inspecting, scanning, and interacting with Modbus TCP devices.

ModScanner provides a command-line interface and Python API for working with Modbus devices. It supports network discovery, adaptive scanning of Modbus data areas, and controlled write operations for coils and holding registers.

ModScanner helps engineers inspect Modbus devices without immediately falling back to slow, one-register-at-a-time scanning. It reads registers in blocks and adaptively splits rejected blocks to identify valid and invalid addresses.

Project status: Early development. The API and CLI may change between releases.

Features

  • Python API and CLI
  • Modbus TCP support
  • Network discovery of Modbus devices
  • Configurable device ID, port, timeout, address range, and block size
  • Adaptive block splitting when a device rejects a register range
  • Seperation of protocol errors, transport errors, and invalid responses
  • Terminal output using Rich
  • Typed Python data module
  • Transport abstraction that is built untop of PyModbus
  • Unit tested scanning and writing behavior
  • Scan all devices on your network that uses Modbus
  • Show the slave ID of the Modbus devices
  • Type Python models

Supported Modbus Operations

Function Description Supported
FC01 Read Coils ✅
FC02 Read Discrete Inputs ✅
FC03 Read Holding Registers ✅
FC04 Read Input Registers ✅
FC05 Write Single Coil ✅
FC06 Write Single Holding Register ✅
FC15 Write Multiple Coils ✅
FC16 Write Multiple Holding Registers ✅

Installation

Install ModScanner from PyPI using either of two(2) methods the below:

  1. Using pip:
pip install modscanner
  1. Using uv:
uv add modscanner

Verify the installation using:

modscanner --help
or
modscanner --h
or 
modscanner version

The above help shows the commands available, while the version shows the current version you have installd on your machine

Quick start

There are different ways to use the library;

Command-Line Usage

ModScanner organizes commands into scanning, writing, and network-discovery operations.

Run:

modscanner --help

to see available commands.

1. Scan Modbus Data

Display available scan commands:

modscanner scan --help

2. Read Coils - FC01

modscanner scan coils \
    --host 192.168.1.10 \
    --device-id 1 \
    --start 0 \
    --count 8

3. Read Discrete Inputs - FC02

modscanner scan coils \
    --host 192.168.1.10 \
    --device-id 1 \
    --start 0 \
    --count 8

3. Read Holding Registers - FC03

modscanner scan holding \
    --host 192.168.1.10 \
    --device-id 1 \
    --start 0 \
    --count 20

4. Read Input Registers - FC04

modscanner scan input \
    --host 192.168.1.10 \
    --device-id 1 \
    --start 0 \
    --count 20

TCP Port

modscanner uses the standard Modbus TCP port 502 by default.

For examples:

modscanner scan holding \
    --host 192.168.1.10 \
    --start 0 \
    --count 20

To use another port:

modscanner scan holding \
    --host 192.168.1.10 \
    --port 8196 \
    --start 0 \
    --count 20

Device ID

The default Modbus device ID is 1.

Specify another device ID with:

modscanner scan holding \
    --host 192.168.1.10 \
    --device-id 5 \
    --start 0 \
    --count 20

Block Size and Timeout

The scan behavior can be configured:

modscanner scan holding \
    --host 192.168.1.10 \
    --device-id 1 \
    --start 0 \
    --count 100 \
    --block-size 50 \
    --timeout 2

Writing

Displat the write commands:

modscanner write --help

Write a Single Coil - FC05

The CLI uses:

  • 1 = ON | True
  • 2 = OFF | False

Turn a coil ON:

modscanner write coil \
    --host 192.168.1.10 \
    --device-id 1 \
    --address 0 \
    --value 1

Turn if OFF:

modscanner write coil \
    --host 192.168.1.10 \
    --device-id 1 \
    --address 0 \
    --value 1

Write Multiple Coils -FC15

modscanner write coils \
    --host 192.168.1.10 \
    --device-id 1 \
    --address 0 \
    --values 1,0,1,0

Write a Single Holding Register - FC06

modscanner write register \
    --host 192.168.1.10 \
    --device-id 1 \
    --address 108 \
    --value 100

Write Multiple Holding Registers - FC16

modscanner write registers \
    --host 192.168.1.10 \
    --device-id 1 \
    --address 200 \
    --values 10,20,30,40

Network Discovery

modscanner can search a subnet for devices accepting Modbus TCP connections.

modscanner scan-network \
    --subnet 192.168.2.0/24

Port 502 is used by default.

To scan another Modbus TCP port:

modscanner scan-network \
    --subnet 192.168.2.0/24 \
    --port 8196

Vendor Information

modscanner cnan optionally attempt to identify devices vendors using MAC ADDRESS information.

modscanner scan-network \
    --subnet 192.168.2.0/24 \
    --show-vendor


PYTHON API

modscanner can also be used directly as a Python library.

Create a Target

from modscanner.models import TcpTarget

target = TcpTarget(
    host="192.168.1.10",
    device_id=1,
)

Port 502 is used automatically.

TO use another port:

target = TcpTarget(
    host="192.168.1.10",
    port=8196,
    device_id=1,
)

Create a Transport

from modscanner.transports.pymodbus_transport import PymodbusTcpTransport

transport = PymodbusTcpTransport(target)

Reading with Python

from modscanner.models import RegisterArea, ScanPlan
from modscanner.scanner import Scanner

scanner = Scanner(
    transport,
    target,
)

plan = ScanPlan(
    area=RegisterArea.HOLDING_REGISTER,
    start=0,
    count=20,
)

transport.connect()

try:
    report = scanner.scan(plan)

    for result in report.results:
        print(
            result.address,
            result.value,
            result.status,
        )

finally:
    transport.close()

Scanner methods are also available directly:

scanner.scan_coils(plan)

scanner.scan_discrete_inputs(plan)

scanner.scan_holding_registers(plan)

scanner.scan_input_registers(plan)

Writing with Python

The Python API uses native Python booleans for coil values.

from modscanner.writer import Writer

writer = Writer(
    transport,
    target,
)

Write One Coil

transport.connect()

try:
    result = writer.write_coil(
        address=0,
        value=True,
    )

finally:
    transport.close()

Write Multiple Coils

result = writer.write_coils(
    address=0,
    values=(
        True,
        False,
        True,
        False,
    ),
)

Write One Holding Register

result = writer.write_register(
    address=108,
    value=100,
)

Write Multiple Holding Registers

result = writer.write_registers(
    address=200,
    values=(
        10,
        20,
        30,
        40,
    ),
)


Examples

Examples are available in the examples directory.

If the shell scripts were created manually, make them executable:

chmod +x examples/cli/*.sh

THen run an example:

./examples/cli/scan_holding_registers.sh

Python examples can be run using:

python examples/python/scan_holding_registers.py

See examples/README.md for additional examples.



Addressing

modscanner uses zero-based Modbus protocol addresses.

Example, a device manual may describe first holding register as:

  • 40001

wihile the corresponding Modbus protocol address may be:

  • 0

Always confirm the addressing conventions used by the device manufacturer before reading or writing registers.



Adaptive Scanning

Some Modbus devices reject an entire request when only part of the requested address range is invalid.

Example:

  • Request
    • 0 -------- 19
    • Valid device registers:
      • 8 9

A normal block request may fail completely because addresses outside the valid range are unsupported.

ModScanner handles this by:

  • Reading a block of addresses.
  • Accepting the block when the response succeeds.
  • Splitting the block when the device returns a protocol-level error.
  • Retrying smaller blocks.
  • Continuing until valid and invalid addresses can be identified.
  • Avoiding recursive retries when the failure is caused by the network or transport layer.

This allows ModScanner to inspect sparse register maps without immediately falling back to one request per address.



Error Handling

modscanner distinguishes between several classes of failues.

Protocol Errors

The Modbus device responded but rejected the request.

Examples include:

  • Illegal Function
  • Illegal Data Address
  • Illegal Data Value

Transport Errors

Communication with the device failed.

Examples include:

  • Connection refused
  • Connection timeout
  • Network unreachable

Invalid responses

A response was received but did not contain the data expected by modscanner.

Keeping these failure categories seperate allows higher-level tools to respond appropriately.

Safety

Modbus write operations can control or reconfigure physical equipments like VFDs, inverters, sensors, etc.

Before performing a write:

  • confirm the target IP address
  • confirm the TCP port
  • confirm the device ID
  • confirm the address
  • confirm that the address is writable
  • confirm the permitted value range
  • understand the effect of changing the value

Do not experiment with unknown writable addresses on production equipment.

Read operations should also be performed responsibly. Excessive scanning may place unnecessary load on embedded or industrial devices.

Current limitations

modscanner is still under active development.

Current limitations include

  • Modbus RTU is not yet supported
  • automatic device ID discovery is not yet implemented
  • JSON/CSV/YAML export is not yet implemented
  • register data-type decoding is not yet implemented
  • byte-order and word-order conversion are not yet implemented
  • device profiles are not yet implemented
  • continuous register monitoring is not yet implemented
  • writable-register allowlists are not yet implemented
  • write readback verification is not yet implemented
  • write audit logging is not yet implemented

All the above are planned for future releasesa

Development

Clone the repository and install the development environment:

git clone https://github.com/davisonyeas/modscanner.git
cd modscanner
uv sync

Activate the environment:

source .venv/bin/activate

Run the unit tests:

pytest tests/unit -v

Run all tests:

pytest

Run linting:

ruff check .

Run type checking:

mypy src

Documentation

Additional documentation and runnable examples are abailable under:

Contibuting

Contributions are welcome.

See CONTRIBUTING.md for contribution guidelines.

Package PyPI

modscanner is available on PyPI:

Visit:

https://pypi.org/project/modscanner/

Security

Do not use ModScanner to perform unauthorized scanning or testing.

License

See LICENSE

Author

Developed by Davis Onyeoguzoro

Metadata

Release files for modscanner 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for modscanner 0.2.0
File Size Uploaded
modscanner-0.2.0.tar.gz 22.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for modscanner 0.2.0
File Interpreter ABI Platform
modscanner-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 43.5 kB

Release files / modscanner-0.2.0.tar.gz

Download URL modscanner-0.2.0.tar.gz
Size 22.4 kB
Tags Source
SHA-256 checksum
How to use checksums
8718877b4dc9098a6d6fe253346ec1d03e003e9369ca092111eac9b001d3b8c6
BLAKE2b-256 checksum
How to use checksums
f09e2ddf16a81b42351d301fa8498d38c965f1909debf4164362d7a7b7ae33b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / modscanner-0.2.0-py3-none-any.whl

Download URL modscanner-0.2.0-py3-none-any.whl
Size 21.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7fb21a47404f6340ad87cd36391ea2930498748c76340b48ce47d7602923b925
BLAKE2b-256 checksum
How to use checksums
fabd643b034460a0e3364a85723777e632aa9bd7489404be260806326098aaea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page