Add support for read configuration from file

This commit is contained in:
Victor Antonovich 2017-11-29 13:39:40 +03:00
parent 7fb5309551
commit 3ea208491a
12 changed files with 514 additions and 131 deletions

View File

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?> <?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings"> <storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="cdt.managedbuild.toolchain.gnu.base.1826992774"> <cconfiguration id="cdt.managedbuild.toolchain.gnu.base.1826992774">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.1826992774" moduleId="org.eclipse.cdt.core.settings" name="Default"> <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.1826992774" moduleId="org.eclipse.cdt.core.settings" name="Default">
@ -16,17 +14,28 @@
</extensions> </extensions>
</storageModule> </storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0"> <storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration buildProperties="" id="cdt.managedbuild.toolchain.gnu.base.1826992774" name="Default" parent="org.eclipse.cdt.build.core.emptycfg"> <configuration buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.base.1826992774" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="cdt.managedbuild.toolchain.gnu.base.1826992774.1618506567" name="/" resourcePath=""> <folderInfo id="cdt.managedbuild.toolchain.gnu.base.1826992774.1618506567" name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.base.1143041813" name="cdt.managedbuild.toolchain.gnu.base" superClass="cdt.managedbuild.toolchain.gnu.base"> <toolChain id="cdt.managedbuild.toolchain.gnu.base.1143041813" name="cdt.managedbuild.toolchain.gnu.base" superClass="cdt.managedbuild.toolchain.gnu.base">
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.target.gnu.platform.base.781693864" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/> <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.target.gnu.platform.base.781693864" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/>
<builder id="cdt.managedbuild.target.gnu.builder.base.751022712" managedBuildOn="false" name="Gnu Make Builder.Default" superClass="cdt.managedbuild.target.gnu.builder.base"/> <builder id="cdt.managedbuild.target.gnu.builder.base.751022712" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="cdt.managedbuild.target.gnu.builder.base"/>
<tool id="cdt.managedbuild.tool.gnu.archiver.base.1388164087" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/> <tool id="cdt.managedbuild.tool.gnu.archiver.base.1388164087" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.1875165406" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base"/> <tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.1875165406" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base">
<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1454341581" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base"/> <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.437218346" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1454341581" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base">
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.722267304" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.linker.base.2010478517" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/> <tool id="cdt.managedbuild.tool.gnu.c.linker.base.2010478517" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.970100266" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base"/> <tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.970100266" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base">
<tool id="cdt.managedbuild.tool.gnu.assembler.base.27610334" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base"/> <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.177254843" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool id="cdt.managedbuild.tool.gnu.assembler.base.27610334" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base">
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.285918871" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
</toolChain> </toolChain>
</folderInfo> </folderInfo>
</configuration> </configuration>
@ -37,8 +46,15 @@
<storageModule moduleId="cdtBuildSystem" version="4.0.0"> <storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="mbus.null.914501471" name="mbus"/> <project id="mbus.null.914501471" name="mbus"/>
</storageModule> </storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope"/>
<storageModule moduleId="scannerConfiguration"> <storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/> <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1826992774;cdt.managedbuild.toolchain.gnu.base.1826992774.1618506567;cdt.managedbuild.tool.gnu.cpp.compiler.base.1875165406;cdt.managedbuild.tool.gnu.cpp.compiler.input.437218346">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1826992774;cdt.managedbuild.toolchain.gnu.base.1826992774.1618506567;cdt.managedbuild.tool.gnu.c.compiler.base.1454341581;cdt.managedbuild.tool.gnu.c.compiler.input.722267304">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
</storageModule> </storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
</cproject> </cproject>

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ Makefile
*.log *.log
*.in~ *.in~
systemd-units/mbusd@.service systemd-units/mbusd@.service
*.conf

View File

@ -4,13 +4,17 @@ ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src doc SUBDIRS = src doc
mbusddocdir = ${prefix}/doc/mbusd mbusddocdir = ${docdir}
mbusddoc_DATA = \ mbusddoc_DATA = \
README.md\ README.md\
CHANGELOG.md\ CHANGELOG.md\
LICENSE LICENSE
EXTRA_DIST = $(mbusddoc_DATA) mbusdconfdir = ${mbusddocdir}
mbusdconf_DATA = \
conf/mbusd.conf.example
EXTRA_DIST = $(mbusddoc_DATA) $(mbusdconf_DATA)
# Copy all the spec files. Of cource, only one is actually used. # Copy all the spec files. Of cource, only one is actually used.
dist-hook: dist-hook:
@ -34,4 +38,3 @@ $(systemdsystemunit_DATA):
distclean-local:: distclean-local::
-$(RM) $(systemdsystemunit_DATA) -$(RM) $(systemdsystemunit_DATA)
endif endif

View File

