Metadata-Version: 2.4
Name: bacsys-pymod
Version: 0.1.0
Summary: Production-grade Modbus TCP/RTU driver for Python (import name: pymod).
Project-URL: Homepage, https://github.com/bacsys/pymod
Project-URL: Documentation, https://bacsys-pymod.readthedocs.io/
Project-URL: Repository, https://github.com/bacsys/pymod
Project-URL: Issues, https://github.com/bacsys/pymod/issues
Author-email: Bacsys <richardson20june@gmail.com>
License: MIT
License-File: LICENSE
Keywords: bacsys,industrial,iot,modbus,rtu,scada,tcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Hardware
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Classifier: Topic :: System :: Networking
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: pyserial>=3.5
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: types-pyserial>=3.5; extra == 'dev'
Provides-Extra: docs
Requires-Dist: myst-parser>=2.0; extra == 'docs'
Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'docs'
Requires-Dist: sphinx>=7.0; extra == 'docs'
Provides-Extra: structured-logging
Requires-Dist: structlog>=24.1; extra == 'structured-logging'
Description-Content-Type: text/markdown

# bacsys-pymod

Production-grade **Modbus TCP / RTU driver** for Python. Library + one-shot CLI.

> *Distribution name `bacsys-pymod`, import name `pymod`.*

```python
import pymod

with pymod.Client.tcp("10.0.0.5", 502, unit_id=1) as c:
    results = c.read([
        pymod.Holding(start=0, count=10, dtype="float32"),
        pymod.Coil(start=0, count=16),
        pymod.Holding(start=0, count=2, dtype="bit", bit_index=8),
    ])
    for r in results:
        print(r.values if r.ok else f"failed: {r.error}")
```

## Highlights

- **Modbus TCP** with concurrent connections and request pipelining (TID demultiplexing).
- **Modbus RTU** over a serial port — sequentialized internally, safe to share across callers.
- **Modbus RTU over TCP** for serial-to-Ethernet gateways (Moxa-style).
- **All standard function codes** (FC01/02/03/04/05/06/15/16) plus a registration hook for custom FCs.
- **Heterogeneous batch reads** — pass a list of typed items (Holding, Input, Coil, Discrete) and the planner coalesces adjacent same-area ranges into the minimum number of Modbus requests.
- **Typed values** — `int16`, `uint16`, `int32`, `uint32`, `float32`, `int64`, `uint64`, `float64`, plus bit extraction from registers.
- **All four PLC byte/word orderings** (ABCD / CDAB / BADC / DCBA) configurable per item.
- **Sync facade** for Flask / scripts / REPL — `pymod.Client.tcp(...)` is a normal blocking object backed by an internal asyncio loop.
- **Server mode** — callback-based, no internal datastore. Bring your own data, use it for protocol bridging (e.g. Modbus → BACnet / MQTT).
- **`pymod` CLI** — `read`, `write`, `scan`, `serve`, plus an interactive `--guide` wizard.
- Python 3.11+, pure Python (one runtime dep: `pyserial`), `mypy --strict` clean.

## Install

```bash
pip install bacsys-pymod
```

Once installed:

```python
import pymod
print(pymod.__version__)
```

…and the `pymod` CLI is on PATH.

## Quick examples

### TCP — sync client (Flask, scripts, REPL)

```python
import pymod

client = pymod.Client.tcp("127.0.0.1", 502, unit_id=1, timeout_s=0.5)
client.connect()
try:
    results = client.read([pymod.Holding(start=0, count=10, dtype="int16")])
    print(results[0].values)
finally:
    client.close()
```

### TCP — async client

```python
import asyncio, pymod

async def main():
    async with pymod.AsyncClient.tcp("127.0.0.1", 502, unit_id=1) as c:
        results = await c.read([pymod.Holding(start=0, count=10, dtype="int16")])
        print(results[0].values)

asyncio.run(main())
```

### Serial RTU

```python
client = pymod.Client.rtu("COM5", baudrate=9600, parity="N", unit_id=1)
```

…the rest of the API is identical to the TCP client.

### Modbus TCP server (callback-based)

```python
import asyncio, pymod

async def on_read(area, address, count):
    return store.read(area, address, count)

async def on_write(area, address, values):
    store.write(area, address, values)

async def main():
    async with pymod.Server(host="0.0.0.0", port=502,
                            on_read=on_read, on_write=on_write):
        await asyncio.Future()

asyncio.run(main())
```

The server has no internal datastore — the host application owns the data, the server is just a Modbus protocol terminator. Perfect for bridging Modbus to other industrial protocols.

### CLI

```
pymod read  --host 127.0.0.1 --port 502 --area holding --start 0 --count 10 --dtype int16
pymod write --host 127.0.0.1 --port 502 --area holding --start 0 --values 1,2,3 --dtype uint16
pymod scan  --host 127.0.0.1 --port 502
pymod serve --host 0.0.0.0   --port 5020 --holding 0=100,1=200
pymod --guide       # interactive wizard
```

Each subcommand has detailed `--help`.

## Documentation

Full docs at <https://bacsys-pymod.readthedocs.io/>.

## License

MIT. See [LICENSE](LICENSE).
