Compare commits

...

55 Commits

Author SHA1 Message Date
Victor Antonovich 2c462b7560 Tag Docker images with `latest` tag 2024-03-28 00:23:37 +03:00
Victor Antonovich ac927aa19a Update Docker publish workflow 2024-03-27 23:55:11 +03:00
Victor Antonovich 91f8e35eae mbusd 0.5.2 2024-03-27 18:44:59 +03:00
Victor Antonovich 0cf2cb0c56 Add Docker info to README 2024-03-27 18:25:25 +03:00
Victor Antonovich 9cee2351f4 Add docker image publish github workflow 2024-03-27 16:38:59 +03:00
JP Meijers a5239439e3
Change Dockerfile to install required packages for linux serial.h (fixes #103) 2024-02-21 17:15:17 +03:00
Victor Antonovich 5b59f5b92f Fix building on older gcc versions (#100) 2023-12-27 13:30:47 +03:00
Victor Antonovich 3bed375c44 Refactor log file name command line option parsing 2023-12-25 17:58:48 +03:00
Victor Antonovich 2b32d097d2 Move string trim functions to util.c 2023-12-25 17:55:55 +03:00
Victor Antonovich 8591a2f1c1 Move CRC calculation function to modbus.c 2023-12-25 16:34:09 +03:00
Victor Antonovich d6e6b74a53 Default log file name changed to /var/log/mbusd.log 2023-12-25 13:00:12 +03:00
kpr0th 7475bdf141
Logging fix and enhancement (#90)
* Logging fix and enhancement

Corrected issue where loglevel in mbusd.conf wasn't parsed correctly. Added support for logfile in mbusd.conf.  Updated README and mbusd..conf.example to true-up with "-h" output and new logging options.

* Updated README

Sync'd up verbiage in the usage details with the usage summary.

* Additional adjustments to sync up README w/ -h output
2023-12-25 12:52:43 +03:00
Victor Antonovich 68f988051c Rename `tty_(set|clr)_rts` to `tty_set_(tx|rx)` 2023-11-27 16:48:47 +03:00
Victor Antonovich b03d50b58c Improve documentation 2023-11-27 16:21:31 +03:00
Victor Antonovich 20c87df994 Add `rts_1` config value as an alias for `rts` 2023-11-27 15:43:34 +03:00
Cían Hughes 4ee7e8a834
Add new flag (-r) to support inverted RTS flow control\n\nThis is useful if you have a MAX485 connected to a Raspberry PI where you pull want RST low to transmit (#98) 2023-11-27 15:14:38 +03:00
Victor Antonovich b07681842f Add pyserial as a dependency for github workflow 2023-09-29 18:16:45 +03:00
Victor Antonovich b464ec9676 Update version to 0.5.2 and year to 2023 2023-09-29 18:09:00 +03:00
Victor Antonovich 6517dea57f Make use of Linux RS-485 support #97 2023-09-29 18:04:09 +03:00
Victor Antonovich 0f7a8c50fb Update .gitignore 2023-09-29 17:20:23 +03:00
Victor Antonovich 18fb6d086e Fix tests for changes in pymodbus API 2023-09-29 17:19:51 +03:00
Victor Antonovich 4e5c7e3781 mbusd 0.5.1 2022-08-18 20:35:49 +03:00
vicencb 874ce63806
Do not segfault when closing last connection (#83) 2022-04-06 17:19:08 +03:00
Victor Antonovich 06ccb29384 Fix crash due to missing logw() argument (fixes #84) 2022-04-06 16:26:26 +03:00
Victor Antonovich 7c268600b6 Add support for more complex serial port device names (fixes #81) 2022-02-17 13:12:04 +03:00
Jakob Schlyter 55889a7292
Add simple Dockerfile for running mbusd containerized (#79)
* init

* simplify

* explicit gcc not needed

* do not clone repository, copy source from cwd (or we cannot build releases)
2021-11-15 14:04:04 +03:00
Victor Antonovich cb9576a5a0 Remove travis.yml 2021-11-15 13:09:24 +03:00
Victor Antonovich 16e68947ce Move to Github Actions build status badge 2021-11-15 13:08:32 +03:00
Victor Antonovich bf98aa6382 Install Twisted as test dependency into github build workflow 2021-11-15 13:01:40 +03:00
Victor Antonovich dd71375628 Install socat as test dependency into github build workflow 2021-11-15 12:53:52 +03:00
Victor Antonovich af0534d18f Install python and test dependencies into github build workflow 2021-11-15 12:31:10 +03:00
Victor Antonovich 868d605f5d
Add Github Actions build file 2021-11-12 18:50:16 +03:00
Victor Antonovich 1efb812013 Fix for "Resource temporarily unavailable" error in tty read() (#78) 2021-11-12 18:04:31 +03:00
Victor Antonovich 4cd68c1bb8 Update version to 0.5.1 and year to 2021 2021-11-12 12:20:48 +03:00
Victor Antonovich a2272c5793 Parse replyonbroadcast config option value #75 2021-07-06 16:37:36 +03:00
Victor Antonovich 6a95a732d4 Merge pull request #75 from dgoo2308/feature-reply-on-broadcast 2021-07-06 15:15:36 +03:00
Danny b22ffa0856 feature-reply-on-broadcast 2021-06-19 12:37:55 +07:00
Victor Antonovich 432b7a1a8f mbusd 0.5.0 2020-11-28 13:47:32 +04:00
Victor Antonovich 252aa45e5d Improve serial port config handling and logging 2020-08-13 20:43:03 +04:00
Victor Antonovich 461ce8baa0 Do not wait for a reply for RTU broadcast address #61 2020-08-13 17:39:30 +04:00
Victor Antonovich ad58141b87 Fix division by zero error for default serial port speed 2020-08-13 11:52:18 +04:00
Victor Antonovich c6301d4250
Merge pull request #63 from KrystianD/feat-baudrate
Show an error when trying to use unsupported baudrate
2020-08-13 11:23:27 +04:00
Victor Antonovich 3742d8d8c5
Merge pull request #62 from KrystianD/master
Add more baudrates
Fixes #60
2020-08-13 11:17:37 +04:00
Krystian Dużyński 32344e5f94 Fix log message (baudrate to speed) 2020-08-12 00:35:02 +02:00
Krystian Dużyński d24b257c13 Make use of baudrate 0 as indicator of default baudrate, show error when trying to use unsupported baudrate 2020-08-12 00:28:23 +02:00
Krystian Dużyński 965ff443c8 Remove unused resppause member 2020-08-12 00:27:48 +02:00
Krystian Dużyński 4bf9d94713 Add more baudrates 2020-08-12 00:08:58 +02:00
Victor Antonovich 5eb74fa568 Fix GCC C99 compatibility error 2020-05-22 16:56:31 +04:00
Victor Antonovich f5becef211 Take into account tty parity bit in the timing calculations (#59) 2020-05-22 15:47:50 +04:00
Victor Antonovich 0c1dcb727d Add online network requirements to the systemd service file (fix #58) 2020-05-21 13:59:53 +04:00
Victor Antonovich 88916fe82d Add IPv6 support (#53) 2019-07-17 17:04:22 +04:00
Victor Antonovich 2aa063db79 Add command-line and config options to set an TCP socket address (#53)
Still no IPv6 support though.
2019-07-16 11:14:37 +04:00
Victor Antonovich 2465860496 Add 'enhancement' label to stale bot exemptLabels 2019-07-08 14:59:51 +04:00
Victor Antonovich 8e266fc5e9 Update working version to 0.4.1 2019-07-08 14:56:36 +04:00
Victor Antonovich 2282376417 Add stale bot config 2019-07-08 14:55:59 +04:00
33 changed files with 978 additions and 460 deletions

17
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
- 'Good first issue'
- enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

44
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
BUILD_TYPE: Release
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Install test dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twisted pyserial pymodbus
sudo apt-get install -y socat
- name: Run tests
working-directory: ${{github.workspace}}/build
run: make test
- name: Show failed test log
if: ${{ failure() }}
run: cat ${{github.workspace}}/build/Testing/Temporary/LastTest.log

63
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: docker publish
on:
push:
tags: [ 'v[0-9].[0-9]+.[0-9]+' ]
workflow_run:
workflows: [ 'build' ]
types: [ completed ]
branches: [ 'master' ]
env:
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ env.IMAGE_NAME }}
cache-to: type=gha,mode=max,scope=${{ env.IMAGE_NAME }}

26
.gitignore vendored
View File

@ -1,25 +1,11 @@
/autom4te.cache
/config.cache
/config.h
/config.status
/.settings/
/src/.deps/
/src/*.o
/src/mbusd
/doc/mbusd.8
Makefile
/.settings
*.log
*.in~
systemd-units/mbusd@.service
*.conf
*.pyc
CMakeCache.txt
CMakeFiles
CPackConfig.cmake
CPackSourceConfig.cmake
cmake_install.cmake
.idea
*~
/.idea
/.vscode
.project
.cproject
build
.pydevproject
/build
build.sh

View File

@ -1,18 +0,0 @@
sudo: required
dist: trusty
language: c
compiler:
- gcc
- clang
before_install:
- sudo apt-get update -qq
script:
- mkdir build
- cd build
- cmake ..
- make

View File

@ -1,4 +1,31 @@
# Change Log
# Changelog
## [0.5.2] - 2024-03-27
- Make use of Linux RS-485 support (#97).
- Add flag `-r` to support inverted RTS flow control(#98).
- Logging fixes and enhancements (#90).
- Default log file name changed to `/var/log/mbusd.log`.
- Fix building on older gcc versions (#100).
- Add docker image publish github workflow.
## [0.5.1] - 2022-08-18
- Add 'reply on broadcast' feature (#75).
- Fix for "Resource temporarily unavailable" error in tty read() (#78).
- Add simple Dockerfile for running mbusd containerized (#79).
- Add support for more complex serial port device names (#81).
- Do not segfault when closing last connection (#83).
- Fix crash due to missing logw() argument (#84).
## [0.5.0] - 2020-11-28
### Added
- Command-line and config options to set an TCP socket address (#53).
- IPv6 support (#53).
- Support for RTU broadcast messages (#61).
- More baudrates (#62).
### Fixed
- Added online network requirements to the systemd service file (#58).
- Take into account tty parity bit in the timing calculations (#59).
- Show an error when trying to use unsupported baudrate (#63).
## [0.4.0] - 2019-07-08
### Added
@ -61,7 +88,11 @@
## 0.1.1 - 2003-09-13
### Initial release
[0.5.2]: https://github.com/3cky/mbusd/compare/v0.5.1...v0.5.2
[0.5.1]: https://github.com/3cky/mbusd/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/3cky/mbusd/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/3cky/mbusd/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/3cky/mbusd/compare/v0.2.3...v0.3.0
[0.2.3]: https://github.com/3cky/mbusd/compare/v0.2.2...v0.2.3
[0.2.2]: https://github.com/3cky/mbusd/compare/v0.2.1...v0.2.2
[0.2.2]: https://github.com/3cky/mbusd/compare/v0.2.1...v0.2.2

View File

@ -1,10 +1,11 @@
cmake_minimum_required(VERSION 3.2)
project(mbusd VERSION 0.4.0)
project(mbusd VERSION 0.5.2)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/extern_GPL)
include(CheckFunctionExists)
include(CheckLibraryExists)
include(CheckSymbolExists)
include(GNUInstallDirs)
include(FindUnixCommands)
include(FindSystemd)
@ -41,6 +42,10 @@ check_function_exists(cfsetispeed HAVE_CFSETISPEED)
if(HAVE_CFSETSPEED AND HAVE_CFSETISPEED)
add_definitions(-DHAVE_CFSETSPEED)
endif()
check_symbol_exists(TIOCSRS485 sys/ioctl.h HAVE_TIOCRS485)
if(HAVE_TIOCRS485)
add_definitions(-DHAVE_TIOCRS485)
endif()
check_function_exists(time HAVE_TIME)
check_function_exists(localtime HAVE_LOCALTIME)
if(HAVE_TIME AND HAVE_LOCALTIME)
@ -65,10 +70,10 @@ set(mbusd_SOURCES
src/conn.c
src/queue.c
src/modbus.c
src/crc16.c
src/state.c
src/sig.c
src/sock.c
src/util.c
)
add_executable(mbusd ${mbusd_SOURCES})
install(TARGETS mbusd DESTINATION bin)

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM --platform=$BUILDPLATFORM alpine:latest AS build
RUN apk add --no-cache alpine-sdk cmake linux-headers
COPY . /mbusd
WORKDIR /mbusd/build
RUN cmake -DCMAKE_INSTALL_PREFIX=/usr .. && make && make install
FROM alpine:latest AS scratch
ENV QEMU_EXECVE=1
COPY --from=build /usr/bin/mbusd /usr/bin/mbusd
ENTRYPOINT ["/usr/bin/mbusd", "-d", "-L", "-", "-c", "/etc/mbusd.conf"]

View File

@ -1,4 +1,4 @@
Copyright (c) 2002-2003, 2013-2019 Victor Antonovich (v.antonovich@gmail.com)
Copyright (c) 2002-2003, 2013-2024 Victor Antonovich (v.antonovich@gmail.com)
Copyright (c) 2011 Andrew Denysenko <nitr0@seti.kr.ua>
All rights reserved.

View File

@ -1,7 +1,7 @@
About mbusd
===========
[![Build Status](https://travis-ci.org/3cky/mbusd.svg?branch=master)](https://travis-ci.org/3cky/mbusd)
[![mbusd](https://github.com/3cky/mbusd/actions/workflows/build.yml/badge.svg)](https://github.com/3cky/mbusd/actions/workflows/build.yml)
**mbusd** is open-source [Modbus TCP to Modbus RTU (RS-232/485)](https://en.wikipedia.org/wiki/Modbus)
gateway. It presents a network of RTU slaves as single TCP slave.
@ -52,19 +52,21 @@ can be altered in many ways, e.g. by using the following tools in the `build` di
Usage:
------
mbusd [-h] [-d] [-L logfile] [-v level] [-c cfgfile] [-p device] [-s speed] [-m mode]
[-t] [-y file] [-Y file] [-P port] [-C maxconn] [-N retries]
[-R pause] [-W wait] [-T timeout]
mbusd [-h] [-d] [-L logfile] [-v level] [-c cfgfile]
[-p device] [-s speed] [-m mode] [-S]
[-t] [-r] [-y sysfsfile] [-Y sysfsfile]
[-A address] [-P port] [-C maxconn] [-N retries]
[-R pause] [-W wait] [-T timeout] [-b]
-h Usage help.
-d Instruct mbusd not to fork itself (non-daemonize).
-L logfile
Specifies log file name ('-' for logging to STDOUT only, default is /var/log/mbusd.log).
Specifies log file name ('-' for logging to STDOUT only, relative path or bare filename
will be stored at /var/log, default is /var/log/mbusd.log).
-v level
Specifies log verbosity level (0 for errors only, 1 for warnings
and 2 for also information messages.) If mbusd was compiled in debug mode,
valid log levels are up to 9, where log levels above 2 forces
logging of information about additional internal events.
Specifies log verbosity level (0 for errors only, 1 for warnings and 2 for informational
messages also). If mbusd was compiled in debug mode, valid log levels are up to 9,
where log levels above 2 adds logging of information about additional internal events.
-c cfgfile
Read configuration from cfgfile.
-p device
@ -73,25 +75,31 @@ Usage:
Specifies serial port speed.
-m mode
Specifies serial port mode (like 8N1).
-t Enable RTS RS-485 data direction control (if not disabled while compile).
-y file
Enable RS-485 direction data direction control by writing '1' to file
for transmitter enable and '0' to file for transmitter disable
-Y file
Enable RS-485 direction data direction control by writing '0' to file
for transmitter enable and '1' to file for transmitter disable
-S Enable RS-485 support for given serial port device (Linux only)
-t Enable RTS RS-485 data direction control using RTS, active transmit.
-r Enable RTS RS-485 data direction control using RTS, active receive.
-y sysfsfile
Enable RS-485 direction data direction control by writing '1' to sysfs file
for transmitter enable and '0' to file for transmitter disable.
-Y sysfsfile
Enable RS-485 direction data direction control by writing '0' to sysfs file
for transmitter enable and '1' to file for transmitter disable.
-A address
Specifies TCP server address to bind (default is 0.0.0.0).
-P port
Specifies TCP port number (default 502).
Specifies TCP server port number (default is 502).
-C maxconn
Specifies maximum number of simultaneous TCP connections.
Specifies maximum number of simultaneous TCP connections (default is 32).
-N retries
Specifies maximum number of request retries (0 disables retries).
Specifies maximum number of request retries (0 disables retries, default is 3).
-R pause
Specifies pause between requests in milliseconds.
Specifies pause between requests in milliseconds (default is 100ms).
-W wait
Specifies response wait time in milliseconds.
Specifies response wait time in milliseconds (default is 500ms).
-T timeout
Specifies connection timeout value in seconds (0 disables timeout).
Specifies connection timeout value in seconds (0 disables timeout, default is 60).
-b
Instructs mbusd to reply on a broadcast.
Please note running **mbusd** on default Modbus TCP port (502) requires root privileges!
@ -112,7 +120,7 @@ The **mbusd** service can be started via:
# systemctl start mbusd@<serial port>.service
where `<serial port>` is serial port device name (like `ttyUSB0`).
where `<serial port>` is escaped serial port device short name (like `ttyUSB0` for `/dev/ttyUSB0` device name or `serial-rs485` for `/dev/serial/rs485` device name).
**mbusd** started by systemd will read its configuration from file named `/etc/mbusd/mbusd-<serial port>.conf`.
This way it's possible to run multiple **mbusd** instances with different configurations.
@ -129,7 +137,23 @@ To start the **mbusd** service on system boot:
# systemctl enable mbusd@<serial port>.service
Please check systemd documentation for other usefull systemd [commands](https://wiki.archlinux.org/index.php/systemd)
Please check systemd documentation for other usefull systemd [commands](https://wiki.archlinux.org/index.php/systemd).
Docker:
-------
**mbusd** can be launched in Docker container with the following command:
```shell
docker run -d --privileged \
--name=mbusd \
-p 502:502 \
-v /dev:/dev \
-v /path/to/mbusd.conf:/etc/mbusd.conf \
3cky/mbusd:latest
```
where `/path/to/mbusd.conf` is the path to **mbusd** config file in the local filesystem.
Contributing:
-------------

View File

@ -4,6 +4,14 @@
# #
#############################################
########## Logging settings #############
# Logging verbosity level
loglevel = 2
# Logfile (fully-qualified path, or filename [stored at /var/log/] or - for STDOUT only)
logfile = /var/log/mbusd.log
########## Serial port settings #############
# Serial port device name
@ -15,7 +23,10 @@ speed = 9600
# Serial port mode
mode = 8n1
# RS-485 data direction control type (addc, rts, sysfs_0, sysfs_1)
# Enable RS-485 support for given serial port device (Linux only)
# enable_rs485 = no
# RS-485 data direction control type (addc, rts_0, rts/rts_1, sysfs_0, sysfs_1)
trx_control = addc
# Sysfs file to use to control data direction
@ -23,6 +34,9 @@ trx_control = addc
############# TCP port settings #############
# TCP server address to bind
address = 0.0.0.0
# TCP server port number
port = 502
@ -42,3 +56,6 @@ pause = 100
# Response wait time in milliseconds
wait = 500
# Reply on Broadcast
replyonbroadcast = no

View File

@ -1,4 +1,4 @@
.TH "mbusd" 8 "8 Jul 2019" "mbusd @PROJECT_VERSION@"
.TH "mbusd" 8 "27 Mar 2024" "mbusd @PROJECT_VERSION@"
.SH NAME
mbusd \- MODBUS/TCP to MODBUS/RTU gateway.
.SH SYNOPSIS
@ -18,10 +18,13 @@ mbusd \- MODBUS/TCP to MODBUS/RTU gateway.
.RB [ -m
.IR mode ]
.RB [ -t ]
.RB [ -r ]
.RB [ -y
.IR file ]
.IR sysfsfile ]
.RB [ -Y
.IR file ]
.IR sysfsfile ]
.RB [ -A
.IR address ]
.RB [ -P
.IR port ]
.RB [ -C
@ -34,6 +37,7 @@ mbusd \- MODBUS/TCP to MODBUS/RTU gateway.
.IR wait ]
.RB [ -T
.IR timeout ]
.RB [ -b ]
.SH DESCRIPTION
\fImbusd\fR is MODBUS/TCP to MODBUS/RTU gateway.
.SH OPTIONS
@ -57,16 +61,22 @@ Specifies serial port device name.
Specifies serial port speed.
.IP "\fB-m \fImode\fR"
Specifies serial port mode (like 8N1).
.IP \fB-S\fR
Enable RS-485 support for given serial port device (Linux only).
.IP "\fB-A \fIaddress\fR"
Specifies TCP address to bind.
.IP "\fB-P \fIport\fR"
Specifies TCP port number.
.IP \fB-t\fR
Enable RTS RS-485 data direction control (if not disabled while compile).
Enable RTS RS-485 data direction control using RTS, active transmit.
.IP \fB-r\fR
Enable RTS RS-485 data direction control using RTS, active receive.
.IP "\fB-y \fIfile\fR"
Enable RS-485 direction data direction control by writing '1' to file
for transmitter enable and '0' to file for transmitter disable
for transmitter enable and '0' to file for transmitter disable.
.IP "\fB-Y \fIfile\fR"
Enable RS-485 direction data direction control by writing '0' to file
for transmitter enable and '1' to file for transmitter disable
for transmitter enable and '1' to file for transmitter disable.
.IP "\fB-C \fImaxconn\fR"
Specifies maximum number of simultaneous TCP connections.
.IP "\fB-N \fIretries\fR"
@ -77,14 +87,14 @@ Specifies pause between requests in milliseconds.
Specifies response wait time in milliseconds.
.IP "\fB-T \fItimeout\fR"
Specifies connection timeout value in seconds (0 - disable timeout).
.IP "\fB-b\fR"
Instructs \fImbusd\fR to reply on a broadcast.
.SH NOTES
In case of situation when \fImbusd\fR received response with invalid CRC and can't correct
error by re-request, it return MODBUS/TCP packet with exception code 04. In case of situation
when \fImbusd\fR can't receive response from MODBUS RTU device (e.g. addressed controller
is not present on the network), it return MODBUS/TCP packet with exception code 0x0B.
.SH BUGS
Broadcast messaging on MODBUS network isn't supported.
Please send bug reports to author.
.SH AUTHORS
Victor Antonovich <v.antonovich@gmail.com>

View File

@ -34,13 +34,15 @@
#include "cfg.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "util.h"
#define CFG_MAX_LINE_LENGTH 200
#define CFG_NAME_MATCH(n) strcmp(n, name) == 0
#define CFG_VALUE_MATCH(n) strcasecmp(n, value) == 0
#define CFG_VALUE_BOOL() (strcasecmp("y", value) == 0 || strcasecmp("yes", value) == 0)
/* Global configuration storage variable */
cfg_t cfg;
@ -63,34 +65,21 @@ cfg_init(void)
strncpy(cfg.ttyport, DEFAULT_PORT, INTBUFSIZE);
cfg.ttyspeed = DEFAULT_SPEED;
strncpy(cfg.ttymode, DEFAULT_MODE, INTBUFSIZE);
#ifdef HAVE_TIOCRS485
cfg.rs485 = FALSE;
#endif
#ifdef TRXCTL
cfg.trxcntl = TRX_ADDC;
*cfg.trxcntl_file = '\0';
#endif
strncpy(cfg.serveraddr, DEFAULT_SERVERADDR, INTBUFSIZE);
cfg.serverport = DEFAULT_SERVERPORT;
cfg.maxconn = DEFAULT_MAXCONN;
cfg.maxtry = DEFAULT_MAXTRY;
cfg.rqstpause = DEFAULT_RQSTPAUSE;
cfg.respwait = DEFAULT_RESPWAIT;
cfg.resppause = DV(3, cfg.ttyspeed);
cfg.conntimeout = DEFAULT_CONNTIMEOUT;
}
static char *
cfg_rtrim(char *s)
{
char *p = s + strlen(s);
while (p > s && isspace((unsigned char )(*--p)))
*p = '\0';
return s;
}
static char *
cfg_ltrim(const char *s)
{
while (*s && isspace((unsigned char )(*s)))
s++;
return (char *) s;
cfg.replyonbroadcast=0;
}
int
@ -102,7 +91,13 @@ cfg_handle_param(char *name, char *value)
}
else if (CFG_NAME_MATCH("speed"))
{
cfg.ttyspeed = strtoul(value, NULL, 0);
char *end;
cfg.ttyspeed = strtoul(value, &end, 0);
if (!cfg.ttyspeed || value == end || '\0' != *end)
{
CFG_ERR("invalid serial port speed: %s", value);
return 0;
}
}
else if (CFG_NAME_MATCH("mode"))
{
@ -122,6 +117,10 @@ cfg_handle_param(char *name, char *value)
}
strncpy(cfg.ttymode, value, INTBUFSIZE);
}
else if (CFG_NAME_MATCH("address"))
{
strncpy(cfg.serveraddr, value, INTBUFSIZE);
}
else if (CFG_NAME_MATCH("port"))
{
cfg.serverport = strtoul(value, NULL, 0);
@ -161,12 +160,22 @@ cfg_handle_param(char *name, char *value)
CFG_ERR("invalid wait value: %s", value);
return 0;
}
}
else if (CFG_NAME_MATCH("replyonbroadcast"))
{
cfg.replyonbroadcast = CFG_VALUE_BOOL();
}
else if (CFG_NAME_MATCH("timeout"))
{
cfg.conntimeout = strtoul(value, NULL, 0);
if (cfg.conntimeout > MAX_CONNTIMEOUT)
return 0;
#ifdef HAVE_TIOCRS485
}
else if (CFG_NAME_MATCH("enable_rs485"))
{
cfg.rs485 = CFG_VALUE_BOOL();
#endif
#ifdef TRXCTL
}
else if (CFG_NAME_MATCH("trx_control"))
@ -175,9 +184,13 @@ cfg_handle_param(char *name, char *value)
{
cfg.trxcntl = TRX_ADDC;
}
else if (CFG_VALUE_MATCH("rts"))
else if (CFG_VALUE_MATCH("rts") || CFG_VALUE_MATCH("rts_1"))
{
cfg.trxcntl = TRX_RTS;
cfg.trxcntl = TRX_RTS_1;
}
else if (CFG_VALUE_MATCH("rts_0"))
{
cfg.trxcntl = TRX_RTS_0;
}
else if (CFG_VALUE_MATCH("sysfs_0"))
{
@ -202,7 +215,40 @@ cfg_handle_param(char *name, char *value)
}
else if (CFG_NAME_MATCH("loglevel"))
{
cfg.dbglvl = (char)strtol(optarg, NULL, 0);
cfg.dbglvl = (char)strtol(value, NULL, 0);
# ifdef DEBUG
if (!(isdigit(*value)) || cfg.dbglvl < 0 || cfg.dbglvl > 9)
{ /* report about invalid log level */
CFG_ERR("invalid loglevel value: %s (must be 0-9)", value);
# else
if (!(isdigit(*value)) || cfg.dbglvl < 0 || cfg.dbglvl > 2)
{ /* report about invalid log level */
CFG_ERR("invalid loglevel value: %s (must be 0-2)", value);
# endif
return 0;
}
}
else if (CFG_NAME_MATCH("logfile"))
{
if (!strlen(value))
{
CFG_ERR("missing logfile value", value);
return 0;
}
else if (*value != '/')
{
if (*value == '-')
{
/* logging to file disabled */
*cfg.logname = '\0';
}
else
{ /* concatenate given log file name with default path */
strncpy(cfg.logname, LOGPATH, INTBUFSIZE);
strncat(cfg.logname, value, INTBUFSIZE - strlen(cfg.logname));
}
}
else strncpy(cfg.logname, value, INTBUFSIZE);
#endif
}
else {
@ -236,7 +282,7 @@ cfg_parse_file(void *file)
{
lineno++;
start = cfg_ltrim(cfg_rtrim(line));
start = util_trim(line);
if (*start == '#')
{
@ -251,8 +297,8 @@ cfg_parse_file(void *file)
{
*end = '\0';
name = cfg_rtrim(start);
value = cfg_ltrim(cfg_rtrim(end + 1));
name = util_rtrim(start);
value = util_trim(end + 1);
/* handle name/value pair */
if (!cfg_handle_param(name, value))

View File

@ -54,10 +54,18 @@ typedef struct
int ttyspeed;
/* tty mode */
char ttymode[INTBUFSIZE + 1];
#ifdef HAVE_TIOCRS485
/* Linux RS-485 support use flag */
bool rs485;
#endif
#ifdef TRXCTL
/* trx control type (0 - ADDC, 1 - by RTS, 2 - by sysfs GPIO with 1 activating transmit, 3 - by sysfs GPIO with 0 activating transmit) */
int trxcntl;
/* trx control sysfs file */
char trxcntl_file[INTBUFSIZE + 1];
#endif
/* TCP server address */
char serveraddr[INTBUFSIZE + 1];
/* TCP server port number */
int serverport;
/* maximum number of connections */
@ -70,8 +78,8 @@ typedef struct
unsigned long rqstpause;
/* response waiting time (in msec) */
unsigned long respwait;
/* inter-byte response pause (in usec) */
unsigned long resppause;
/* reply to client on broadcast */
int replyonbroadcast;
} cfg_t;
/* Prototypes */

View File

@ -56,11 +56,7 @@ int tty_reopen()
{
logw(3, "tty re-opening...");
tty_close(&tty);
#ifdef TRXCTL
tty_init(&tty, cfg.ttyport, cfg.ttyspeed, cfg.trxcntl);
#else
tty_init(&tty, cfg.ttyport, cfg.ttyspeed);
#endif
tty_init(&tty);
if (tty_open(&tty) != RC_OK)
{
#ifdef LOG
@ -96,11 +92,7 @@ int
conn_init(void)
{
/* tty device initialization */
#ifdef TRXCTL
tty_init(&tty, cfg.ttyport, cfg.ttyspeed, cfg.trxcntl);
#else
tty_init(&tty, cfg.ttyport, cfg.ttyspeed);
#endif
tty_init(&tty);
if (tty_open(&tty) != RC_OK)
{
#ifdef LOG
@ -113,13 +105,12 @@ conn_init(void)
state_tty_set(&tty, TTY_PAUSE);
/* create server socket */
if ((server_sd =
sock_create_server("", cfg.serverport, TRUE)) < 0)
if ((server_sd = sock_create_server(cfg.serveraddr, cfg.serverport, TRUE)) < 0)
{
#ifdef LOG
logw(0, "conn_init():"
" can't create listen() socket (%s)",
strerror(errno));
" can't create listen socket (%s)",
(errno != 0) ? strerror(errno) : "failed");
#endif
return RC_ERR;
}
@ -140,26 +131,28 @@ conn_open(void)
{
int sd;
conn_t *newconn;
struct sockaddr_in rmt_addr;
struct sockaddr_storage rmt_addr;
char ipstr[INET6_ADDRSTRLEN];
if ((sd = sock_accept(server_sd, &rmt_addr, TRUE)) == RC_ERR)
if ((sd = sock_accept(server_sd, (struct sockaddr *)&rmt_addr,
sizeof(rmt_addr), TRUE)) == RC_ERR)
{ /* error in conn_accept() */
#ifdef LOG
logw(0, "conn_open(): error in accept() (%s)", strerror(errno));
#endif
return;
}
inet_ntop(rmt_addr.ss_family, sock_addr((struct sockaddr *)&rmt_addr),
ipstr, sizeof(ipstr));
#ifdef LOG
logw(2, "conn_open(): accepting connection from %s",
inet_ntoa(rmt_addr.sin_addr));
logw(2, "conn_open(): accepting connection from %s", ipstr);
#endif
/* compare descriptor of connection with FD_SETSIZE */
if (sd >= FD_SETSIZE)
{
#ifdef LOG
logw(1, "conn_open(): FD_SETSIZE limit reached,"
" connection from %s will be dropped",
inet_ntoa(rmt_addr.sin_addr));
" connection from %s will be dropped", ipstr);
#endif
close(sd);
return;
@ -169,8 +162,7 @@ conn_open(void)
{
#ifdef LOG
logw(1, "conn_open(): number of connections limit reached,"
" connection from %s will be dropped",
inet_ntoa(rmt_addr.sin_addr));
" connection from %s will be dropped", ipstr);
#endif
close(sd);
return;
@ -178,8 +170,7 @@ conn_open(void)
/* enqueue connection */
newconn = queue_new_elem(&queue);
newconn->sd = sd;
memcpy((void *) &newconn->sockaddr,
&rmt_addr, sizeof(struct sockaddr_in));
memcpy((void *) &newconn->remote_addr, &ipstr, sizeof(ipstr));
state_conn_set(newconn, CONN_HEADER);
}
@ -193,8 +184,7 @@ conn_close(conn_t *conn)
{
conn_t *nextconn;
#ifdef LOG
logw(2, "conn_close(): closing connection from %s",
inet_ntoa(conn->sockaddr.sin_addr));
logw(2, "conn_close(): closing connection from %s", conn->remote_addr);
#endif
/* close socket */
close(conn->sd);
@ -202,6 +192,7 @@ conn_close(conn_t *conn)
nextconn = queue_next_elem(&queue, conn);
/* dequeue connection */
queue_delete_elem(&queue, conn);
if (nextconn == conn) nextconn = NULL;
if (actconn == conn) actconn = nextconn;
return nextconn;
}
@ -237,7 +228,7 @@ conn_read(int d, void *buf, size_t nbytes)
{ /* trying read from descriptor while breaked by signals */
rc = read(d, buf, nbytes);
} while (rc == -1 && errno == EINTR);
return (rc < 0) ? RC_ERR : rc;
return (rc < 0) ? (errno == EAGAIN ? RC_EAGAIN : RC_ERR) : rc;
}
#include <stdio.h>
@ -256,43 +247,30 @@ conn_write(int d, void *buf, size_t nbytes, int istty)
fd_set fs;
struct timeval ts, tts;
long delay;
#ifdef TRXCTL
if (istty) {
if (cfg.trxcntl != TRX_ADDC )
tty_set_rts(d);
usleep(35000000l/cfg.ttyspeed);
if (istty && cfg.trxcntl != TRX_ADDC)
{
tty_set_tx(d);
tty_delay(35000000l/cfg.ttyspeed);
}
#endif
FD_ZERO(&fs);
FD_SET(d, &fs);
do
{ /* trying write to descriptor while breaked by signals */
gettimeofday(&ts, NULL);
{
rc = write(d, buf, nbytes);
} while (rc == -1 && errno == EINTR);
gettimeofday(&ts, NULL);
#ifdef TRXCTL
if (istty) {
#if 1
do {
gettimeofday(&tts, NULL);
delay = DV(nbytes, cfg.ttyspeed) -
((tts.tv_sec * 1000000l + tts.tv_usec) - (ts.tv_sec * 1000000l + ts.tv_usec));
} while (delay > 0);
#else
gettimeofday(&tts, NULL);
delay = DV(nbytes, cfg.ttyspeed) -
((tts.tv_sec * 1000000l + tts.tv_usec) - (ts.tv_sec * 1000000l + ts.tv_usec));
usleep(delay);
#endif
/* tcdrain(d); - hangs sometimes, so make calculated delay */
if (cfg.trxcntl != TRX_ADDC ) {
tty_clr_rts(d);
}
if (istty && cfg.trxcntl != TRX_ADDC )
{
tty_delay(DV(nbytes, tty.bpc, cfg.ttyspeed));
tty_set_rx(d);
}
#endif
return (rc < 0) ? RC_ERR : rc;
}
@ -491,8 +469,10 @@ conn_loop(void)
#endif
if (!tty.trynum) {
modbus_ex_write(actconn->buf, MB_EX_CRC);
#ifdef DEBUG
logw(3, "tty: response is incorrect (%d of %d bytes, offset %d), return error", tty.ptrbuf,
tty.rxoffset + tty.rxlen, tty.rxoffset);
#endif
} else
{ /* retry request */
#ifdef DEBUG
@ -536,15 +516,13 @@ conn_loop(void)
if (curconn->state == CONN_TTY)
{ /* deadlock in CONN_TTY state, make attempt to reinitialize serial port */
#ifdef LOG
logw(0, "conn[%s]: state CONN_TTY deadlock.",
inet_ntoa(curconn->sockaddr.sin_addr));
logw(0, "conn[%s]: state CONN_TTY deadlock.", curconn->remote_addr);
#endif
tty_reinit();
}
/* purge connection */
#ifdef LOG
logw(2, "conn[%s]: timeout, closing connection",
inet_ntoa(curconn->sockaddr.sin_addr));
logw(2, "conn[%s]: timeout, closing connection", curconn->remote_addr);
#endif
curconn = conn_close(curconn);
continue;
@ -581,34 +559,56 @@ conn_loop(void)
#ifdef DEBUG
logw(7, "tty: request written (total %d bytes)", tty.txlen);
#endif
state_tty_set(&tty, TTY_RESP);
switch (tty.txbuf[1]) {
case 1:
case 2:
tty.rxlen = 5 + (tty.txbuf[4] * 256 + tty.txbuf[5] + 7)/8;
break;
case 3:
case 4:
tty.rxlen = 5 + tty.txbuf[5] * 2;
break;
case 7:
tty.rxlen = 5;
break;
case 11:
case 15:
case 16:
tty.rxlen = 8;
break;
default:
tty.rxlen = tty.txlen;
break;
}
if (tty.rxlen > TTY_BUFSIZE)
tty.rxlen = TTY_BUFSIZE;
tty.timer += DV(tty.rxlen, tty.speed);
if (!tty.txbuf[0])
{
#ifdef DEBUG
logw(5, "tty: estimated %d bytes, waiting %lu usec", tty.rxlen, tty.timer);
logw(5, "conn[%s]: broadcast request sent", curconn->remote_addr);
#endif
/* broadcast request sent, no reply expected from TTY*/
state_tty_set(&tty, TTY_PAUSE);
if (cfg.replyonbroadcast)
{
/* switch connection to response state, reply w actconn->buf */
state_conn_set(actconn, CONN_RESP);
}
else
{
/* Done, ready for next */
state_conn_set(actconn, CONN_HEADER);
}
}
else
{
state_tty_set(&tty, TTY_RESP);
switch (tty.txbuf[1]) {
case 1:
case 2:
tty.rxlen = 5 + (tty.txbuf[4] * 256 + tty.txbuf[5] + 7)/8;
break;
case 3:
case 4:
tty.rxlen = 5 + tty.txbuf[5] * 2;
break;
case 7:
tty.rxlen = 5;
break;
case 11:
case 15:
case 16:
tty.rxlen = 8;
break;
default:
tty.rxlen = tty.txlen;
break;
}
if (tty.rxlen > TTY_BUFSIZE)
tty.rxlen = TTY_BUFSIZE;
tty.timer += DV(tty.rxlen, tty.bpc, tty.speed);
#ifdef DEBUG
logw(5, "tty: estimated %d bytes, waiting %lu usec", tty.rxlen, tty.timer);
#endif
}
}
}
@ -623,59 +623,77 @@ conn_loop(void)
}
rc = conn_read(tty.fd, tty.rxbuf + tty.ptrbuf,
tty.rxlen - tty.ptrbuf + tty.rxoffset);
if (rc <= 0)
if (rc == RC_EAGAIN)
{ /* some tty devices seem to be set as ready to be read by select()
while no data is available (see #78) */
#ifdef LOG
logw(5, "tty: read(): no data available");
#endif
}
else if (rc <= 0)
{ /* error - make attempt to reinitialize serial port */
#ifdef LOG
logw(0, "tty: error in read() (%s)", rc ? strerror(errno) : "port closed");
#endif
tty_reinit();
}
else
{
#ifdef DEBUG
logw(7, "tty: read %d bytes", rc);
#endif
if (tty.ptrbuf - tty.rxoffset < 3 && tty.ptrbuf - tty.rxoffset + rc >= 3) {
/* we received more than 3 bytes from header - address, request id and bytes count */
if (!tty.rxoffset) {
/* offset is unknown */
unsigned char i;
for (i = 0; i < tty.ptrbuf - tty.rxoffset + rc - 1; i++) {
if (tty.rxbuf[i] == tty.txbuf[0] && tty.rxbuf[i+1] == tty.txbuf[1]) {
if (tty.ptrbuf - tty.rxoffset < 3 && tty.ptrbuf - tty.rxoffset + rc >= 3) {
/* we received more than 3 bytes from header - address, request id and bytes count */
if (!tty.rxoffset) {
/* offset is unknown */
unsigned char i;
for (i = 0; i < tty.ptrbuf - tty.rxoffset + rc - 1; i++) {
if (tty.rxbuf[i] == tty.txbuf[0] && tty.rxbuf[i+1] == tty.txbuf[1]) {
#ifdef DEBUG
logw(5, "tty: rx offset is %d", i);
logw(5, "tty: rx offset is %d", i);
#endif
tty.rxoffset = i;
break;
tty.rxoffset = i;
break;
}
}
switch (tty.txbuf[1]) {
case 1:
case 2:
case 3:
case 4:
i = 5 + tty.rxbuf[tty.rxoffset + 2];
break;
default:
i = tty.rxlen;
break;
}
if (i + tty.rxoffset > TTY_BUFSIZE)
i = TTY_BUFSIZE - tty.rxoffset;
if (i != tty.rxlen) {
#ifdef DEBUG
logw(5, "tty: rx len changed from %d to %d", tty.rxlen, i);
#endif
tty.rxlen = i;
}
}
switch (tty.txbuf[1]) {
case 1:
case 2:
case 3:
case 4:
i = 5 + tty.rxbuf[tty.rxoffset + 2];
break;
default:
i = tty.rxlen;
break;
}
if (i + tty.rxoffset > TTY_BUFSIZE)
i = TTY_BUFSIZE - tty.rxoffset;
if (i != tty.rxlen) {
#ifdef DEBUG
logw(5, "tty: rx len changed from %d to %d", tty.rxlen, i);
#endif
tty.rxlen = i;
}
}
tty.ptrbuf += rc;
logw(5, "tty: read %d bytes of %d, offset %d", tty.ptrbuf, tty.rxlen + tty.rxoffset, tty.rxoffset);
if (tty.ptrbuf == tty.rxlen + tty.rxoffset)
state_tty_set(&tty, TTY_PROC);
}
tty.ptrbuf += rc;
logw(5, "tty: read %d bytes of %d, offset %d", tty.ptrbuf, tty.rxlen + tty.rxoffset, tty.rxoffset);
if (tty.ptrbuf == tty.rxlen + tty.rxoffset)
state_tty_set(&tty, TTY_PROC);
}
else if (tty.state != TTY_PROC)
{ /* drop unexpected tty data */
if ((rc = conn_read(tty.fd, tty.rxbuf, BUFSIZE)) <= 0)
rc = conn_read(tty.fd, tty.rxbuf, BUFSIZE);
if (rc == RC_EAGAIN)
{ /* some tty devices seem to be set as ready to be read by select()
while no data is available (see #78) */
#ifdef LOG
logw(5, "tty: read(): no data available");
#endif
}
else if (rc <= 0)
{ /* error - make attempt to reinitialize serial port */
#ifdef LOG
logw(0, "tty: error in read() (%s)", rc ? strerror(errno) : "port closed");
@ -790,8 +808,7 @@ conn_loop(void)
/* check request function code */
unsigned char fc = MB_FRAME(curconn->buf, MB_FCODE);
#ifdef DEBUG
logw(7, "conn[%s]: read request fc %d",
inet_ntoa(curconn->sockaddr.sin_addr), fc);
logw(7, "conn[%s]: read request fc %d", curconn->remote_addr, fc);
#endif
switch (fc)
{
@ -846,11 +863,12 @@ conn_loop(void)
{ /* ### frame received completely ### */
#ifdef DEBUG
t[0] = '\0';
int i;
for (i = MB_UNIT_ID; i < curconn->ctr; i++) {
sprintf(v, "[%2.2x]", curconn->buf[i]);
strncat(t, v, 1024-strlen(t));
}
logw(5, "conn[%s]: request: %s", inet_ntoa(curconn->sockaddr.sin_addr), t);
logw(5, "conn[%s]: request: %s", curconn->remote_addr, t);
#endif
state_conn_set(curconn, CONN_TTY);
if (tty.state == TTY_READY)
@ -896,7 +914,7 @@ conn_fix_request_header_len(conn_t *conn, unsigned char len)
{
#ifdef DEBUG
logw(5, "conn[%s]: request data len changed from %d to %d",
MB_FRAME(conn->buf, MB_LENGTH_L), len);
conn->remote_addr, MB_FRAME(conn->buf, MB_LENGTH_L), len);
#endif
MB_FRAME(conn->buf, MB_LENGTH_L) = len;
}

View File

@ -46,6 +46,7 @@
/*
* Default values
*/
#define DEFAULT_SERVERADDR "0.0.0.0"
#define DEFAULT_SERVERPORT 502
#define DEFAULT_MAXCONN 32
#define DEFAULT_MAXTRY 3
@ -103,7 +104,7 @@ typedef struct conn_t
int sd; /* socket descriptor */
int state; /* current state */
int timeout; /* timeout value, secs */
struct sockaddr_in sockaddr; /* connection structure */
char remote_addr[INET6_ADDRSTRLEN]; /* remote client address */
int ctr; /* counter of data in the buffer */
int read_len; /* length of modbus frame to read */
unsigned char buf[HDRSIZE + BUFSIZE]; /* data buffer */

View File

@ -1,79 +0,0 @@
/*
* OpenMODBUS/TCP to RS-232/485 MODBUS RTU gateway
*
* crc16.c - Cyclic Redundant Checks calculating
*
* Copyright (c) 2002-2003, 2013, Victor Antonovich (v.antonovich@gmail.com)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: crc16.c,v 1.3 2015/02/25 10:33:57 kapyar Exp $
*/
#include "crc16.h"
unsigned short
crc_16_tab[] = {
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
};
unsigned short
crc16(unsigned char *buf, unsigned int bsize)
{
unsigned short crc = 0xffff;
while (bsize--)
crc = (unsigned short)(crc >> 8) ^ crc_16_tab[(crc ^ *buf++) & 0xff];
return crc;
}

View File

@ -59,6 +59,7 @@
#include <netdb.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdbool.h>
#ifdef HAVE_LIBUTIL
# include <libutil.h>
#endif
@ -84,6 +85,7 @@
#define RC_TIMEOUT -3
#define RC_AOPEN -4
#define RC_ACLOSE -5
#define RC_EAGAIN -6
/* Internal string buffers size */
#if defined(PATH_MAX)

View File

@ -40,7 +40,7 @@ extern int isdaemon;
/* Default log file path and name */
#define LOGPATH "/var/log/"
#define LOGNAME "mbus.log"
#define LOGNAME "mbusd.log"
#ifdef LOG
int log_init(char *logname);

View File

@ -40,6 +40,7 @@
#include "conn.h"
#include "queue.h"
#include "sig.h"
#include "util.h"
#ifdef LOG
# include "log.h"
#endif
@ -100,67 +101,85 @@ void
usage(char *exename)
{
cfg_init();
printf("%s-%s Copyright (C) 2002-2003, 2011, 2013-2019 Victor Antonovich <v.antonovich@gmail.com>, "
printf("%s-%s Copyright (C) 2002-2003, 2011, 2013-2024 Victor Antonovich <v.antonovich@gmail.com>, "
"Andrew Denysenko <nitr0@seti.kr.ua>\n\n"
"Usage: %s [-h] [-d] "
#ifdef LOG
"[-L logfile] [-v level] "
#endif
"[-c cfgfile] \n"
" [-p device] [-s speed] [-m mode]\n"
#ifdef TRXCTL
" [-t] [-y sysfsfile] [-Y sysfsfile]\n"
" [-p device] [-s speed] [-m mode]"
#ifdef HAVE_TIOCRS485
" [-S]"
#endif
" [-P port] [-C maxconn] [-N retries] [-R pause] [-W wait] [-T timeout]\n\n"
"\n"
#ifdef TRXCTL
" [-t] [-r] [-y sysfsfile] [-Y sysfsfile]\n"
#endif
" [-A address] [-P port] [-C maxconn] [-N retries]\n"
" [-R pause] [-W wait] [-T timeout] [-b]\n\n"
"Options:\n"
" -h : this help\n"
" -d : don't daemonize\n"
" -d : don't fork (non-daemonize)\n"
#ifdef LOG
" -L logfile : set log file name (default %s%s, \n"
" -L logfile : set log file name (default is %s%s, \n"
" '-' for logging to STDOUT only)\n"
#ifdef DEBUG
" -v level : set log level (0-9, default %d, 0 - errors only)\n"
" -v level : set log level (0-9, default is %d, 0 - errors only)\n"
#else
" -v level : set log level (0-2, default %d, 0 - errors only)\n"
#endif
#endif
" -c cfgfile : read configuration from cfgfile\n"
" -p device : set serial port device name (default %s)\n"
" -s speed : set serial port speed (default %d)\n"
" -m mode : set serial port mode (default %s)\n"
" -P port : set TCP server port number (default %d)\n"
" -p device : set serial port device name (default is %s)\n"
" -s speed : set serial port speed (default is %d)\n"
" -m mode : set serial port mode (default is %s)\n"
#ifdef HAVE_TIOCRS485
" -S : enable Linux RS-485 support for given serial port device\n"
#endif
#ifdef TRXCTL
" -t : enable RTS RS-485 data direction control using RTS\n"
" -t : enable RTS RS-485 data direction control using RTS, active transmit\n"
" -r : enable RTS RS-485 data direction control using RTS, active receive\n"
" -y : enable RTS RS-485 data direction control using sysfs file, active transmit\n"
" (writes '1' to sysfs file for transmit enable, '0' for transmit disable)\n"
" -Y : enable RTS RS-485 data direction control using sysfs file, active receive\n"
" (writes '0' to sysfs file for transmit enable, '1' for transmit disable)\n"
#endif
" -A address : set TCP server address to bind (default is %s)\n"
" -P port : set TCP server port number (default is %d)\n"
" -C maxconn : set maximum number of simultaneous TCP connections\n"
" (1-%d, default %d)\n"
" (1-%d, default is %d)\n"
" -N retries : set maximum number of request retries\n"
" (0-%d, default %d, 0 - without retries)\n"
" (0-%d, default is %d, 0 disables retrying)\n"
" -R pause : set pause between requests in milliseconds\n"
" (1-%d, default %lu)\n"
" (1-%d, default is %lu)\n"
" -W wait : set response wait time in milliseconds\n"
" (1-%d, default %lu)\n"
" (1-%d, default is %lu)\n"
" -T timeout : set connection timeout value in seconds\n"
" (0-%d, default %d, 0 - no timeout)"
" (0-%d, default is %d, 0 disables timeout)\n"
" -b : enable reply on broadcast"
"\n", PACKAGE, VERSION, exename,
#ifdef LOG
LOGPATH, LOGNAME, cfg.dbglvl,
#endif
cfg.ttyport, cfg.ttyspeed, cfg.ttymode, cfg.serverport,
cfg.ttyport, cfg.ttyspeed, cfg.ttymode,
cfg.serveraddr, cfg.serverport,
MAX_MAXCONN, cfg.maxconn, MAX_MAXTRY, cfg.maxtry,
MAX_RQSTPAUSE, cfg.rqstpause, MAX_RESPWAIT, cfg.respwait,
MAX_CONNTIMEOUT, cfg.conntimeout);
exit(0);
}
int
main(int argc, char *argv[])
{
int err = 0, rc, err_line;
char *exename;
char ttyparity;
char *end;
char *logfilenamevalue;
char *logfilename;
sig_init();
@ -177,10 +196,13 @@ main(int argc, char *argv[])
#ifdef TRXCTL
"ty:Y:"
#endif
#ifdef HAVE_TIOCRS485
"S"
#endif
#ifdef LOG
"v:L:"
#endif
"p:s:m:P:C:N:R:W:T:c:")) != RC_ERR)
"p:s:m:A:P:C:N:R:W:T:c:b")) != RC_ERR)
{
switch (rc)
{
@ -203,38 +225,53 @@ main(int argc, char *argv[])
break;
#ifdef TRXCTL
case 't':
cfg.trxcntl = TRX_RTS;
cfg.trxcntl = TRX_RTS_1;
break;
case 'r':
cfg.trxcntl = TRX_RTS_0;
break;
case 'y':
cfg.trxcntl = TRX_SYSFS_1;
strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE);
break;
break;
case 'Y':
cfg.trxcntl = TRX_SYSFS_0;
strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE);
break;
break;
#endif
#ifdef HAVE_TIOCRS485
case 'S':
cfg.rs485 = TRUE;
break;
#endif
#ifdef LOG
case 'v':
cfg.dbglvl = (char)strtol(optarg, NULL, 0);
# ifdef DEBUG
if (cfg.dbglvl > 9)
if (!(isdigit(*optarg)) || cfg.dbglvl < 0 || cfg.dbglvl > 9)
{ /* report about invalid log level */
printf("%s: -v: invalid loglevel value"
" (%d, must be 0-9)\n", exename, cfg.dbglvl);
" (%s, must be 0-9)\n", exename, optarg);
# else
if (cfg.dbglvl < 0 || cfg.dbglvl > 9)
if (!(isdigit(*optarg)) || cfg.dbglvl < 0 || cfg.dbglvl > 2)
{ /* report about invalid log level */
printf("%s: -v: invalid loglevel value"
" (%d, must be 0-2)\n", exename, cfg.dbglvl);
" (%s, must be 0-2)\n", exename, optarg);
# endif
exit(-1);
}
break;
case 'L':
if (*optarg != '/')
logfilenamevalue = strdup(optarg);
logfilename = util_trim(logfilenamevalue);
if (!strlen(logfilename))
{ /* report about empty log file */
printf("%s: -L: log file name is empty, exiting...\n", exename);
exit(-1);
}
else if (*logfilename != '/')
{
if (*optarg == '-')
if (*logfilename == '-')
{
/* logging to file disabled */
*cfg.logname = '\0';
@ -242,10 +279,11 @@ main(int argc, char *argv[])
else
{ /* concatenate given log file name with default path */
strncpy(cfg.logname, LOGPATH, INTBUFSIZE);
strncat(cfg.logname, optarg, INTBUFSIZE - strlen(cfg.logname));
strncat(cfg.logname, logfilename, INTBUFSIZE - strlen(cfg.logname));
}
}
else strncpy(cfg.logname, optarg, INTBUFSIZE);
else strncpy(cfg.logname, logfilename, INTBUFSIZE);
free(logfilenamevalue);
break;
#endif
case 'p':
@ -258,7 +296,12 @@ main(int argc, char *argv[])
else strncpy(cfg.ttyport, optarg, INTBUFSIZE);
break;
case 's':
cfg.ttyspeed = strtoul(optarg, NULL, 0);
cfg.ttyspeed = strtoul(optarg, &end, 10);
if (!cfg.ttyspeed || optarg == end || '\0' != *end)
{
printf("%s: -s: invalid serial port speed (%s)\n", exename, optarg);
exit(-1);
}
break;
case 'm':
strncpy(cfg.ttymode, optarg, INTBUFSIZE);
@ -290,6 +333,9 @@ main(int argc, char *argv[])
exit(-1);
}
break;
case 'A':
strncpy(cfg.serveraddr, optarg, INTBUFSIZE);
break;
case 'P':
cfg.serverport = strtoul(optarg, NULL, 0);
break;
@ -338,6 +384,9 @@ main(int argc, char *argv[])
exit(-1);
}
break;
case 'b':
cfg.replyonbroadcast = 1;
break;
case 'h':
usage(exename);
break;

View File

@ -34,6 +34,57 @@
#include "modbus.h"
#include "conn.h"
static const unsigned short
modbus_crc16_table[] = {
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
};
/*
* Calculate Modbus frame CRC (Cyclical Redundancy Checking) value
* Parameters: FRAME - address of the frame,
* LEN - frame length;
* Return: calculated CRC value
*/
static unsigned short
modbus_crc_calculate(unsigned char *frame, unsigned int len)
{
unsigned short crc = 0xffff;
while (len--)
crc = (unsigned short)(crc >> 8) ^ modbus_crc16_table[(crc ^ *frame++) & 0xff];
return crc;
}
/*
* Check CRC of MODBUS frame
* Parameters: FRAME - address of the frame,
@ -44,7 +95,7 @@
int
modbus_crc_correct(unsigned char *frame, unsigned int len)
{
return (!crc16(frame, len));
return (!modbus_crc_calculate(frame, len));
}
/*
@ -56,7 +107,7 @@ modbus_crc_correct(unsigned char *frame, unsigned int len)
void
modbus_crc_write(unsigned char *frame, unsigned int len)
{
WORD_WR_LE(frame + len, crc16(frame, len));
WORD_WR_LE(frame + len, modbus_crc_calculate(frame, len));
}
/*

View File

@ -35,7 +35,6 @@
#define _MODBUS_H
#include "globals.h"
#include "crc16.h"
/*
* Macro for accessing data in MODBUS frame

View File

@ -115,5 +115,5 @@ queue_delete_elem(queue_t *queue, conn_t *conn)
conn_t *
queue_next_elem(queue_t *queue, conn_t *conn)
{
return (conn->next == NULL) ? queue->beg : conn->next;
return (conn == NULL || conn->next == NULL) ? queue->beg : conn->next;
}

View File

@ -58,12 +58,11 @@ sock_set_blkmode(int sd, int blkmode)
* Return: socket descriptor, otherwise RC_ERR if there some errors
*/
int
sock_create(int blkmode)
sock_create(int blkmode, sa_family_t sa_family)
{
int sock;
if ((sock =
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
if ((sock = socket(sa_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
#ifdef LOG
logw(0, "sock_create(): unable to create socket (%s)",
@ -94,15 +93,52 @@ sock_create(int blkmode)
* Return: socket descriptor, otherwise RC_ERR if there some errors
*/
int
sock_create_server(char *server_ip,
unsigned short server_port, int blkmode)
sock_create_server(char *server_ip, unsigned short server_port, int blkmode)
{
struct sockaddr_in server_sockaddr;
struct sockaddr_storage server_sockaddr;
int sock_opt = 1;
int server_s;
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
/* parse address to bind socket */
if (server_ip != NULL)
{
/* try first to parse server_ip as IPv6 address, if it fail, try to parse as IPv4 */
if (inet_pton(AF_INET6, server_ip,
&(*((struct sockaddr_in6 *)&server_sockaddr)).sin6_addr) != 0)
server_sockaddr.ss_family = AF_INET6;
else if (inet_pton(AF_INET, server_ip,
&(*((struct sockaddr_in *)&server_sockaddr)).sin_addr) != 0)
server_sockaddr.ss_family = AF_INET;
else
{
#ifdef LOG
logw(0, "sock_create_server():"
" can't parse address: %s",
server_ip);
#endif
return RC_ERR;
}
}
else
{
server_sockaddr.ss_family = AF_INET; // TODO AF_INET6/in6addr_any?
(*((struct sockaddr_in *)&server_sockaddr)).sin_addr.s_addr = htonl(INADDR_ANY);
}
/* parse IP port */
switch (server_sockaddr.ss_family) {
case AF_INET:
(*((struct sockaddr_in *)&server_sockaddr)).sin_port = htons(server_port);
break;
case AF_INET6:
(*((struct sockaddr_in6 *)&server_sockaddr)).sin6_port = htons(server_port);
break;
}
/* create socket in desired blocking mode */
server_s = sock_create(blkmode);
server_s = sock_create(blkmode, server_sockaddr.ss_family);
if (server_s < 0) return server_s;
/* set to close socket on exec() */
@ -144,18 +180,9 @@ sock_create_server(char *server_ip,
return RC_ERR;
}
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
server_sockaddr.sin_family = AF_INET;
if (server_ip != NULL)
inet_aton(server_ip, &server_sockaddr.sin_addr);
else
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
server_sockaddr.sin_port = htons(server_port);
if (bind(server_s, (struct sockaddr *) & server_sockaddr,
sizeof(server_sockaddr)) == -1)
/* bind socket to given address and port */
if (bind(server_s, (struct sockaddr *)&server_sockaddr,
sa_len((struct sockaddr *)&server_sockaddr)) == -1)
{
#ifdef LOG
logw(0, "sock_create_server():"
@ -187,13 +214,11 @@ sock_create_server(char *server_ip,
* RMT_ADDR - ptr to connection info structure
*/
int
sock_accept(int server_sd, struct sockaddr_in *rmt_addr, int blkmode)
sock_accept(int server_sd, struct sockaddr *rmt_addr, socklen_t rmt_len, int blkmode)
{
int sd, sock_opt = SOCKBUFSIZE;
int rmt_len = sizeof(struct sockaddr_in);
sd = accept(server_sd, (struct sockaddr *) rmt_addr,
(socklen_t *) &rmt_len);
sd = accept(server_sd, rmt_addr, &rmt_len);
if (sd == -1)
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
@ -207,7 +232,7 @@ sock_accept(int server_sd, struct sockaddr_in *rmt_addr, int blkmode)
if (sock_set_blkmode(sd, blkmode) == RC_ERR)
{
#ifdef LOG
logw(0, "sock_accept(): can't set socket blocking mode (%s)",
logw(0, "sock_accept(): can't set socket blocking mode (%s)",
strerror(errno));
#endif
close(sd);
@ -230,3 +255,17 @@ sock_accept(int server_sd, struct sockaddr_in *rmt_addr, int blkmode)
}
return sd;
}
/*
* Return reference to socket address structure according to its family (AF_INET/AF_INET6)
*/
void *
sock_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET)
{
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

View File

@ -1,21 +1,21 @@
/*
* OpenMODBUS/TCP to RS-232/485 MODBUS RTU gateway
*
* sock.h - socket manipulation routines
* sock.h - socket manipulation routines
*
* Copyright (c) 2002-2003, 2013, Victor Antonovich (v.antonovich@gmail.com)
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -44,11 +44,13 @@
/* Socket buffers size */
#define SOCKBUFSIZE 512
#define sa_len(sa_ptr) ((sa_ptr)->sa_family == AF_INET \
? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6))
int sock_set_blkmode(int sd, int blkmode);
int sock_create(int blkmode);
int sock_create_server(char *server_ip,
unsigned short server_port, int blkmode);
int sock_accept(int server_sd,
struct sockaddr_in *rmt_addr, int blkmode);
int sock_create(int blkmode, sa_family_t sa_family);
int sock_create_server(char *server_ip, unsigned short server_port, int blkmode);
int sock_accept(int server_sd, struct sockaddr *rmt_addr, socklen_t rmt_len, int blkmode);
void *sock_addr(struct sockaddr *sa);
#endif

View File

@ -76,47 +76,40 @@ state_conn_set(conn_t *conn, int state)
conn->ctr = 0;
conn->read_len = HDRSIZE;
#ifdef DEBUG
logw(5, "conn[%s]: state now is CONN_HEADER",
inet_ntoa(conn->sockaddr.sin_addr));
logw(5, "conn[%s]: state now is CONN_HEADER", conn->remote_addr);
#endif
break;
case CONN_RQST_FUNC:
conn->read_len = HDRSIZE + MB_FRAME(conn->buf, MB_LENGTH_L);
#ifdef DEBUG
logw(5, "conn[%s]: state now is CONN_RQST_FUNC",
inet_ntoa(conn->sockaddr.sin_addr));
logw(5, "conn[%s]: state now is CONN_RQST_FUNC", conn->remote_addr);
#endif
break;
case CONN_RQST_NVAL:
#ifdef DEBUG
logw(5, "conn[%s]: state now is CONN_RQST_NVAL",
inet_ntoa(conn->sockaddr.sin_addr));
logw(5, "conn[%s]: state now is CONN_RQST_NVAL", conn->remote_addr);
#endif
break;
case CONN_RQST_TAIL:
#ifdef DEBUG
logw(5, "conn[%s]: state now is CONN_RQST_TAIL",
inet_ntoa(conn->sockaddr.sin_addr));
logw(5, "conn[%s]: state now is CONN_RQST_TAIL", conn->remote_addr);
#endif
break;
case CONN_TTY:
#ifdef DEBUG
logw(5, "conn[%s]: state now is CONN_TTY",
inet_ntoa(conn->sockaddr.sin_addr));
logw(5, "conn[%s]: state now is CONN_TTY", conn->remote_addr);
#endif
break;
case CONN_RESP:
conn->ctr = 0;
#ifdef DEBUG
logw(5, "conn[%s]: state now is CONN_RESP",
inet_ntoa(conn->sockaddr.sin_addr));
logw(5, "conn[%s]: state now is CONN_RESP", conn->remote_addr);
#endif
break;
default:
/* unknown state, exiting */
#ifdef DEBUG
logw(5, "conn_set_state([%s]) - invalid state (%d)",
inet_ntoa(conn->sockaddr.sin_addr), state);
logw(5, "conn_set_state([%s]) - invalid state (%d)", conn->remote_addr, state);
#endif
exit (-1);
}
@ -155,7 +148,7 @@ state_tty_set(ttydata_t *mod, int state)
logw(5, "tty: state now is TTY_RQST");
#endif
#ifndef NOSILENT
tty_delay(DV(2, cfg.ttyspeed));
tty_delay(DV(2, mod->bpc, cfg.ttyspeed));
#endif
break;
case TTY_RESP:
@ -163,7 +156,7 @@ state_tty_set(ttydata_t *mod, int state)
mod->rxoffset = 0;
/* XXX need real recv length? */
mod->rxlen = TTY_BUFSIZE;
mod->timer = cfg.respwait * 1000l + DV(mod->txlen, mod->speed);
mod->timer = cfg.respwait * 1000l + DV(mod->txlen, mod->bpc, mod->speed);
#ifdef DEBUG
logw(5, "tty: state now is TTY_RESP");
#endif

148
src/tty.c
View File

@ -48,20 +48,28 @@ tty_sighup(void)
}
/*
* Init serial link parameters MOD to PORT name, SPEED and TRXCNTL type
* Init serial link parameters
*/
void
#ifdef TRXCTL
tty_init(ttydata_t *mod, char *port, int speed, int trxcntl)
#else
tty_init(ttydata_t *mod, char *port, int speed)
#endif
tty_init(ttydata_t *mod)
{
mod->fd = -1;
mod->port = port;
mod->speed = speed;
#ifdef TRXCTL
mod->trxcntl = trxcntl;
mod->port = cfg.ttyport;
mod->speed = cfg.ttyspeed;
mod->bpc = DEFAULT_BITS_PER_CHAR;
if (toupper(cfg.ttymode[1]) != 'N')
{
mod->bpc++;
}
if (cfg.ttymode[2] == '2')
{
mod->bpc++;
}
#ifdef HAVE_TIOCRS485
mod->rs485 = cfg.rs485;
#endif
#ifdef TRXCTL
mod->trxcntl = cfg.trxcntl;
#endif
}
@ -109,6 +117,9 @@ tty_open(ttydata_t *mod)
errno = buferr;
return RC_ERR;
}
#endif
#ifdef LOG
logw(2, "tty: trying to open %s (speed %d mode %s)", mod->port, mod->speed, cfg.ttymode);
#endif
mod->fd = open(mod->port, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (mod->fd < 0)
@ -178,7 +189,26 @@ tty_set_attr(ttydata_t *mod)
return RC_ERR;
tcflush(mod->fd, TCIOFLUSH);
#ifdef TRXCTL
tty_clr_rts(mod->fd);
tty_set_rx(mod->fd);
#endif
#ifdef HAVE_TIOCRS485
if (mod->rs485)
{
struct serial_rs485 rs485conf;
#ifdef LOG
logw(2, "tty: trying to enable RS-485 support for %s", mod->port);
#endif
if (ioctl(mod->fd, TIOCGRS485, &rs485conf) < 0) {
return RC_ERR;
}
rs485conf.flags |= SER_RS485_ENABLED;
if (ioctl(mod->fd, TIOCSRS485, &rs485conf) < 0) {
return RC_ERR;
}
#ifdef LOG
logw(2, "tty: enabled RS-485 support for %s", mod->port);
#endif
}
#endif
flag = fcntl(mod->fd, F_GETFL, 0);
if (flag < 0)
@ -195,9 +225,6 @@ tty_transpeed(int speed)
speed_t tspeed;
switch (speed)
{
case 0:
tspeed = B0;
break;
#if defined(B50)
case 50:
tspeed = B50;
@ -306,9 +333,76 @@ tty_transpeed(int speed)
tspeed = B115200;
break;
#endif
default:
tspeed = DEFAULT_BSPEED;
#if defined(B230400)
case 230400:
tspeed = B230400;
break;
#endif
#if defined(B460800)
case 460800:
tspeed = B460800;
break;
#endif
#if defined(B500000)
case 500000:
tspeed = B500000;
break;
#endif
#if defined(B576000)
case 576000:
tspeed = B576000;
break;
#endif
#if defined(B921600)
case 921600:
tspeed = B921600;
break;
#endif
#if defined(B1000000)
case 1000000:
tspeed = B1000000;
break;
#endif
#if defined(B1152000)
case 1152000:
tspeed = B1152000;
break;
#endif
#if defined(B1500000)
case 1500000:
tspeed = B1500000;
break;
#endif
#if defined(B2000000)
case 2000000:
tspeed = B2000000;
break;
#endif
#if defined(B2500000)
case 2500000:
tspeed = B2500000;
break;
#endif
#if defined(B3000000)
case 3000000:
tspeed = B3000000;
break;
#endif
#if defined(B3500000)
case 3500000:
tspeed = B3500000;
break;
#endif
#if defined(B4000000)
case 4000000:
tspeed = B4000000;
break;
#endif
default:
#ifdef LOG
logw(0, "tty: unsupported speed: %d", speed);
#endif
exit (-1);
}
return tspeed;
}
@ -371,27 +465,33 @@ void sysfs_gpio_set(char *filename, char *value) {
}
/* Set RTS line to active state */
/* Set tty device into transmit mode */
void
tty_set_rts(int fd)
tty_set_tx(int fd)
{
if ( TRX_RTS == cfg.trxcntl ) {
if ( TRX_RTS_1 == cfg.trxcntl ) {
int mstat = TIOCM_RTS;
ioctl(fd, TIOCMBIS, &mstat);
} else if ( TRX_SYSFS_1 == cfg.trxcntl) {
} else if ( TRX_RTS_0 == cfg.trxcntl ) {
int mstat = TIOCM_RTS;
ioctl(fd, TIOCMBIC, &mstat);
} else if ( TRX_SYSFS_1 == cfg.trxcntl) {
sysfs_gpio_set(cfg.trxcntl_file,"1");
} else if ( TRX_SYSFS_0 == cfg.trxcntl) {
sysfs_gpio_set(cfg.trxcntl_file,"0");
}
}
/* Set RTS line to passive state */
/* Set tty device into receive mode */
void
tty_clr_rts(int fd)
tty_set_rx(int fd)
{
if ( TRX_RTS == cfg.trxcntl ) {
if ( TRX_RTS_1 == cfg.trxcntl ) {
int mstat = TIOCM_RTS;
ioctl(fd, TIOCMBIC, &mstat);
} else if ( TRX_RTS_0 == cfg.trxcntl ) {
int mstat = TIOCM_RTS;
ioctl(fd, TIOCMBIS, &mstat);
} else if ( TRX_SYSFS_1 == cfg.trxcntl) {
sysfs_gpio_set(cfg.trxcntl_file,"0");
} else if ( TRX_SYSFS_0 == cfg.trxcntl) {
@ -412,7 +512,7 @@ tty_delay(int usec)
do
{
(void)gettimeofday(&ttv, NULL);
ts = 1000000 * (ttv.tv_sec - tv.tv_sec) + (ttv.tv_usec - tv.tv_usec);
ts = 1000000l * (ttv.tv_sec - tv.tv_sec) + (ttv.tv_usec - tv.tv_usec);
} while (ts < usec && !tty_break);
}

View File

@ -34,13 +34,20 @@
#ifndef _TTY_H
#define _TTY_H
#ifdef HAVE_TIOCRS485
#include <linux/serial.h>
#endif
#include "globals.h"
#include "cfg.h"
/*
* Delay value calculation macros
* Delay value calculation macros.
* c - number of characters
* b - bits per character
* s - bits per second
*/
#define DV(x, y) (x * 10000000l / y)
#define DV(c, b, s) (c * b * 1000000l / s)
/*
* Default tty port parameters
@ -58,6 +65,8 @@
#define DEFAULT_MODE "8N1"
#define DEFAULT_BITS_PER_CHAR 10
/*
* Maximum tty buffer size
*/
@ -68,9 +77,10 @@
*/
#ifdef TRXCTL
#define TRX_ADDC 0
#define TRX_RTS 1
#define TRX_SYSFS_1 2
#define TRX_SYSFS_0 3
#define TRX_RTS_1 1
#define TRX_RTS_0 2
#define TRX_SYSFS_1 3
#define TRX_SYSFS_0 4
#endif
/*
@ -90,6 +100,10 @@ typedef struct
int fd; /* tty file descriptor */
int speed; /* serial port speed */
char *port; /* serial port device name */
int bpc; /* bits per character */
#ifdef HAVE_TIOCRS485
bool rs485; /* use Linux RS-485 support flag */
#endif
#ifdef TRXCTL
int trxcntl; /* trx control type (enum - see values in config.h) */
#endif
@ -108,18 +122,14 @@ typedef struct
/* prototypes */
void tty_sighup(void);
#ifdef TRXCTL
void tty_init(ttydata_t *mod, char *port, int speed, int trxcntl);
#else
void tty_init(ttydata_t *mod, char *port, int speed);
#endif
void tty_init(ttydata_t *mod);
int tty_open(ttydata_t *mod);
int tty_set_attr(ttydata_t *mod);
speed_t tty_transpeed(int speed);
int tty_cooked(ttydata_t *mod);
int tty_close(ttydata_t *mod);
void tty_set_rts(int fd);
void tty_clr_rts(int fd);
void tty_set_tx(int fd);
void tty_set_rx(int fd);
void tty_delay(int usec);
#endif /* _TTY_H */

80
src/util.c Normal file
View File

@ -0,0 +1,80 @@
/*
* OpenMODBUS/TCP to RS-232/485 MODBUS RTU gateway
*
* util.h - utility functions
*
* Copyright (c) 2002-2023, Victor Antonovich (v.antonovich@gmail.com)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "util.h"
#include <ctype.h>
#include <string.h>
/**
* Removes leading whitespace from the string.
* Parameters: s - the string to be trimmed.
* Return: a pointer to the first non-whitespace character in string.
*/
char *
util_ltrim(const char *s)
{
while (*s && isspace((unsigned char )(*s)))
s++;
return (char *) s;
}
/**
* Removes trailing whitespace from the string.
* The first trailing whitespace is replaced with a NUL-terminator
* in the given string.
* Parameters: s - the string to be trimmed.
* Return: a pointer to the string.
*/
char *
util_rtrim(char *s)
{
char *p = s + strlen(s);
while (p > s && isspace((unsigned char )(*--p)))
*p = '\0';
return s;
}
/**
* Removes leading and trailing whitespace from the string.
* The first trailing whitespace is replaced with a NUL-terminator
* in the given string.
* Parameters: s - the string to be trimmed.
* Return: a pointer to the first non-whitespace character in string.
*/
char *
util_trim(char *s)
{
if (!strlen(s))
return s;
return util_ltrim(util_rtrim(s));
}

View File

@ -1,21 +1,21 @@
/*
* OpenMODBUS/TCP to RS-232/485 MODBUS RTU gateway
*
* crc16.h - Cyclic Redundant Checks calculating
* util.h - utility functions
*
* Copyright (c) 2002-2023, Victor Antonovich (v.antonovich@gmail.com)
*
* Copyright (c) 2002-2003, 2013, Victor Antonovich (v.antonovich@gmail.com)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -27,15 +27,13 @@
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: crc16.h,v 1.3 2015/02/25 10:33:57 kapyar Exp $
*/
#ifndef _CRC_H
#define _CRC_H
#include "globals.h"
#ifndef _UTIL_H
#define _UTIL_H
unsigned short crc16(unsigned char *buf, unsigned int bsize);
char *util_ltrim(const char *s);
char *util_rtrim(char *s);
char *util_trim(char *s);
#endif /*_CRC_H */
#endif /* _UTIL_H */

View File

@ -1,9 +1,11 @@
[Unit]
Description=Modbus TCP to Modbus RTU (RS-232/485) gateway.
Requires=network.target
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/mbusd -d -v2 -L - -c @CMAKE_INSTALL_FULL_SYSCONFDIR@/mbusd/mbusd-%i.conf -p /dev/%i
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/mbusd -d -v2 -L - -c @CMAKE_INSTALL_FULL_SYSCONFDIR@/mbusd/mbusd-%i.conf -p /dev/%I
Restart=on-failure
RestartSec=1
StandardOutput=journal

View File

@ -14,7 +14,7 @@ import logging
#---------------------------------------------------------------------------#
#from pymodbus.server.async import StartTcpServer
#from pymodbus.server.async import StartUdpServer
from pymodbus.server.asynchronous import StartSerialServer
from pymodbus.server import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
@ -92,7 +92,7 @@ class ModbusSerialServer:
hr = ModbusSequentialDataBlock(0, [0]*8), # holding regs
ir = ModbusSequentialDataBlock(0, list(range(8))), # input regs
zero_mode=True) # request(0-7) will map to the address (0-7)
context = ModbusServerContext(slaves=store, single=True)
context = ModbusServerContext(slaves={1: store}, single=False)
#---------------------------------------------------------------------------#
# initialize the server information
@ -112,7 +112,7 @@ class ModbusSerialServer:
#---------------------------------------------------------------------------#
#StartTcpServer(context, identity=identity, address=("localhost", 5020))
#StartUdpServer(context, identity=identity, address=("localhost", 502))
StartSerialServer(context, identity=identity, port=self.serialPort, baudrate=19200, framer=framer)
StartSerialServer(context=context, identity=identity, port=self.serialPort, baudrate=19200, framer=framer, broadcast_enable=True)
#StartSerialServer(context, identity=identity, port='/dev/pts/3', framer=ModbusAsciiFramer)
p = None

View File

@ -8,7 +8,7 @@ from subprocess import Popen, PIPE, STDOUT
from os.path import isfile
from time import sleep
from pymodbus.client.sync import ModbusTcpClient
from pymodbus.client import ModbusTcpClient
from pymodbus.pdu import ExceptionResponse
from pymodbus.bit_read_message import ReadDiscreteInputsResponse, ReadCoilsResponse
from pymodbus.bit_write_message import WriteMultipleCoilsResponse, WriteSingleCoilResponse
@ -33,7 +33,7 @@ class TestModbusRequests(unittest.TestCase):
cls.mbs.start()
cls.log.debug("3. run mbusd to be tested with the binary:%s" % MBUSD_BINARY)
cls.mbusd_main = Popen([MBUSD_BINARY, "-d", "-L", "-v9", "-p/tmp/pts0", "-s19200", "-P" + str(MBUSD_PORT)],
cls.mbusd_main = Popen([MBUSD_BINARY, "-d", "-L/tmp/mbusd.log", "-v9", "-p/tmp/pts0", "-s19200", "-P" + str(MBUSD_PORT)],
stdout=PIPE, stderr=STDOUT)
# wait a little bit for mbusd to come up
# alternatively do a poll for the socket
@ -41,7 +41,7 @@ class TestModbusRequests(unittest.TestCase):
sleep(5)
cls.log.debug("4. connect the modbus TCP client to mbusd")
cls.client = ModbusTcpClient('127.0.0.1', port=MBUSD_PORT)
cls.client = ModbusTcpClient('127.0.0.1', port=MBUSD_PORT, broadcast_enable=True)
cls.client.connect()
@classmethod
@ -62,35 +62,35 @@ class TestModbusRequests(unittest.TestCase):
bits = [random.randrange(2)>0 for i in range(8)]
# 15 Write Multiple Coils
result = self.client.write_coils(0, bits)
result = self.client.write_coils(0, bits, slave=1)
self.assertIsInstance(result, WriteMultipleCoilsResponse, result)
self.assertEqual(result.address, 0, result)
self.assertEqual(result.count, 8, result)
# 01 Read Coils
result = self.client.read_coils(0, 8)
result = self.client.read_coils(0, 8, slave=1)
self.assertIsInstance(result, ReadCoilsResponse, result)
self.assertEqual(result.bits, bits, result)
# 05 Write Single Coil
bit1 = not bits[0]
result = self.client.write_coil(0, bit1)
result = self.client.write_coil(0, bit1, slave=1)
self.assertIsInstance(result, WriteSingleCoilResponse, result)
self.assertEqual(result.address, 0, result)
self.assertEqual(result.value, bit1, result)
result = self.client.read_coils(0, 1)
result = self.client.read_coils(0, 1, slave=1)
self.assertIsInstance(result, ReadCoilsResponse, result)
self.assertEqual(result.bits[0], bit1, result)
def test_discreteInputs(self):
# 02 Read Discrete Inputs
result = self.client.read_discrete_inputs(0, 8)
result = self.client.read_discrete_inputs(0, 8, slave=1)
self.assertIsInstance(result, ReadDiscreteInputsResponse, result)
self.assertEqual(result.bits, [True]*8, result)
def test_inputRegisters(self):
# 04 Read Input Registers
result = self.client.read_input_registers(0, 8)
result = self.client.read_input_registers(0, 8, slave=1)
self.assertIsInstance(result, ReadInputRegistersResponse, result)
self.assertEqual(result.registers, list(range(8)), result)
@ -98,32 +98,42 @@ class TestModbusRequests(unittest.TestCase):
registers = [random.randrange(8) for i in range(8)]
# 16 Write Multiple Holding Registers
result = self.client.write_registers(0, registers)
result = self.client.write_registers(0, registers, slave=1)
self.assertIsInstance(result, WriteMultipleRegistersResponse, result)
self.assertEqual(result.address, 0, result)
self.assertEqual(result.count, 8, result)
# 03 Read Multiple Holding Registers
result = self.client.read_holding_registers(0, 8)
result = self.client.read_holding_registers(0, 8, slave=1)
self.assertIsInstance(result, ReadHoldingRegistersResponse, result)
self.assertEqual(result.registers, registers, result)
# 06 Write Single Holding Register
register1 = (registers[0] + 1) % 65535
result = self.client.write_register(0, register1)
result = self.client.write_register(0, register1, slave=1)
self.assertIsInstance(result, WriteSingleRegisterResponse, result)
self.assertEqual(result.address, 0, result)
self.assertEqual(result.value, register1, result)
result = self.client.read_holding_registers(0, 1)
result = self.client.read_holding_registers(0, 1, slave=1)
self.assertIsInstance(result, ReadHoldingRegistersResponse, result)
self.assertEqual(result.registers[0], register1, result)
def test_exception(self):
result = self.client.write_coil(1000, False) # invalid address 1000
result = self.client.write_coil(1000, False, slave=1) # invalid address 1000
self.assertIsInstance(result, ExceptionResponse, result)
self.assertEqual(result.original_code, 5, result) # fc05 Write Single Coil
self.assertEqual(result.exception_code, 2, result) # Illegal Data Address
def test_broadcast(self):
registers = [random.randrange(8) for i in range(8)]
# 16 Write Multiple Holding Registers
result = self.client.write_registers(0, registers, slave=0)
# 03 Read Multiple Holding Registers
result = self.client.read_holding_registers(0, 8, slave=1)
self.assertIsInstance(result, ReadHoldingRegistersResponse, result)
self.assertEqual(result.registers, registers, result)
if __name__ == '__main__':
stdout_handler = logging.StreamHandler(sys.stdout)
logging.basicConfig(level=logging.DEBUG,