@ -158,9 +158,9 @@ am__uninstall_files_from_dir = { \
|| { echo " ( cd '$$dir' && rm -f" $$files ")"; \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
$(am__cd) "$$dir" && rm -f $$files; }; \ $(am__cd) "$$dir" && rm -f $$files; }; \
} }
am__installdirs = "$(DESTDIR)$(mbusddocdir)" \ am__installdirs = "$(DESTDIR)$(mbusdconfdir)" \
"$(DESTDIR)$(systemdsystemunitdir)" "$(DESTDIR)$(mbusddocdir)" "$(DESTDIR)$(systemdsystemunitdir)"
DATA = $(mbusddoc_DATA) $(systemdsystemunit_DATA) DATA = $(mbusdconf_DATA) $(mbusddoc_DATA) $(systemdsystemunit_DATA)
RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
distclean-recursive maintainer-clean-recursive distclean-recursive maintainer-clean-recursive
am__recursive_targets = \ am__recursive_targets = \
@ -367,13 +367,17 @@ top_builddir = @top_builddir@
top_srcdir = @top_srcdir@ top_srcdir = @top_srcdir@
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src doc SUBDIRS = src doc
mbusddocdir = ${prefix}/doc/mbusd mbusddocdir = ${docdir}
mbusddoc_DATA = \ mbusddoc_DATA = \
README.md\ README.md\
CHANGELOG.md\ CHANGELOG.md\
LICENSE LICENSE
EXTRA_DIST = $(mbusddoc_DATA) mbusdconfdir = ${mbusddocdir}
mbusdconf_DATA = \
conf/mbusd.conf.example
EXTRA_DIST = $(mbusddoc_DATA) $(mbusdconf_DATA)
# systemd # systemd
AM_DISTCHECK_CONFIGURE_FLAGS = \ AM_DISTCHECK_CONFIGURE_FLAGS = \
@ -443,6 +447,27 @@ clean-libtool:
distclean-libtool: distclean-libtool:
-rm -f libtool config.lt -rm -f libtool config.lt
install-mbusdconfDATA: $(mbusdconf_DATA)
@$(NORMAL_INSTALL)
@list='$(mbusdconf_DATA)'; test -n "$(mbusdconfdir)" || list=; \
if test -n "$$list"; then \
echo " $(MKDIR_P) '$(DESTDIR)$(mbusdconfdir)'"; \
$(MKDIR_P) "$(DESTDIR)$(mbusdconfdir)" || exit 1; \
fi; \
for p in $$list; do \
if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
echo "$$d$$p"; \
done | $(am__base_list) | \
while read files; do \
echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(mbusdconfdir)'"; \
$(INSTALL_DATA) $$files "$(DESTDIR)$(mbusdconfdir)" || exit $$?; \
done
uninstall-mbusdconfDATA:
@$(NORMAL_UNINSTALL)
@list='$(mbusdconf_DATA)'; test -n "$(mbusdconfdir)" || list=; \
files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
dir='$(DESTDIR)$(mbusdconfdir)'; $(am__uninstall_files_from_dir)
install-mbusddocDATA: $(mbusddoc_DATA) install-mbusddocDATA: $(mbusddoc_DATA)
@$(NORMAL_INSTALL) @$(NORMAL_INSTALL)
@list='$(mbusddoc_DATA)'; test -n "$(mbusddocdir)" || list=; \ @list='$(mbusddoc_DATA)'; test -n "$(mbusddocdir)" || list=; \
@ -788,7 +813,7 @@ check: check-recursive
all-am: Makefile $(DATA) config.h all-am: Makefile $(DATA) config.h
installdirs: installdirs-recursive installdirs: installdirs-recursive
installdirs-am: installdirs-am:
for dir in "$(DESTDIR)$(mbusddocdir)" "$(DESTDIR)$(systemdsystemunitdir)"; do \ for dir in "$(DESTDIR)$(mbusdconfdir)" "$(DESTDIR)$(mbusddocdir)" "$(DESTDIR)$(systemdsystemunitdir)"; do \
test -z "$$dir" || $(MKDIR_P) "$$dir"; \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
done done
install: install-recursive install: install-recursive
@ -844,7 +869,8 @@ info: info-recursive
info-am: info-am:
install-data-am: install-mbusddocDATA install-systemdsystemunitDATA install-data-am: install-mbusdconfDATA install-mbusddocDATA \
install-systemdsystemunitDATA
install-dvi: install-dvi-recursive install-dvi: install-dvi-recursive
@ -890,7 +916,8 @@ ps: ps-recursive
ps-am: ps-am:
uninstall-am: uninstall-mbusddocDATA uninstall-systemdsystemunitDATA uninstall-am: uninstall-mbusdconfDATA uninstall-mbusddocDATA \
uninstall-systemdsystemunitDATA
.MAKE: $(am__recursive_targets) all install-am install-strip .MAKE: $(am__recursive_targets) all install-am install-strip
@ -905,12 +932,13 @@ uninstall-am: uninstall-mbusddocDATA uninstall-systemdsystemunitDATA
install install-am install-data install-data-am install-dvi \ install install-am install-data install-data-am install-dvi \
install-dvi-am install-exec install-exec-am install-html \ install-dvi-am install-exec install-exec-am install-html \
install-html-am install-info install-info-am install-man \ install-html-am install-info install-info-am install-man \
install-mbusddocDATA install-pdf install-pdf-am install-ps \ install-mbusdconfDATA install-mbusddocDATA install-pdf \
install-ps-am install-strip install-systemdsystemunitDATA \ install-pdf-am install-ps install-ps-am install-strip \
installcheck installcheck-am installdirs installdirs-am \ install-systemdsystemunitDATA installcheck installcheck-am \
maintainer-clean maintainer-clean-generic mostlyclean \ installdirs installdirs-am maintainer-clean \
mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ maintainer-clean-generic mostlyclean mostlyclean-generic \
tags tags-am uninstall uninstall-am uninstall-mbusddocDATA \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
uninstall-am uninstall-mbusdconfDATA uninstall-mbusddocDATA \
uninstall-systemdsystemunitDATA uninstall-systemdsystemunitDATA
.PRECIOUS: Makefile .PRECIOUS: Makefile

