mirror of
https://github.com/3cky/mbusd.git
synced 2024-09-27 03:53:02 +02:00
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4a05446732 | ||
|
ea8e43b1df | ||
|
3c37407afd | ||
|
f013ed12c6 | ||
|
2c462b7560 | ||
|
ac927aa19a | ||
|
91f8e35eae | ||
|
0cf2cb0c56 | ||
|
9cee2351f4 | ||
|
a5239439e3 | ||
|
5b59f5b92f | ||
|
3bed375c44 | ||
|
2b32d097d2 | ||
|
8591a2f1c1 | ||
|
d6e6b74a53 | ||
|
7475bdf141 | ||
|
68f988051c | ||
|
b03d50b58c | ||
|
20c87df994 | ||
|
4ee7e8a834 | ||
|
b07681842f | ||
|
b464ec9676 | ||
|
6517dea57f | ||
|
0f7a8c50fb | ||
|
18fb6d086e | ||
|
4e5c7e3781 | ||
|
874ce63806 | ||
|
06ccb29384 | ||
|
7c268600b6 | ||
|
55889a7292 | ||
|
cb9576a5a0 | ||
|
16e68947ce | ||
|
bf98aa6382 | ||
|
dd71375628 | ||
|
af0534d18f | ||
|
868d605f5d | ||
|
1efb812013 | ||
|
4cd68c1bb8 | ||
|
a2272c5793 | ||
|
6a95a732d4 | ||
|
b22ffa0856 | ||
|
432b7a1a8f | ||
|
252aa45e5d | ||
|
461ce8baa0 | ||
|
ad58141b87 | ||
|
c6301d4250 | ||
|
3742d8d8c5 | ||
|
32344e5f94 | ||
|
d24b257c13 | ||
|
965ff443c8 | ||
|
4bf9d94713 | ||
|
5eb74fa568 | ||
|
f5becef211 | ||
|
0c1dcb727d | ||
|
88916fe82d | ||
|
2aa063db79 | ||
|
2465860496 | ||
|
8e266fc5e9 | ||
|
2282376417 |
17
.github/stale.yml
vendored
Normal file
17
.github/stale.yml
vendored
Normal 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
44
.github/workflows/build.yml
vendored
Normal 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==3.6.9
|
||||
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
63
.github/workflows/docker-publish.yml
vendored
Normal 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
26
.gitignore
vendored
@ -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
|
18
.travis.yml
18
.travis.yml
@ -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
|
33
CHANGELOG.md
33
CHANGELOG.md
@ -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
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
project(mbusd VERSION 0.4.0)
|
||||
project(mbusd VERSION 0.5.3 LANGUAGES C)
|
||||
|
||||
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
10
Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
FROM 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"]
|
2
LICENSE
2
LICENSE
@ -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.
|
||||
|
||||
|
72
README.md
72
README.md
@ -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:
|
||||
-------------
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
98
src/cfg.c
98
src/cfg.c
@ -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))
|
||||
|
12
src/cfg.h
12
src/cfg.h
@ -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 */
|
||||
|
144
src/conn.c
144
src/conn.c
@ -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,6 +559,27 @@ conn_loop(void)
|
||||
#ifdef DEBUG
|
||||
logw(7, "tty: request written (total %d bytes)", tty.txlen);
|
||||
#endif
|
||||
if (!tty.txbuf[0])
|
||||
{
|
||||
#ifdef DEBUG
|
||||
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:
|
||||
@ -605,12 +604,13 @@ conn_loop(void)
|
||||
}
|
||||
if (tty.rxlen > TTY_BUFSIZE)
|
||||
tty.rxlen = TTY_BUFSIZE;
|
||||
tty.timer += DV(tty.rxlen, tty.speed);
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(tty.fd, &sdsetrd))
|
||||
{
|
||||
@ -623,13 +623,22 @@ 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
|
||||
@ -673,9 +682,18 @@ conn_loop(void)
|
||||
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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
79
src/crc16.c
79
src/crc16.c
@ -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;
|
||||
}
|
@ -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)
|
||||
|
@ -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);
|
||||
|
115
src/main.c
115
src/main.c
@ -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"
|
||||
#ifdef TRXCTL
|
||||
" -t : enable RTS RS-485 data direction control using RTS\n"
|
||||
" -y : enable RTS RS-485 data direction control using sysfs file, active transmit\n"
|
||||
" -Y : enable RTS RS-485 data direction control using sysfs file, active receive\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, 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,7 +225,10 @@ 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;
|
||||
@ -214,27 +239,39 @@ main(int argc, char *argv[])
|
||||
strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE);
|
||||
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;
|
||||
|
55
src/modbus.c
55
src/modbus.c
@ -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));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -35,7 +35,6 @@
|
||||
#define _MODBUS_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "crc16.h"
|
||||
|
||||
/*
|
||||
* Macro for accessing data in MODBUS frame
|
||||
|
@ -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;
|
||||
}
|
||||
|
83
src/sock.c
83
src/sock.c
@ -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);
|
||||
|
||||
/* bind socket to given address and port */
|
||||
if (bind(server_s, (struct sockaddr *)&server_sockaddr,
|
||||
sizeof(server_sockaddr)) == -1)
|
||||
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)
|
||||
@ -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);
|
||||
}
|
||||
|
12
src/sock.h
12
src/sock.h
@ -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
|
||||
|
25
src/state.c
25
src/state.c
@ -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
|
||||
|
144
src/tty.c
144
src/tty.c
@ -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;
|
||||
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 = trxcntl;
|
||||
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,13 +465,16 @@ 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_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) {
|
||||
@ -385,13 +482,16 @@ tty_set_rts(int fd)
|
||||
}
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
34
src/tty.h
34
src/tty.h
@ -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
80
src/util.c
Normal 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));
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
/*
|
||||
* OpenMODBUS/TCP to RS-232/485 MODBUS RTU gateway
|
||||
*
|
||||
* crc16.h - Cyclic Redundant Checks calculating
|
||||
* util.h - utility functions
|
||||
*
|
||||
* Copyright (c) 2002-2003, 2013, Victor Antonovich (v.antonovich@gmail.com)
|
||||
* 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
|
||||
@ -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
|
||||
#ifndef _UTIL_H
|
||||
#define _UTIL_H
|
||||
|
||||
#include "globals.h"
|
||||
char *util_ltrim(const char *s);
|
||||
char *util_rtrim(char *s);
|
||||
char *util_trim(char *s);
|
||||
|
||||
unsigned short crc16(unsigned char *buf, unsigned int bsize);
|
||||
|
||||
#endif /*_CRC_H */
|
||||
#endif /* _UTIL_H */
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user