Files
eddys_snippets/modbus_params/Readme.md

276 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# modbus_params
**modbus_params** is a command-line tool and network server for interacting with Modbus RTU
devices. It uses a humanreadable **dictionary** that maps register addresses to mnemonic codes,
supports periodic dumping of selected parameters to a TSV file, and provides a TCP/UNIX socket
interface for remote access.
The project is hosted at:
[https://github.com/eddyem/eddys_snippets/tree/master/modbus_params](https://github.com/eddyem/eddys_snippets/tree/master/modbus_params)
## Features
- Read and write Modbus holding registers by **address** or **mnemonic code**.
- Define a dictionary (`code → register`) with optional comments and readonly flags.
- Dump selected registers periodically to a TSV file.
- Start a **TCP or UNIX socket server** that accepts commands to:
- Read/write registers (by code or address).
- List dictionary entries or aliases.
- Start/stop and rename the dump file.
- Define **aliases** macros that expand to other commands or expressions.
- Verbose logging with configurable level.
## Dependencies
- [libmodbus](https://libmodbus.org/) (Modbus RTU backend)
- [usefull_macros](https://github.com/eddyem/snippets_library) (argument parsing, socket helpers,
logging macros)
- POSIX threads (pthread)
- Standard C library
## Building
Clone the repository together with the required submodule:
```bash
git clone --depth=1 https://github.com/eddyem/eddys_snippets.git
cd eddys_snippets/modbus_params
```
Then compile with `make`:
```bash
make
```
The resulting executable is named `modbus_params`. You can copy this file wherever you want, e.g.
```bash
su -c "cp modbus_params /usr/local/bin
```
## Commandline usage
```
modbus_params [options]
```
### General options
| Option | Argument | Description |
|--------|-------------------|-------------|
| `-h`, `--help` | | Show help and exit |
| `-v`, `--verbose` | | Increase verbosity (can be used multiple times) |
| `-D`, `--dictionary` | *file* | Dictionary file (format: `code register value readonly`). *Required for most operations.* |
| `-a`, `--alias` | *file* | Aliases file (format: `name : command`). Optional. |
| `-s`, `--slave` | *ID* | Modbus slave ID (default: 1) |
| `-d`, `--device` | *path* | Serial device (default: `/dev/ttyUSB0`) |
| `-b`, `--baudrate` | *rate* | Baud rate (default: 9600) |
### Read/write operations
| Option | Description |
|--------|-------------|
| `-r`, `--readr` *addr* | Read register by address (multiply: `-r 100 -r 200`) |
| `-R`, `--readc` *code* | Read register by dictionary code (multiply) |
| `-w`, `--writer` *addr=val* | Write value to register by address (multiply) |
| `-W`, `--writec` *code=val* | Write value to register by dictionary code (multiply) |
| `-O`, `--outdic` *file* | Read **all** registers listed in the dictionary and save them (with current values) into a new dictionary file |
### Dumping (periodic TSV output)
| Option | Description |
|--------|-------------|
| `-k`, `--dumpkey` *code* | Add a dictionary key to the dump list (multiply) |
| `-o`, `--outfile` *file* | TSV file for the dump |
| `-t`, `--dumptime` *seconds* | Dump interval (default: 0.1 s) |
### Server mode
| Option | Description |
|--------|-------------|
| `-N`, `--node` *spec* | Start a server. For TCP: IP address or hostname with port after ':' (e.g. `:1212` or `localhost:1212`). For UNIX socket: a path (e.g. `/tmp/mb_sock`). |
| `-U`, `--unixsock` | Use UNIX domain socket instead of TCP (requires `-N` with a path) |
If `-N` is given, the program runs as a server **after** executing any immediate read/write/dump
operations. The server stays alive until interrupted (Ctrl+C).
## File formats
### Dictionary file
Each line defines one register. Empty lines and text after `#` are ignored.
Format:
```
<code> <register> <value> <readonly>
```
- `code` mnemonic string (no spaces).
- `register` 16bit Modbus address (decimal).
- `value` initial/default value (16bit, not used by the tool except for documentation).
- `readonly` `0` (writable) or `1` (readonly).
Example (from `dictionary.dic`):
```
F00.00 61440 1 0 # Motor management (V/F)
F00.01 61441 0 0 # Selecting the START command task
...
A02.01 41473 0 1 # Output frequency (read-only)
```
### Aliases file
Aliases provide a way to define shortcuts or composite commands.
Format:
```
name : expression # optional comment
```
The `expression` is a string that will be interpreted as a command (register read/write, another
alias, or a builtin server command). Aliases are resolved recursively.
Example:
```
stop : F00.00=0 # stop motor
start : F00.00=1 # start motor
freq : F00.11 # read current frequency
```
When the server receives `stop`, it writes `0` to register mapped to `F00.00`.
### Dump file (TSV)
When dumping is active, the tool writes a tab-separated file with a header line:
```
# time,s <code1> <code2> ...
```
Each subsequent line contains a timestamp (seconds since dump start) followed by the current value
of each registered key. Unreadable registers are shown as `----`.
Example:
```
# time,s F00.00 F00.11
0.000 1 5000
0.102 1 5000
```
## Server protocol
The server listens for plain text commands, terminated by newline (`\n`).
Each command is a keyvalue pair: `key[=value]`.
- **Getter** just the key → returns current value.
- **Setter** `key=value` → writes the value (if the register is writable).
### Builtin keys (handlers)
| Key | Description |
|-----|-------------|
| `list` | As a getter: prints the whole dictionary (one line per entry). As a setter (e.g. `list=F00.01`): prints details of the given code or register address. |
| `alias` | Getter: prints all aliases. Setter (e.g. `alias=myalias`): prints that specific alias. |
| `newdump` | Getter: returns current dump file name. Setter (e.g. `newdump=/path/file.dump`): closes current dump file (if any), opens a new one and starts dumping. |
| `clodump` | Stops the dump thread and closes the dump file. No value expected. |
### Default handler (register access)
If the key is **not** a builtin handler, the program tries to interpret it as:
- A **register address** (decimal integer) → find the corresponding dictionary entry.
- A **dictionary code** → find the entry.
- An **alias** → recursively expand and execute.
Then:
- **Getter** (only key) → reads the register and replies `key=value`.
- **Setter** (`key=value`) → writes the value to the register (if not readonly) and replies `OK`.
### Examples (using `netcat`, `telnet` or `socat`)
**TCP server** (assuming `-N :5020`):
```
$ nc localhost 5020
F00.00
F00.00=1
F00.00=0
OK
list=F00.01
F00.01 61441 0 0 # Selecting the START command task
newdump=/tmp/motor.dump
OK
clodump
OK
```
**UNIX socket**:
```
$ socat - UNIX-CONNECT:/tmp/mb_sock
F00.11
F00.11=5000
```
## Examples
### 1. Simple read by address
```bash
modbus_params -D dictionary.txt -s 1 -d /dev/ttyUSB0 -b 9600 -r 61440
```
### 2. Write a value using a code
```bash
modbus_params -D dictionary.txt -W "F00.00=1"
```
### 3. Dump two registers every 0.5 seconds to `data.tsv`
```bash
modbus_params -D dictionary.txt -k F00.00 -k F00.11 -o data.tsv -t 0.5
```
(No server started the program will dump until interrupted.)
### 4. Start a local server with dumping already active
```bash
modbus_params -D dictionary.txt -k F00.00 -o /tmp/dump.csv -t 1 -N localhost:5020
```
Now you can connect to port 5020 and use `newdump` to change the output file, or `clodump` to stop.
### 5. Read all dictionary registers and save current values
```bash
modbus_params -D dictionary.dic -O current_state.dic
```
## Signals
- `SIGINT` (Ctrl+C), `SIGTERM`, `SIGQUIT` gracefully close Modbus, dump files, and exit.
- `SIGHUP`, `SIGTSTP` ignored (no action).
## Notes
- The dictionary is **shared** between the main program and the server thread. Do not modify the
dictionary file while the program is running.
- The dump thread uses a separate timer; if a read operation hangs, the dump may stall.
- When using UNIX sockets, the path can be prefixed with `\0` or `@` (for abstract sockets).
The program accepts plain paths for filesystem sockets.
- The `usefull_macros` library provides the `sl_dtime()` highresolution timer and threadsafe
logging macros.
## License
Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
GNU General Public License v3 or later.
Full text of the GPLv3 is available at <https://www.gnu.org/licenses/gpl-3.0.html>.