View File

@ -47,20 +47,15 @@ Usage:
-h Usage help. -h Usage help.
-d Instruct mbusd not to fork itself (non-daemonize). -d Instruct mbusd not to fork itself (non-daemonize).
-t Enable RTS RS-485 data direction control (if not disabled while compile). -L logfile
-y file Specifies log file name ('-' for logging to STDOUT only, default is /var/log/mbusd.log).
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
-v level -v level
Specifies log verbosity level (0 for errors only, 1 for warnings Specifies log verbosity level (0 for errors only, 1 for warnings
and 2 for also information messages.) If mbusd was compiled in debug mode, 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 valid log levels are up to 9, where log levels above 2 forces
logging of information about additional internal events. logging of information about additional internal events.
-L logfile -c cfgfile
Specifies log file name ('-' for logging to STDOUT only, default is /var/log/mbusd.log). Read configuration from cfgfile.
-p device -p device
Specifies serial port device name. Specifies serial port device name.
-s speed -s speed
@ -69,6 +64,13 @@ Usage:
Specifies serial port mode (like 8N1). Specifies serial port mode (like 8N1).
-P port -P port
Specifies TCP port number (default 502). Specifies TCP port number (default 502).
-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
-C maxconn -C maxconn
Specifies maximum number of simultaneous TCP connections. Specifies maximum number of simultaneous TCP connections.
-N retries -N retries
@ -82,16 +84,28 @@ Usage:
Please note running **mbusd** on default Modbus TCP port (502) requires root privileges! Please note running **mbusd** on default Modbus TCP port (502) requires root privileges!
Configuration file:
-------------------
**mbusd** can read the configuration from a file specified by `-c` command line flag.
Please see [example configuration file](conf/mbusd.conf.example)
for complete list of available configuration options.
systemd: systemd:
--------------- ---------------
**mbusd** has [systemd](https://wiki.archlinux.org/index.php/systemd) support. The build system detects whether the system has systemd after which `sudo make install` installs the `mbusd@.service` file on systems with systemd active. **mbusd** has [systemd](https://wiki.archlinux.org/index.php/systemd) support.
The build system detects whether the system has systemd after which `sudo make install`
installs the `mbusd@.service` file on systems with systemd active.
The **mbusd** service can be started via: The **mbusd** service can be started via:
# systemctl start mbusd@<serial port>.service # systemctl start mbusd@<serial port>.service
where `<serial port>` is serial port device name (like `ttyUSB0`). where `<serial port>` is serial port device name (like `ttyUSB0`).
**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.
To see the **mbusd** service status: To see the **mbusd** service status:
# systemctl status mbusd@<serial port>.service # systemctl status mbusd@<serial port>.service
@ -106,12 +120,6 @@ To start the **mbusd** service on system boot:
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)
The checked in `mbusd@.service` starts **mbusd** equivalent to the following command:
# $PREFIX/bin/mbusd -p /dev/<serial port> -s 9600 -m 8N1 -P 502 -d -v2
Feel free to modify the service file locally with your own desired configuration.
Reporting bugs: Reporting bugs:
--------------- ---------------
@ -119,7 +127,8 @@ Please file [issue](https://github.com/3cky/mbusd/issues) with attached debug lo
# mbusd -L/tmp/mbusd.log -p /dev/ttyUSB0 -s 9600 -P 502 -d -v9 # mbusd -L/tmp/mbusd.log -p /dev/ttyUSB0 -s 9600 -P 502 -d -v9
Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report),
please do not send bug reports via personal email.
Contributing: Contributing:
------------- -------------
@ -146,6 +155,9 @@ Andrew Denysenko (<nitr0@seti.kr.ua>):
James Jarvis (<jj@aprsworld.com>): James Jarvis (<jj@aprsworld.com>):
- file based RS-485 data direction control - file based RS-485 data direction control
Luuk Loeffen (<luukloeffen@hotmail.com>):
- systemd support
License: License:
-------- --------

44
conf/mbusd.conf.example Normal file
View File

@ -0,0 +1,44 @@
#############################################
# #
# Sample configuration file for mbusd #
# #
#############################################
########## Serial port settings #############
# Serial port device name
device = /dev/ttyS0
# Serial port speed
speed = 9600
# Serial port mode
mode = 8n1
# RS-485 data direction control type (addc, rts, sysfs_0, sysfs_1)
trx_control = addc
# Sysfs file to use to control data direction
# trx_sysfile =
############# TCP port settings #############
# TCP server port number
port = 502
# Maximum number of simultaneous TCP connections
maxconn = 32
# Connection timeout value in seconds
timeout = 60
######### Request/response settings #########
# Maximum number of request retries
retries = 3
# Pause between requests in milliseconds
pause = 100
# Response wait time in milliseconds
wait = 500

View File

@ -5,21 +5,23 @@ mbusd \- MODBUS/TCP to MODBUS/RTU gateway.
.B mbusd .B mbusd
.RB [ -h ] .RB [ -h ]
.RB [ -d ] .RB [ -d ]
.RB [ -t ]
.RB [ -y
.IR file ]
.RB [ -Y
.IR file ]
.RB [ -v
.IR level ]
.RB [ -L .RB [ -L
.IR logfile ] .IR logfile ]
.RB [ -v
.IR level ]
.RB [ -c
.IR cfgfile ]
.RB [ -p .RB [ -p
.IR device ] .IR device ]
.RB [ -s .RB [ -s
.IR speed ] .IR speed ]
.RB [ -m .RB [ -m
.IR mode ] .IR mode ]
.RB [ -t ]
.RB [ -y
.IR file ]
.RB [ -Y
.IR file ]
.RB [ -P .RB [ -P
.IR port ] .IR port ]
.RB [ -C .RB [ -C
@ -39,22 +41,16 @@ mbusd \- MODBUS/TCP to MODBUS/RTU gateway.
Usage help. Usage help.
.IP \fB-d\fR .IP \fB-d\fR
Instruct \fImbusd\fR not to fork itself (non-daemonize). Instruct \fImbusd\fR not to fork itself (non-daemonize).
.IP \fB-t\fR .IP "\fB-L \fIlogfile\fR"
Enable RTS RS-485 data direction control (if not disabled while compile). Specifies log file name ('-' for logging to STDOUT only, default is /var/log/mbusd.log).
.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
.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
.IP "\fB-v \fIlevel\fR" .IP "\fB-v \fIlevel\fR"
Specifies log verbosity level. 0 enables logging of errors only, Specifies log verbosity level. 0 enables logging of errors only,
1 also enables warnings and 2 enables information messages. 1 also enables warnings and 2 enables information messages.
If \fImbusd\fR was compiled in debug mode, valid log levels are up to 9, If \fImbusd\fR was compiled in debug mode, valid log levels are up to 9,
where log levels above 2 forces logging of information about additional where log levels above 2 forces logging of information about additional
internal events. internal events.
.IP "\fB-L \fIlogfile\fR" .IP "\fB-c \fIcfgfile\fR"
Specifies log file name ('-' for logging to STDOUT only, default is /var/log/mbusd.log). Read configuration from cfgfile.
.IP "\fB-p \fIdevice\fR" .IP "\fB-p \fIdevice\fR"
Specifies serial port device name. Specifies serial port device name.
.IP "\fB-s \fIspeed\fR" .IP "\fB-s \fIspeed\fR"
@ -63,6 +59,14 @@ Specifies serial port speed.
Specifies serial port mode (like 8N1). Specifies serial port mode (like 8N1).
.IP "\fB-P \fIport\fR" .IP "\fB-P \fIport\fR"
Specifies TCP port number. Specifies TCP port number.
.IP \fB-t\fR
Enable RTS RS-485 data direction control (if not disabled while compile).
.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
.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
.IP "\fB-C \fImaxconn\fR" .IP "\fB-C \fImaxconn\fR"
Specifies maximum number of simultaneous TCP connections. Specifies maximum number of simultaneous TCP connections.
.IP "\fB-N \fIretries\fR" .IP "\fB-N \fIretries\fR"

233
src/cfg.c
View File

@ -33,9 +33,23 @@
#include "cfg.h" #include "cfg.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.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
/* Global configuration storage variable */ /* Global configuration storage variable */
cfg_t cfg; cfg_t cfg;
/* Configuration error message */
char cfg_err[INTBUFSIZE + 1];
#define CFG_ERR(s, v) snprintf(cfg_err, INTBUFSIZE, s, v)
/* /*
* Setting up config defaults * Setting up config defaults
*/ */
@ -48,9 +62,10 @@ cfg_init(void)
#endif #endif
strncpy(cfg.ttyport, DEFAULT_PORT, INTBUFSIZE); strncpy(cfg.ttyport, DEFAULT_PORT, INTBUFSIZE);
cfg.ttyspeed = DEFAULT_SPEED; cfg.ttyspeed = DEFAULT_SPEED;
cfg.ttymode = DEFAULT_MODE; strncpy(cfg.ttymode, DEFAULT_MODE, INTBUFSIZE);
#ifdef TRXCTL #ifdef TRXCTL
cfg.trxcntl = TRX_ADDC; cfg.trxcntl = TRX_ADDC;
*cfg.trxcntl_file = '\0';
#endif #endif
cfg.serverport = DEFAULT_SERVERPORT; cfg.serverport = DEFAULT_SERVERPORT;
cfg.maxconn = DEFAULT_MAXCONN; cfg.maxconn = DEFAULT_MAXCONN;
@ -60,3 +75,217 @@ cfg_init(void)
cfg.resppause = DV(3, cfg.ttyspeed); cfg.resppause = DV(3, cfg.ttyspeed);
cfg.conntimeout = DEFAULT_CONNTIMEOUT; 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;
}
int
cfg_handle_param(char *name, char *value)
{
if (CFG_NAME_MATCH("device"))
{
strncpy(cfg.ttyport, value, INTBUFSIZE);
}
else if (CFG_NAME_MATCH("speed"))
{
cfg.ttyspeed = strtoul(value, NULL, 0);
}
else if (CFG_NAME_MATCH("mode"))
{
int mode_invalid;
if (strlen(value) != 3)
mode_invalid = 1;
else
{
char parity = toupper(value[1]);
mode_invalid = value[0] != '8' || (value[2] != '1' && value[2] != '2') ||
(parity != 'N' && parity != 'E' && parity != 'O');
}
if (mode_invalid)
{
CFG_ERR("invalid device mode: %s", value);
return 0;
}
strncpy(cfg.ttymode, value, INTBUFSIZE);
}
else if (CFG_NAME_MATCH("port"))
{
cfg.serverport = strtoul(value, NULL, 0);
}
else if (CFG_NAME_MATCH("maxconn"))
{
cfg.maxconn = strtoul(value, NULL, 0);
if (cfg.maxconn < 1 || cfg.maxconn > MAX_MAXCONN)
{
CFG_ERR("invalid maxconn value: %s", value);
return 0;
}
}
else if (CFG_NAME_MATCH("retries"))
{
cfg.maxtry = strtoul(value, NULL, 0);
if (cfg.maxtry > MAX_MAXTRY)
{
CFG_ERR("invalid retries value: %s", value);
return 0;
}
}
else if (CFG_NAME_MATCH("pause"))
{
cfg.rqstpause = strtoul(value, NULL, 0);
if (cfg.rqstpause < 1 || cfg.rqstpause > MAX_RQSTPAUSE)
{
CFG_ERR("invalid pause value: %s", value);
return 0;
}
}
else if (CFG_NAME_MATCH("wait"))
{
cfg.respwait = strtoul(value, NULL, 0);
if (cfg.respwait < 1 || cfg.respwait > MAX_RESPWAIT)
{
CFG_ERR("invalid wait value: %s", value);
return 0;
}
}
else if (CFG_NAME_MATCH("timeout"))
{
cfg.conntimeout = strtoul(value, NULL, 0);
if (cfg.conntimeout > MAX_CONNTIMEOUT)
return 0;
#ifdef TRXCTL
}
else if (CFG_NAME_MATCH("trx_control"))
{
if (CFG_VALUE_MATCH("addc"))
{
cfg.trxcntl = TRX_ADDC;
}
else if (CFG_VALUE_MATCH("rts"))
{
cfg.trxcntl = TRX_RTS;
}
else if (CFG_VALUE_MATCH("sysfs_0"))
{
cfg.trxcntl = TRX_SYSFS_0;
}
else if (CFG_VALUE_MATCH("sysfs_1"))
{
cfg.trxcntl = TRX_SYSFS_1;
}
else
{
/* Unknown TRX control mode */
CFG_ERR("unknown trx control mode: %s", value);
return 0;
}
}
else if (CFG_NAME_MATCH("trx_sysfile"))
{
strncpy(cfg.trxcntl_file, value, INTBUFSIZE);
#endif
#ifdef LOG
}
else if (CFG_NAME_MATCH("loglevel"))
{
cfg.dbglvl = (char)strtol(optarg, NULL, 0);
#endif
}
else {
/* Unknown parameter name */
CFG_ERR("unknown parameter: %s", name);
return 0;
}
return 1;
}
int
cfg_parse_file(void *file)
{
char *line;
char *start;
char *end;
char *name;
char *value;
int lineno = 0;
int error = 0;
*cfg_err = '\0';
line = (char *) malloc(CFG_MAX_LINE_LENGTH);
if (!line)
{
return -1;
}
while (fgets(line, CFG_MAX_LINE_LENGTH, file) != NULL)
{
lineno++;
start = cfg_ltrim(cfg_rtrim(line));
if (*start == '#')
{
/* skip comment */
continue;
}
else if (*start)
{
/* parse `name=value` pair */
for (end = start; *end && *end != '='; end++);
if (*end == '=')
{
*end = '\0';
name = cfg_rtrim(start);
value = cfg_ltrim(cfg_rtrim(end + 1));
/* handle name/value pair */
if (!cfg_handle_param(name, value))
{
error = lineno;
break;
}
}
else
{
/* no '=' found on config line */
error = lineno;
CFG_ERR("can't parse line: %s", start);
break;
}
}
}
free(line);
return error;
}
int
cfg_read_file(const char *filename)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = cfg_parse_file(file);
fclose(file);
return error;
}

View File

@ -53,7 +53,7 @@ typedef struct
/* tty speed */ /* tty speed */
int ttyspeed; int ttyspeed;
/* tty mode */ /* tty mode */
char *ttymode; char ttymode[INTBUFSIZE + 1];
/* trx control type (0 - ADDC, 1 - by RTS, 2 - by sysfs GPIO with 1 activating transmit, 3 - by sysfs GPIO with 0 activating transmit) */ /* 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; int trxcntl;
/* trx control sysfs file */ /* trx control sysfs file */
@ -76,6 +76,8 @@ typedef struct
/* Prototypes */ /* Prototypes */
extern cfg_t cfg; extern cfg_t cfg;
extern char cfg_err[];
void cfg_init(void); void cfg_init(void);
int cfg_read_file(const char *filename);
#endif /* _CFG_H */ #endif /* _CFG_H */

View File

@ -53,6 +53,31 @@
#define DEFAULT_RESPWAIT 500 #define DEFAULT_RESPWAIT 500
#define DEFAULT_CONNTIMEOUT 60 #define DEFAULT_CONNTIMEOUT 60
/* Max simultaneous TCP connections to server */
#ifndef MAX_MAXCONN
# define MAX_MAXCONN 128
#endif
/* Max RTU device request retries */
#ifndef MAX_MAXTRY
# define MAX_MAXTRY 15
#endif
/* Max RTU device pause between requests, in msecs */
#ifndef MAX_RQSTPAUSE
# define MAX_RQSTPAUSE 10000
#endif
/* Max RTU device response wait, in msecs */
#ifndef MAX_RESPWAIT
# define MAX_RESPWAIT 10000
#endif
/* Max connection timeout, in secs */
#ifndef MAX_CONNTIMEOUT
# define MAX_CONNTIMEOUT 1000
#endif
#define CRCSIZE 2 /* size (in bytes) of CRC */ #define CRCSIZE 2 /* size (in bytes) of CRC */
#define HDRSIZE 6 /* size (in bytes) of header */ #define HDRSIZE 6 /* size (in bytes) of header */
#define BUFSIZE 256 /* size (in bytes) of MODBUS data */ #define BUFSIZE 256 /* size (in bytes) of MODBUS data */

View File

@ -100,62 +100,70 @@ void
usage(char *exename) usage(char *exename)
{ {
cfg_init(); cfg_init();
printf("%s-%s Copyright (C) 2002-2003, 2011, 2013-2016 Victor Antonovich <v.antonovich@gmail.com>, " printf("%s-%s Copyright (C) 2002-2003, 2011, 2013-2017 Victor Antonovich <v.antonovich@gmail.com>, "
"Andrew Denysenko <nitr0@seti.kr.ua>\n\n" "Andrew Denysenko <nitr0@seti.kr.ua>\n\n"
"Usage: %s [-h] [-d] " "Usage: %s [-h] [-d] "
#ifdef TRXCTL #ifdef LOG
"[-t] [-y sysfsfile] [-Y sysfsfile]\n" "[-L logfile] [-v level] "
#endif #endif
"[-v level] [-L logfile] [-p device] [-s speed] [-m mode] [-P port]\n" "[-c cfgfile] \n"
" [-C maxconn] [-N retries] [-R pause] [-W wait] [-T timeout]\n\n" " [-p device] [-s speed] [-m mode]\n"
#ifdef TRXCTL
" [-t] [-y sysfsfile] [-Y sysfsfile]\n"
#endif
" [-P port] [-C maxconn] [-N retries] [-R pause] [-W wait] [-T timeout]\n\n"
"Options:\n" "Options:\n"
" -h : this help\n" " -h : this help\n"
" -d : don't daemonize\n" " -d : don't daemonize\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"
#endif
#ifdef LOG #ifdef LOG
" -L logfile : set log file name (default %s%s, \n"
" '-' for logging to STDOUT only)\n"
#ifdef DEBUG #ifdef DEBUG
" -v level : set log level (0-9, default %d, 0 - errors only)\n" " -v level : set log level (0-9, default %d, 0 - errors only)\n"
#else #else
" -v level : set log level (0-2, default %d, 0 - errors only)\n" " -v level : set log level (0-2, default %d, 0 - errors only)\n"
#endif #endif
" -L logfile : set log file name (default %s%s, \n"
" '-' for logging to STDOUT only)\n"
#endif #endif
" -c cfgfile : read configuration from cfgfile\n"
" -p device : set serial port device name (default %s)\n" " -p device : set serial port device name (default %s)\n"
" -s speed : set serial port speed (default %d)\n" " -s speed : set serial port speed (default %d)\n"
" -m mode : set serial port mode (default %s)\n" " -m mode : set serial port mode (default %s)\n"
" -P port : set TCP server port number (default %d)\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"
#endif
" -C maxconn : set maximum number of simultaneous TCP connections\n" " -C maxconn : set maximum number of simultaneous TCP connections\n"
" (1-128, default %d)\n" " (1-%d, default %d)\n"
" -N retries : set maximum number of request retries\n" " -N retries : set maximum number of request retries\n"
" (0-15, default %d, 0 - without retries)\n" " (0-%d, default %d, 0 - without retries)\n"
" -R pause : set pause between requests in milliseconds\n" " -R pause : set pause between requests in milliseconds\n"
" (1-10000, default %lu)\n" " (1-%d, default %lu)\n"
" -W wait : set response wait time in milliseconds\n" " -W wait : set response wait time in milliseconds\n"
" (1-10000, default %lu)\n" " (1-%d, default %lu)\n"
" -T timeout : set connection timeout value in seconds\n" " -T timeout : set connection timeout value in seconds\n"
" (0-1000, default %d, 0 - no timeout)" " (0-%d, default %d, 0 - no timeout)"
"\n", PACKAGE, VERSION, exename, "\n", PACKAGE, VERSION, exename,
#ifdef LOG #ifdef LOG
cfg.dbglvl, LOGPATH, LOGNAME, LOGPATH, LOGNAME, cfg.dbglvl,
#endif #endif
cfg.ttyport, cfg.ttyspeed, cfg.ttymode, cfg.serverport, cfg.maxconn, cfg.ttyport, cfg.ttyspeed, cfg.ttymode, cfg.serverport,
cfg.maxtry, cfg.rqstpause, cfg.respwait, cfg.conntimeout); MAX_MAXCONN, cfg.maxconn, MAX_MAXTRY, cfg.maxtry,
MAX_RQSTPAUSE, cfg.rqstpause, MAX_RESPWAIT, cfg.respwait,
MAX_CONNTIMEOUT, cfg.conntimeout);
exit(0); exit(0);
} }
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
int err = 0, rc; int err = 0, rc, err_line;
char *exename; char *exename;
char ttyparity; char ttyparity;
sig_init(); sig_init();
cfg_init(); cfg_init();
if ((exename = strrchr(argv[0], '/')) == NULL) if ((exename = strrchr(argv[0], '/')) == NULL)
@ -172,7 +180,7 @@ main(int argc, char *argv[])
#ifdef LOG #ifdef LOG
"v:L:" "v:L:"
#endif #endif
"p:s:m:P:C:N:R:W:T:")) != RC_ERR) "p:s:m:P:C:N:R:W:T:c:")) != RC_ERR)
{ {
switch (rc) switch (rc)
{ {
@ -181,16 +189,28 @@ main(int argc, char *argv[])
case 'd': case 'd':
isdaemon = FALSE; isdaemon = FALSE;
break; break;
case 'c':
if ((err_line = cfg_read_file(optarg)) != 0)
{
if (err_line > 0)
printf("%s: can't read config file %s: error at line %d: %s\n",
exename, optarg, err_line, cfg_err);
else
printf("%s: can't read config file %s: %s\n",
exename, optarg, strerror(errno));
exit(-1);
}
break;
#ifdef TRXCTL #ifdef TRXCTL
case 't': case 't':
cfg.trxcntl = TRX_RTS; cfg.trxcntl = TRX_RTS;
break; break;
case 'y': case 'y':
cfg.trxcntl = TRX_SYSFS_1; cfg.trxcntl = TRX_SYSFS_1;
strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE); strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE);
break; break;
case 'Y': case 'Y':
cfg.trxcntl = TRX_SYSFS_0; cfg.trxcntl = TRX_SYSFS_0;
strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE); strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE);
break; break;
#endif #endif
@ -222,8 +242,7 @@ main(int argc, char *argv[])
else else
{ /* concatenate given log file name with default path */ { /* concatenate given log file name with default path */
strncpy(cfg.logname, LOGPATH, INTBUFSIZE); strncpy(cfg.logname, LOGPATH, INTBUFSIZE);
strncat(cfg.logname, optarg, strncat(cfg.logname, optarg, INTBUFSIZE - strlen(cfg.logname));
INTBUFSIZE - strlen(cfg.logname));
} }
} }
else strncpy(cfg.logname, optarg, INTBUFSIZE); else strncpy(cfg.logname, optarg, INTBUFSIZE);
@ -234,8 +253,7 @@ main(int argc, char *argv[])
{ /* concatenate given port name with default { /* concatenate given port name with default
path to devices mountpoint */ path to devices mountpoint */
strncpy(cfg.ttyport, "/dev/", INTBUFSIZE); strncpy(cfg.ttyport, "/dev/", INTBUFSIZE);
strncat(cfg.ttyport, optarg, strncat(cfg.ttyport, optarg, INTBUFSIZE - strlen(cfg.ttyport));
INTBUFSIZE - strlen(cfg.ttyport));
} }
else strncpy(cfg.ttyport, optarg, INTBUFSIZE); else strncpy(cfg.ttyport, optarg, INTBUFSIZE);
break; break;
@ -243,79 +261,80 @@ main(int argc, char *argv[])
cfg.ttyspeed = strtoul(optarg, NULL, 0); cfg.ttyspeed = strtoul(optarg, NULL, 0);
break; break;
case 'm': case 'm':
cfg.ttymode = optarg; strncpy(cfg.ttymode, optarg, INTBUFSIZE);
/* tty mode sanity checks */ /* tty mode sanity checks */
if (strlen(cfg.ttymode) != 3) if (strlen(cfg.ttymode) != 3)
{ {
printf("%s: -m: invalid serial port mode ('%s')\n", exename, cfg.ttymode); printf("%s: -m: invalid serial port mode ('%s')\n",
exit(-1); exename, cfg.ttymode);
} exit(-1);
if (cfg.ttymode[0] != '8') }
{ if (cfg.ttymode[0] != '8')
printf("%s: -m: invalid serial port character size " {
"(%c, must be 8)\n", printf("%s: -m: invalid serial port character size "
exename, cfg.ttymode[0]); "(%c, must be 8)\n",
exit(-1); exename, cfg.ttymode[0]);
} exit(-1);
ttyparity = toupper(cfg.ttymode[1]); }
if (ttyparity != 'N' && ttyparity != 'E' && ttyparity != 'O') ttyparity = toupper(cfg.ttymode[1]);
{ if (ttyparity != 'N' && ttyparity != 'E' && ttyparity != 'O')
printf("%s: -m: invalid serial port parity " {
"(%c, must be N, E or O)\n", exename, ttyparity); printf("%s: -m: invalid serial port parity "
exit(-1); "(%c, must be N, E or O)\n", exename, ttyparity);
} exit(-1);
if (cfg.ttymode[2] != '1' && cfg.ttymode[2] != '2') }
{ if (cfg.ttymode[2] != '1' && cfg.ttymode[2] != '2')
printf("%s: -m: invalid serial port stop bits " {
"(%c, must be 1 or 2)\n", exename, cfg.ttymode[2]); printf("%s: -m: invalid serial port stop bits "
exit(-1); "(%c, must be 1 or 2)\n", exename, cfg.ttymode[2]);
} exit(-1);
break; }
break;
case 'P': case 'P':
cfg.serverport = strtoul(optarg, NULL, 0); cfg.serverport = strtoul(optarg, NULL, 0);
break; break;
case 'C': case 'C':
cfg.maxconn = strtoul(optarg, NULL, 0); cfg.maxconn = strtoul(optarg, NULL, 0);
if (cfg.maxconn < 1 || cfg.maxconn > 128) if (cfg.maxconn < 1 || cfg.maxconn > MAX_MAXCONN)
{ /* report about invalid max conn number */ { /* report about invalid max conn number */
printf("%s: -C: invalid maxconn value" printf("%s: -C: invalid maxconn value"
" (%d, must be 1-128)\n", exename, cfg.maxconn); " (%d, must be 1-%d)\n", exename, cfg.maxconn, MAX_MAXCONN);
exit(-1); exit(-1);
} }
break; break;
case 'N': case 'N':
cfg.maxtry = strtoul(optarg, NULL, 0); cfg.maxtry = strtoul(optarg, NULL, 0);
if (cfg.maxtry > 15) if (cfg.maxtry > MAX_MAXTRY)
{ /* report about invalid max try number */ { /* report about invalid max try number */
printf("%s: -N: invalid maxtry value" printf("%s: -N: invalid maxtry value"
" (%d, must be 0-15)\n", exename, cfg.maxtry); " (%d, must be 0-%d)\n", exename, cfg.maxtry, MAX_MAXTRY);
exit(-1); exit(-1);
} }
break; break;
case 'R': case 'R':
cfg.rqstpause = strtoul(optarg, NULL, 0); cfg.rqstpause = strtoul(optarg, NULL, 0);
if (cfg.rqstpause < 1 || cfg.rqstpause > 10000) if (cfg.rqstpause < 1 || cfg.rqstpause > MAX_RQSTPAUSE)
{ /* report about invalid rqst pause value */ { /* report about invalid rqst pause value */
printf("%s: -R: invalid inter-request pause value" printf("%s: -R: invalid inter-request pause value"
" (%lu, must be 1-10000)\n", exename, cfg.rqstpause); " (%lu, must be 1-%d)\n", exename, cfg.rqstpause, MAX_RQSTPAUSE);
exit(-1); exit(-1);
} }
break; break;
case 'W': case 'W':
cfg.respwait = strtoul(optarg, NULL, 0); cfg.respwait = strtoul(optarg, NULL, 0);
if (cfg.respwait < 1 || cfg.respwait > 10000) if (cfg.respwait < 1 || cfg.respwait > MAX_RESPWAIT)
{ /* report about invalid resp wait value */ { /* report about invalid resp wait value */
printf("%s: -W: invalid response wait time value" printf("%s: -W: invalid response wait time value"
" (%lu, must be 1-10000)\n", exename, cfg.respwait); " (%lu, must be 1-%d)\n", exename, cfg.respwait, MAX_RESPWAIT);
exit(-1); exit(-1);
} }
break; break;
case 'T': case 'T':
cfg.conntimeout = strtoul(optarg, NULL, 0); cfg.conntimeout = strtoul(optarg, NULL, 0);
if (cfg.conntimeout > 1000) if (cfg.conntimeout > MAX_CONNTIMEOUT)
{ /* report about invalid conn timeout value */ { /* report about invalid conn timeout value */
printf("%s: -T: invalid conn timeout value" printf("%s: -T: invalid conn timeout value"
" (%d, must be 1-1000)\n", exename, cfg.conntimeout); " (%d, must be 1-%d)\n", exename, cfg.conntimeout, MAX_CONNTIMEOUT);
exit(-1); exit(-1);
} }
break; break;

View File

@ -3,7 +3,7 @@ Description=Modbus TCP to Modbus RTU (RS-232/485) gateway.
Requires=network.target Requires=network.target
[Service] [Service]
ExecStart=@bindir@/mbusd -p /dev/%i -s 9600 -m 8N1 -P 502 -d -v2 ExecStart=@bindir@/mbusd -d -v2 -L - -c /etc/mbusd/mbusd-%i.conf -p /dev/%i
Restart=on-failure Restart=on-failure
RestartSec=1 RestartSec=1
StandardOutput=journal StandardOutput=journal