Merge remote-tracking branch 'origin/master' into enhanced_device
# Conflicts: # src/lib/ebus/device.cpp
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5Y52QSKVXLYQG']
|
||||
@@ -53,6 +53,7 @@ check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP)
|
||||
check_function_exists(pselect HAVE_PSELECT)
|
||||
check_function_exists(ppoll HAVE_PPOLL)
|
||||
check_include_file(linux/serial.h HAVE_LINUX_SERIAL -DHAVE_LINUX_SERIAL=1)
|
||||
check_include_file(dev/usb/uftdiio.h HAVE_FREEBSD_UFTDI -DHAVE_FREEBSD_UFTDI=1)
|
||||
check_function_exists(argp_parse HAVE_ARGP)
|
||||
if(NOT HAVE_ARGP)
|
||||
find_library(LIB_ARGP argp)
|
||||
|
||||
Executable → Regular
+34
-2
@@ -1,9 +1,41 @@
|
||||
# next version
|
||||
# 3.4 (2019-10-?)
|
||||
|
||||
## Bug Fixes
|
||||
* fix for always enabled "--mqttchanges" option
|
||||
* fix for dynamically adjusted poll priority
|
||||
* fix for enhanced escape chars
|
||||
* fix for too frequent logging on MQTT broker communication error
|
||||
* fix for unexpected "different version available" message during update check
|
||||
* fix for detecting subsequent fields with less than 8 bits not fitting into 1 byte
|
||||
* fix for compilation and running on FreeBSD and MacOS with low latency setting for FTDI device (thanks to samm-git)
|
||||
* fix for switching to daemon mode when log file was not opened
|
||||
* fix for checking required arguments of "write" command with "-h"
|
||||
|
||||
## Features
|
||||
* added option to set poll priority with MQTT get topic
|
||||
* added "direct" command for listening to all valid messages from the bus
|
||||
* added MQTT /list topic for retrieval of all known messages
|
||||
* added support for init scripts on non-LSB distributions (thanks to andr2000)
|
||||
* added support for logging to syslog instead of file (thanks to samm-git)
|
||||
|
||||
|
||||
# 3.3 (2018-12-26)
|
||||
|
||||
## Bug Fixes
|
||||
* fix for missing MQTT subscription after broker reconnect
|
||||
* fix for answering to first scan only in answer mode
|
||||
* fix don't add transfer latency to receive timeout when acting as SYN generator
|
||||
* fix dont add transfer latency to receive timeout when acting as SYN generator
|
||||
* fix for bit combinations during write to SymbolString
|
||||
* fix for MQTT handling after broker disconnect
|
||||
* fix for NaN in JSON
|
||||
* fix for deadlock in libmosquitto
|
||||
|
||||
## Features
|
||||
* wait for being online before starting and automatically restart after 30 seconds
|
||||
* added "--mqttchanges" option to only publish changed messages and changed to publish all messages by default
|
||||
* added "--mqttclientid" to set own client ID instead of using the default
|
||||
* added support for single quotes to all commands
|
||||
* added "--mqttlog" and "--mqttversion" options
|
||||
|
||||
## Breaking Changes
|
||||
* added support for enhanced network protocol mode for recent [ebusd-esp firmware](https://github.com/john30/ebusd-esp/) that allows the arbitration to be done directly by the Wemos
|
||||
|
||||
@@ -38,7 +38,7 @@ Installation
|
||||
------------
|
||||
|
||||
Either pick the [latest release package](https://github.com/john30/ebusd/releases/latest)
|
||||
suitable for your system or build it yourself.
|
||||
suitable for your system, use the Debian repository as [described here](https://github.com/john30/ebusd-debian/blob/master/README.md), or build it yourself.
|
||||
|
||||
Building ebusd from the source requires the following packages and/or features:
|
||||
* autoconf (>=2.63) + automake (>=1.11) or cmake
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
/* Defined if linux/serial.h is available. */
|
||||
#cmakedefine HAVE_LINUX_SERIAL
|
||||
|
||||
/* Defined if dev/usb/uftdiio.h is available. */
|
||||
#cmakedefine HAVE_FREEBSD_UFTDI
|
||||
|
||||
/* Defined if pthread_setname_np is available. */
|
||||
#cmakedefine HAVE_PTHREAD_SETNAME_NP
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ AC_SUBST(EXTRA_LIBS)
|
||||
AC_CHECK_FUNC([pselect], [AC_DEFINE(HAVE_PSELECT, [1], [Defined if pselect() is available.])])
|
||||
AC_CHECK_FUNC([ppoll], [AC_DEFINE(HAVE_PPOLL, [1], [Defined if ppoll() is available.])])
|
||||
AC_CHECK_HEADER([linux/serial.h], [AC_DEFINE(HAVE_LINUX_SERIAL, [1], [Defined if linux/serial.h is available.])])
|
||||
AC_CHECK_HEADER([dev/usb/uftdiio.h], [AC_DEFINE(HAVE_FREEBSD_UFTDI, [1], [Defined if dev/usb/uftdiio.h is available.])])
|
||||
|
||||
AC_ARG_ENABLE(coverage, AS_HELP_STRING([--enable-coverage], [enable code coverage tracking]), [CXXFLAGS+=" -coverage -O0"], [])
|
||||
AC_ARG_WITH(contrib, AS_HELP_STRING([--without-contrib], [disable inclusion of contributed sources]), [], [with_contrib=yes])
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Contributor: Milan Knizek <knizek volny cz>
|
||||
# Usage: makepkg
|
||||
pkgname=ebusd
|
||||
pkgver=3.2
|
||||
pkgver=3.3
|
||||
pkgrel=1
|
||||
pkgdesc="ebusd, the daemon for communication with eBUS heating systems."
|
||||
arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64')
|
||||
@@ -37,4 +37,4 @@ package() {
|
||||
install -m 0644 contrib/archlinux/conf.d/ebusd "${pkgdir}/etc/conf.d/ebusd"
|
||||
}
|
||||
# update md5sums: updpkgsums
|
||||
md5sums=('413886187f5e93138674cbea916ad1fb')
|
||||
md5sums=('f65252d59ccce4c244d3c4c5597a6b8e')
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Usage: makepkg -p PKGBUILD.git
|
||||
pkgname=ebusd-git
|
||||
_gitname=ebusd
|
||||
pkgver=3.2
|
||||
pkgver=3.3
|
||||
pkgrel=1
|
||||
pkgdesc="ebusd, the daemon for communication with eBUS heating systems."
|
||||
arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64')
|
||||
|
||||
@@ -6,6 +6,7 @@ ConditionPathExists=/var/log
|
||||
[Service]
|
||||
Type=forking
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
PIDFile=/run/ebusd.pid
|
||||
EnvironmentFile=-/etc/conf.d/ebusd
|
||||
ExecStart=/usr/bin/ebusd $EBUSD_OPTS
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
ebusd.eu config webservice
|
||||
==========================
|
||||
|
||||
This is the code running on the ebusd.eu config webservice that allows ebusd to download the needed CSV configuration files instead of having to install the ebusd-configuration package.
|
||||
|
||||
It is enabled by default in ebusd due to the default value of the `--configpath=http://ebusd.eu/config/` commandline option.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
$agent = $_SERVER['HTTP_USER_AGENT'];
|
||||
if (substr($agent, 0, 5)!=='ebusd') {
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
$p=@$_REQUEST['p'];
|
||||
$e=@$_REQUEST['t'];
|
||||
$a=@$_REQUEST['a'];
|
||||
if ($p && $p[0]==='/') {
|
||||
$p=substr($p, 1);
|
||||
}
|
||||
if ($p && $p[strlen($p)-1]==='/') {
|
||||
$p=substr($p, 0, strlen($p)-1);
|
||||
}
|
||||
if ($p && $p[0]!=='/' && strpos($p, '..')===FALSE && is_file($p)) {
|
||||
header('Content-Type: text/comma-separated-values');
|
||||
header('Cache-Control: Private');
|
||||
$t=@filemtime($p);
|
||||
if ($t) {
|
||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $t).' GMT');
|
||||
}
|
||||
ob_start();
|
||||
echo @file_get_contents($p);
|
||||
header('Content-Length: '.ob_get_length());
|
||||
ob_end_flush();
|
||||
exit;
|
||||
}
|
||||
if (!$e) {
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
$e=".$e";
|
||||
if (!$p) {
|
||||
$p='.';
|
||||
} else if (strpos($p, '/')!==FALSE || strpos($p, '\\')!==FALSE || strpos($p, '..')!==FALSE) {
|
||||
header('HTTP/1.1 400 Bad Request');
|
||||
exit;
|
||||
}
|
||||
$prefix=$a ? $a==='-' ? $a : substr(dechex(0x100|hexdec($a)), 1).'.' : NULL;
|
||||
$dir=@opendir($p);
|
||||
if ($dir===FALSE) {
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
header('Content-Type: text/comma-separated-values');
|
||||
header('Cache-Control: Private');
|
||||
ob_start();
|
||||
while(($f=@readdir())!==FALSE) {
|
||||
if (!$f || !is_file($p.'/'.$f) || substr($f, -strlen($e))!==$e) {
|
||||
continue;
|
||||
}
|
||||
if ($f!=='_templates'.$e && $prefix && ($prefix==='-' ? strpos($f, '.') === 2 : substr($f, 0, strlen($prefix))!==$prefix)) {
|
||||
continue;
|
||||
}
|
||||
echo "$f\n";
|
||||
}
|
||||
@closedir($dir);
|
||||
header('Content-Length: '.ob_get_length());
|
||||
ob_end_flush();
|
||||
?>
|
||||
@@ -13,7 +13,19 @@ DAEMON=/usr/bin/ebusd
|
||||
PIDFILE_PREFIX=/var/run/ebusd
|
||||
PIDFILE_SUFFIX=.pid
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
if test -f /lib/lsb/init-functions; then
|
||||
. /lib/lsb/init-functions
|
||||
else
|
||||
. /etc/init.d/functions
|
||||
|
||||
log_daemon_msg() { logger "$@"; }
|
||||
log_end_msg() { [ $1 -eq 0 ] && RES=OK; logger ${RES:=FAIL}; }
|
||||
log_failure_msg() { logger "FAIL $@"; }
|
||||
log_warning_msg() { logger "WARN $@"; }
|
||||
status_of_proc () { status $3; }
|
||||
|
||||
fi
|
||||
|
||||
[ -r /etc/default/ebusd ] && . /etc/default/ebusd
|
||||
|
||||
if [ ! -x $DAEMON ]; then
|
||||
|
||||
@@ -6,6 +6,7 @@ ConditionPathExists=/var/log
|
||||
[Service]
|
||||
Type=forking
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
PIDFile=/var/run/ebusd.pid
|
||||
EnvironmentFile=-/etc/default/ebusd
|
||||
ExecStart=/usr/bin/ebusd $EBUSD_OPTS
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:jessie
|
||||
FROM debian:stretch
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
logrotate libmosquitto1 libstdc++6 libc6 libgcc1 \
|
||||
@@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y \
|
||||
|
||||
LABEL maintainer "ebusd@ebusd.eu"
|
||||
|
||||
ENV EBUSD_VERSION 3.2
|
||||
ENV EBUSD_VERSION 3.3
|
||||
ENV EBUSD_ARCH amd64
|
||||
|
||||
LABEL version "${EBUSD_VERSION}-${EBUSD_ARCH}-devel"
|
||||
@@ -30,6 +30,6 @@ RUN rm -rf /build
|
||||
|
||||
EXPOSE 8888
|
||||
|
||||
COPY runtime/docker-entrypoint.sh /
|
||||
COPY release/docker-entrypoint.sh /
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["-f", "--scanconfig"]
|
||||
|
||||
@@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y \
|
||||
|
||||
LABEL maintainer "ebusd@ebusd.eu"
|
||||
|
||||
ENV EBUSD_VERSION 3.2
|
||||
ENV EBUSD_VERSION 3.3
|
||||
ENV EBUSD_ARCH amd64
|
||||
|
||||
LABEL version "${EBUSD_VERSION}-${EBUSD_ARCH}"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
FROM debian:stretch-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
logrotate libmosquitto1 libstdc++6 libc6 libgcc1 \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
LABEL maintainer "ebusd@ebusd.eu"
|
||||
|
||||
ENV EBUSD_VERSION 3.2
|
||||
ENV EBUSD_ARCH amd64
|
||||
|
||||
LABEL version "${EBUSD_VERSION}-${EBUSD_ARCH}-mqtt1"
|
||||
|
||||
COPY ebusd-${EBUSD_VERSION}_${EBUSD_ARCH}_mqtt1.deb ebusd.deb
|
||||
|
||||
RUN dpkg -i ebusd.deb
|
||||
|
||||
RUN rm ebusd.deb
|
||||
|
||||
EXPOSE 8888
|
||||
|
||||
COPY docker-entrypoint.sh /
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["-f", "--scanconfig"]
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "${1#-}" != "$1" ]; then
|
||||
set -- ebusd "$@"
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
Binary file not shown.
Executable → Regular
+1
-1
@@ -8,5 +8,5 @@
|
||||
daily
|
||||
postrotate
|
||||
/usr/bin/killall -HUP ebusd
|
||||
endscript
|
||||
endscript
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
ebusd.eu update webservice
|
||||
==========================
|
||||
|
||||
This is the code running on the ebusd.eu update webservice that allows ebusd to check its own version and the configuration files for availability of new versions.
|
||||
|
||||
It is enabled by default in ebusd due to the default value of the `--updatecheck=on` commandline option.
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
version=`head -n 1 ../../VERSION`
|
||||
revision=`git describe --always`
|
||||
echo "ebusd=${version},${revision}" > versions.txt
|
||||
echo "ebusd=${version},${revision}" > oldversions.txt
|
||||
files=`find config/ -type f -or -type l`
|
||||
../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> versions.txt
|
||||
./oldtest_filereader $files|sed -e 's#^config/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> oldversions.txt
|
||||
|
||||
@@ -5,7 +5,7 @@ if (substr($agent, 0, 5)==='ebusd') {
|
||||
$r = @file_get_contents('php://input');
|
||||
header('Content-Type: text/plain');
|
||||
$r = @json_decode($r, true);
|
||||
echo checkUpdate(@$r['v'], @$r['r'], @$r['l']);
|
||||
echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['l']);
|
||||
exit;
|
||||
}
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
<?php
|
||||
$v = @file_get_contents('versions.txt');
|
||||
$versions = array();
|
||||
if ($v) {
|
||||
$v = explode("\n", $v);
|
||||
$func = function($val, $k) {
|
||||
global $versions;
|
||||
$val = explode('=', $val);
|
||||
if (count($val)>1) {
|
||||
$versions[$val[0]] = explode(',', $val[1]);
|
||||
}
|
||||
};
|
||||
array_walk($v, $func);
|
||||
function readVersions($old=false) {
|
||||
global $versions;
|
||||
$versions = array();
|
||||
$v = @file_get_contents(($old?'old':'').'versions.txt');
|
||||
if ($v) {
|
||||
$v = explode("\n", $v);
|
||||
$func = function($val, $k) {
|
||||
global $versions;
|
||||
$val = explode('=', $val);
|
||||
if (count($val)>1) {
|
||||
$versions[$val[0]] = explode(',', $val[1]);
|
||||
}
|
||||
};
|
||||
array_walk($v, $func);
|
||||
}
|
||||
}
|
||||
function checkUpdate($ebusdVersion, $ebusdRelease, $loadedFiles) {
|
||||
function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $loadedFiles) {
|
||||
if (!$ebusdVersion) {
|
||||
return 'invalid request';
|
||||
}
|
||||
readVersions($architecture && substr($architecture, 0, 3)!=='arm' && ($ebusdVersion<'3.3' || ($ebusdVersion==='3.3' && ($ebusdRelease==='v3.3' || (strtok($ebusdRelease, '-')==='v3.3') && strtok('-')<18))));
|
||||
global $versions;
|
||||
$ret = 'unknown';
|
||||
if ($ebusdVersion==$versions['ebusd'][0] && $ebusdRelease==$versions['ebusd'][1]) {
|
||||
@@ -39,7 +44,7 @@ function checkUpdate($ebusdVersion, $ebusdRelease, $loadedFiles) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (strlen($configs)>100) {
|
||||
if (strlen($configs)>1000) {
|
||||
return $ret.($newerAvailable?', '.$newerAvailable.' newer':', different').' configuration files available';
|
||||
}
|
||||
return $ret.$configs;
|
||||
|
||||
+27
-13
@@ -190,6 +190,7 @@ bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) {
|
||||
|
||||
|
||||
void GrabbedMessage::setLastData(const MasterSymbolString& master, const SlaveSymbolString& slave) {
|
||||
time(&m_lastTime);
|
||||
m_lastMaster = master;
|
||||
m_lastSlave = slave;
|
||||
m_count++;
|
||||
@@ -249,7 +250,8 @@ bool decodeType(const DataType* type, const SymbolString& input, size_t length,
|
||||
return !first;
|
||||
}
|
||||
|
||||
bool GrabbedMessage::dump(bool unknown, MessageMap* messages, bool first, bool decode, ostringstream* output) const {
|
||||
bool GrabbedMessage::dump(bool unknown, MessageMap* messages, bool first, bool decode, ostringstream* output,
|
||||
bool isDirectMode) const {
|
||||
Message* message = messages->find(m_lastMaster);
|
||||
if (unknown && message) {
|
||||
return false;
|
||||
@@ -260,11 +262,13 @@ bool GrabbedMessage::dump(bool unknown, MessageMap* messages, bool first, bool d
|
||||
symbol_t dstAddress = m_lastMaster[1];
|
||||
*output << m_lastMaster.getStr();
|
||||
if (dstAddress != BROADCAST && !isMaster(dstAddress)) {
|
||||
*output << " / " << m_lastSlave.getStr();
|
||||
*output << (isDirectMode ? " " : " / ") << m_lastSlave.getStr();
|
||||
}
|
||||
*output << " = " << m_count;
|
||||
if (message) {
|
||||
*output << ": " << message->getCircuit() << " " << message->getName();
|
||||
if (!isDirectMode) {
|
||||
*output << " = " << m_count;
|
||||
if (message) {
|
||||
*output << ": " << message->getCircuit() << " " << message->getName();
|
||||
}
|
||||
}
|
||||
if (decode) {
|
||||
DataTypeList *types = DataTypeList::getInstance();
|
||||
@@ -1585,16 +1589,26 @@ bool BusHandler::enableGrab(bool enable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void BusHandler::formatGrabResult(bool unknown, bool decode, ostringstream* output) const {
|
||||
void BusHandler::formatGrabResult(bool unknown, bool decode, ostringstream* output, bool isDirectMode,
|
||||
time_t since, time_t until) const {
|
||||
if (!m_grabMessages) {
|
||||
*output << "grab disabled";
|
||||
} else {
|
||||
bool first = true;
|
||||
for (const auto& it : m_grabbedMessages) {
|
||||
if (it.second.dump(unknown, m_messages, first, decode, output)) {
|
||||
first = false;
|
||||
}
|
||||
if (!isDirectMode) {
|
||||
*output << "grab disabled";
|
||||
}
|
||||
return;
|
||||
}
|
||||
bool first = true;
|
||||
for (const auto& it : m_grabbedMessages) {
|
||||
if ((since > 0 && it.second.getLastTime() < since)
|
||||
|| (until > 0 && it.second.getLastTime() >= until)) {
|
||||
continue;
|
||||
}
|
||||
if (it.second.dump(unknown, m_messages, first, decode, output, isDirectMode)) {
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
if (isDirectMode && !first) {
|
||||
*output << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-3
@@ -296,7 +296,7 @@ class GrabbedMessage {
|
||||
/**
|
||||
* Construct a new instance.
|
||||
*/
|
||||
GrabbedMessage() : m_count(0) {}
|
||||
GrabbedMessage() : m_lastTime(0), m_count(0) {}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
@@ -314,6 +314,12 @@ class GrabbedMessage {
|
||||
*/
|
||||
void setLastData(const MasterSymbolString& master, const SlaveSymbolString& slave);
|
||||
|
||||
/**
|
||||
* Get the last received time.
|
||||
* @return the last received time.
|
||||
*/
|
||||
time_t getLastTime() const { return m_lastTime; }
|
||||
|
||||
/**
|
||||
* Get the last @a MasterSymbolString.
|
||||
* @return the last @a MasterSymbolString.
|
||||
@@ -327,12 +333,17 @@ class GrabbedMessage {
|
||||
* @param first whether this is the first message to be added to the output.
|
||||
* @param decode whether to add decoding hints.
|
||||
* @param output the @a ostringstream to format the messages to.
|
||||
* @param isDirectMode true for direct mode, false for grab command.
|
||||
* @return whether the message was added to the output.
|
||||
*/
|
||||
bool dump(bool unknown, MessageMap* messages, bool first, bool decode, ostringstream* output) const;
|
||||
bool dump(bool unknown, MessageMap* messages, bool first, bool decode, ostringstream* output,
|
||||
bool isDirectMode = false) const;
|
||||
|
||||
|
||||
private:
|
||||
/** the last received time. */
|
||||
time_t m_lastTime;
|
||||
|
||||
/** the last @a MasterSymbolString. */
|
||||
MasterSymbolString m_lastMaster;
|
||||
|
||||
@@ -516,13 +527,23 @@ class BusHandler : public WaitThread {
|
||||
*/
|
||||
bool enableGrab(bool enable = true);
|
||||
|
||||
/**
|
||||
* Return whether grabbing unknown messages is enabled.
|
||||
* @return whether grabbing unknown messages is enabled.
|
||||
*/
|
||||
bool isGrabEnabled() { return m_grabMessages; }
|
||||
|
||||
/**
|
||||
* Format the grabbed messages to the @a ostringstream.
|
||||
* @param unknown whether to dump only unknown messages.
|
||||
* @param decode whether to add decoding hints.
|
||||
* @param output the @a ostringstream to format the messages to.
|
||||
* @param isDirectMode true for direct mode, false for grab command.
|
||||
* @param since the start time from which to add received messages (inclusive), or 0 for all.
|
||||
* @param until the end time to which to add received messages (exclusive), or 0 for all.
|
||||
*/
|
||||
void formatGrabResult(bool unknown, bool decode, ostringstream* output) const;
|
||||
void formatGrabResult(bool unknown, bool decode, ostringstream* output, bool isDirectMode = false,
|
||||
time_t since = 0, time_t until = 0) const;
|
||||
|
||||
/**
|
||||
* Return true when a signal on the bus is available.
|
||||
|
||||
+13
-10
@@ -223,7 +223,7 @@ static const struct argp_option argpoptions[] = {
|
||||
{"updatecheck", O_UPDCHK, "MODE", 0, "Set automatic update check to MODE (on|off) [on]", 0 },
|
||||
|
||||
{nullptr, 0, nullptr, 0, "Log options:", 5 },
|
||||
{"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon) [" PACKAGE_LOGFILE "]", 0 },
|
||||
{"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon, empty string for using syslog) [" PACKAGE_LOGFILE "]", 0 },
|
||||
{"log", O_LOG, "AREAS LEVEL", 0, "Only write log for matching AREA(S) below or equal to LEVEL"
|
||||
" (alternative to --logareas/--logevel, may be used multiple times) [all notice]", 0 },
|
||||
{"logareas", O_LOGARE, "AREAS", 0, "Only write log for matching AREA(S): main|network|bus|update|all"
|
||||
@@ -487,7 +487,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) {
|
||||
|
||||
// Log options:
|
||||
case 'l': // --logfile=/var/log/ebusd.log
|
||||
if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) {
|
||||
if (arg == nullptr || strcmp("/", arg) == 0) {
|
||||
argp_error(state, "invalid logfile");
|
||||
return EINVAL;
|
||||
}
|
||||
@@ -599,7 +599,7 @@ void daemonize() {
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid < 0) {
|
||||
logError(lf_main, "fork() failed");
|
||||
logError(lf_main, "fork() failed, exiting");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -612,14 +612,14 @@ void daemonize() {
|
||||
// Create a new SID for the child process and
|
||||
// detach the process from the parent (normally a shell)
|
||||
if (setsid() < 0) {
|
||||
logError(lf_main, "setsid() failed");
|
||||
logError(lf_main, "setsid() failed, exiting");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Change the current working directory. This prevents the current
|
||||
// directory from being locked; hence not being able to remove it.
|
||||
if (chdir("/tmp") < 0) {
|
||||
logError(lf_main, "daemon chdir() failed");
|
||||
logError(lf_main, "daemon chdir() failed, exiting");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -642,7 +642,7 @@ void daemonize() {
|
||||
}
|
||||
}
|
||||
if (pidFile == nullptr) {
|
||||
logError(lf_main, "can't open pidfile: %s", opt.pidFile);
|
||||
logError(lf_main, "can't open pidfile: %s, exiting", opt.pidFile);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ void signalHandler(int sig) {
|
||||
switch (sig) {
|
||||
case SIGHUP:
|
||||
logNotice(lf_main, "SIGHUP received");
|
||||
if (!opt.foreground) {
|
||||
if (!opt.foreground && opt.logFile && opt.logFile[0] != 0) { // for log file rotation
|
||||
closeLogFile();
|
||||
setLogFile(opt.logFile);
|
||||
}
|
||||
@@ -988,8 +988,8 @@ result_t loadConfigFiles(MessageMap* messages, bool verbose, bool denyRecursive)
|
||||
if (result == RESULT_OK) {
|
||||
logInfo(lf_main, "read config files");
|
||||
} else {
|
||||
logError(lf_main, "error reading config files: %s, last error: %s", getResultCode(result),
|
||||
errorDescription.c_str());
|
||||
logError(lf_main, "error reading config files from %s: %s, last error: %s", opt.configPath,
|
||||
getResultCode(result), errorDescription.c_str());
|
||||
}
|
||||
messages->unlock();
|
||||
return RESULT_OK;
|
||||
@@ -1293,7 +1293,10 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (!opt.foreground) {
|
||||
setLogFile(opt.logFile);
|
||||
if (!setLogFile(opt.logFile)) {
|
||||
logError(lf_main, "unable to open log file %s", opt.logFile);
|
||||
return EINVAL;
|
||||
}
|
||||
daemonize(); // make me daemon
|
||||
}
|
||||
|
||||
|
||||
+144
-69
@@ -371,22 +371,24 @@ void MainLoop::run() {
|
||||
continue;
|
||||
}
|
||||
if (m_shutdown) {
|
||||
netMessage->setResult("ERR: shutdown", "", false, now, true);
|
||||
netMessage->setResult("ERR: shutdown", "", cm_normal, now, true);
|
||||
break;
|
||||
}
|
||||
string request = netMessage->getRequest();
|
||||
string user = netMessage->getUser();
|
||||
bool listening = netMessage->isListening(&since);
|
||||
if (!listening) {
|
||||
ClientMode mode = netMessage->getMode(&since);
|
||||
if (!netMessage->isListeningMode()) {
|
||||
since = now;
|
||||
}
|
||||
ostringstream ostream;
|
||||
bool connected = true;
|
||||
if (request.length() > 0) {
|
||||
logDebug(lf_main, ">>> %s", request.c_str());
|
||||
result_t result = decodeMessage(request, netMessage->isHttp(), &connected, &listening, &user, &reload, &ostream);
|
||||
result_t result = decodeMessage(request, netMessage->isHttp(), &connected, &mode, &user, &reload, &ostream);
|
||||
if (!netMessage->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) {
|
||||
ostream.str("");
|
||||
if (mode != cm_direct) {
|
||||
ostream.str("");
|
||||
}
|
||||
ostream << getResultCode(result);
|
||||
}
|
||||
if (ostream.tellp() > 100) {
|
||||
@@ -397,10 +399,10 @@ void MainLoop::run() {
|
||||
if (ostream.tellp() == 0) {
|
||||
ostream << "\n"; // only for HTTP
|
||||
} else if (!netMessage->isHttp()) {
|
||||
ostream << "\n\n";
|
||||
ostream << (mode == cm_direct ? "\n" : "\n\n");
|
||||
}
|
||||
}
|
||||
if (listening) {
|
||||
if (mode == cm_listen) {
|
||||
string levels = getUserLevels(user);
|
||||
messages.clear();
|
||||
m_messages->findAll("", "", levels, false, true, true, true, true, true, since, now, true, &messages);
|
||||
@@ -409,9 +411,13 @@ void MainLoop::run() {
|
||||
message->decodeLastData(false, nullptr, -1, 0, &ostream);
|
||||
ostream << endl;
|
||||
}
|
||||
} else if (mode == cm_direct) {
|
||||
if (m_busHandler->isGrabEnabled()) {
|
||||
m_busHandler->formatGrabResult(false, false, &ostream, true, since, now);
|
||||
}
|
||||
}
|
||||
// send result to client
|
||||
netMessage->setResult(ostream.str(), user, listening, now, !connected);
|
||||
netMessage->setResult(ostream.str(), user, mode, now, !connected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +462,7 @@ void MainLoop::notifyDeviceData(symbol_t symbol, bool received) {
|
||||
}
|
||||
}
|
||||
|
||||
result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connected, bool* listening,
|
||||
result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connected, ClientMode* mode,
|
||||
string* user, bool* reload, ostringstream* ostream) {
|
||||
string token, previous;
|
||||
istringstream stream(data);
|
||||
@@ -476,11 +482,11 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte
|
||||
} else if (token.length() == 0) { // allow multiple space chars for a single delimiter
|
||||
continue;
|
||||
} else if (token[0] == '"' || token[0] == '\'') {
|
||||
escaped = token[0];
|
||||
token.erase(0, 1);
|
||||
if (token.length() > 0 && token[token.length()-1] == token[0]) {
|
||||
if (token.length() > 0 && token[token.length()-1] == escaped) {
|
||||
token.erase(token.length() - 1, 1);
|
||||
} else {
|
||||
escaped = token[0];
|
||||
escaped = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,21 +512,25 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
if (args.size() == 0) {
|
||||
string cmd = args.size() > 0 ? args[0] : "";
|
||||
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||
if (cmd == "?" || cmd == "H" || cmd == "HELP") {
|
||||
// found "HELP CMD"
|
||||
cmd = args.size() > 1 ? args[1] : "";
|
||||
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||
args.clear(); // empty args is used as command help indicator
|
||||
}
|
||||
if (*mode == cm_direct) {
|
||||
return executeDirect(args, mode, ostream);
|
||||
}
|
||||
if (cmd.empty() && args.size() == 0) {
|
||||
return executeHelp(ostream);
|
||||
}
|
||||
string cmd = args[0];
|
||||
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||
if (args.size() == 2) {
|
||||
string arg = args[1];
|
||||
if (arg == "?" || arg == "-?" || arg == "--help") {
|
||||
// found "CMD HELP"
|
||||
args.clear(); // empty args is used as command help indicator
|
||||
} else if (cmd == "?" || cmd == "H" || cmd == "HELP") {
|
||||
// found "HELP CMD"
|
||||
cmd = args[1];
|
||||
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||
args.clear(); // empty args is used as command help indicator
|
||||
}
|
||||
}
|
||||
if (cmd == "AUTH" || cmd == "A") {
|
||||
@@ -543,7 +553,10 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte
|
||||
return executeFind(args, getUserLevels(*user), ostream);
|
||||
}
|
||||
if (cmd == "L" || cmd == "LISTEN") {
|
||||
return executeListen(args, listening, ostream);
|
||||
return executeListen(args, mode, ostream);
|
||||
}
|
||||
if (cmd == "DIRECT") {
|
||||
return executeDirect(args, mode, ostream);
|
||||
}
|
||||
if (cmd == "S" || cmd == "STATE") {
|
||||
return executeState(args, ostream);
|
||||
@@ -748,7 +761,8 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
*ostream <<
|
||||
"usage: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*]"
|
||||
" NAME [FIELD[.N]]\n"
|
||||
" or: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*] -def DEFINITION\n"
|
||||
" or: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*] -def DEFINITION "
|
||||
"(only if enabled)\n"
|
||||
" or: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
|
||||
" Read value(s) or hex message.\n"
|
||||
" -f force reading from the bus (same as '-m 0')\n"
|
||||
@@ -765,7 +779,7 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
" NAME NAME of the message to send\n"
|
||||
" FIELD only retrieve the field named FIELD\n"
|
||||
" N only retrieve the N'th field named FIELD (0-based)\n"
|
||||
" -def read with explicit message definition:\n"
|
||||
" -def read with explicit message definition (only if enabled):\n"
|
||||
" DEFINITION message definition to use instead of known definition\n"
|
||||
" -h send hex read message (or answer from cache):\n"
|
||||
" ZZ destination address\n"
|
||||
@@ -987,14 +1001,16 @@ result_t MainLoop::executeWrite(const vector<string>& args, const string levels,
|
||||
argPos++;
|
||||
}
|
||||
|
||||
if ((hex && (newDefinition || dstAddress != SYN || !circuit.empty() || args.size() < argPos + 1))
|
||||
|| (newDefinition && (hex || !circuit.empty() || args.size() < argPos + 1 || args.size() > argPos + 2))) {
|
||||
if ((args.size() < argPos + 1)
|
||||
|| (hex && (newDefinition || dstAddress != SYN))
|
||||
|| (newDefinition && (hex || !circuit.empty() || args.size() > argPos + 2))
|
||||
|| (!newDefinition && !hex && (circuit.empty() || args.size() > argPos + 2))) {
|
||||
argPos = 0; // print usage
|
||||
}
|
||||
|
||||
if (argPos == 0 || (!newDefinition && (circuit.empty() || (args.size() != argPos+2 && args.size() != argPos+1)))) {
|
||||
if (argPos == 0) {
|
||||
*ostream << "usage: write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" or: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*]\n"
|
||||
" or: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*] (only if enabled)\n"
|
||||
" or: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
|
||||
" Write value(s) or hex message.\n"
|
||||
" -s QQ override source address QQ\n"
|
||||
@@ -1002,7 +1018,7 @@ result_t MainLoop::executeWrite(const vector<string>& args, const string levels,
|
||||
" -c CIRCUIT CIRCUIT of the message to send\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" VALUE a single field VALUE\n"
|
||||
" -def write with explicit message definition:\n"
|
||||
" -def write with explicit message definition (only if enabled):\n"
|
||||
" DEFINITION message definition to use instead of known definition\n"
|
||||
" -h send hex write message:\n"
|
||||
" ZZ destination address\n"
|
||||
@@ -1133,56 +1149,114 @@ result_t MainLoop::executeWrite(const vector<string>& args, const string levels,
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
result_t MainLoop::executeHex(const vector<string>& args, ostringstream* ostream) {
|
||||
size_t argPos = 1;
|
||||
result_t MainLoop::parseHexAndSend(const vector<string>& args, size_t& argPos, bool isDirectMode,
|
||||
ostringstream* ostream) {
|
||||
symbol_t srcAddress = SYN;
|
||||
if (args.size() > argPos && args[argPos] == "-s") {
|
||||
argPos++;
|
||||
if (argPos >= args.size()) {
|
||||
argPos = 0; // print usage
|
||||
} else {
|
||||
result_t ret;
|
||||
symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret);
|
||||
if (ret != RESULT_OK || !isValidAddress(address, false) || !isMaster(address)) {
|
||||
return RESULT_ERR_INVALID_ADDR;
|
||||
}
|
||||
srcAddress = address == m_address ? SYN : address;
|
||||
return RESULT_OK;
|
||||
}
|
||||
result_t ret;
|
||||
symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret);
|
||||
if (ret != RESULT_OK || !isValidAddress(address, false) || !isMaster(address)) {
|
||||
return RESULT_ERR_INVALID_ADDR;
|
||||
}
|
||||
srcAddress = address == m_address ? SYN : address;
|
||||
argPos++;
|
||||
}
|
||||
if (args.size() < argPos + 1 || (args.size() > argPos && args[argPos][0] == '-')) {
|
||||
argPos = 0; // print usage
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
if (argPos > 0) {
|
||||
MasterSymbolString master;
|
||||
result_t ret = parseHexMaster(args, argPos, srcAddress, &master);
|
||||
if (ret != RESULT_OK) {
|
||||
return ret;
|
||||
}
|
||||
logNotice(lf_main, "hex cmd: %s", master.getStr().c_str());
|
||||
|
||||
// send message
|
||||
SlaveSymbolString slave;
|
||||
ret = m_busHandler->sendAndWait(master, &slave);
|
||||
|
||||
if (ret == RESULT_OK) {
|
||||
if (master[1] == BROADCAST) {
|
||||
*ostream << "done broadcast";
|
||||
return RESULT_OK;
|
||||
}
|
||||
if (isMaster(master[1])) {
|
||||
return RESULT_OK;
|
||||
}
|
||||
*ostream << slave.getStr();
|
||||
return RESULT_OK;
|
||||
}
|
||||
logError(lf_main, "hex: %s", getResultCode(ret));
|
||||
MasterSymbolString master;
|
||||
result_t ret = parseHexMaster(args, argPos, srcAddress, &master);
|
||||
argPos = args.size(); // mark as successfully parsed
|
||||
if (ret != RESULT_OK) {
|
||||
return ret;
|
||||
}
|
||||
logNotice(lf_main, isDirectMode ? "direct cmd: %s" : "hex cmd: %s", master.getStr().c_str());
|
||||
|
||||
// send message
|
||||
SlaveSymbolString slave;
|
||||
ret = m_busHandler->sendAndWait(master, &slave);
|
||||
|
||||
if (ret == RESULT_OK) {
|
||||
if (master[1] == BROADCAST) {
|
||||
*ostream << "done broadcast";
|
||||
return RESULT_OK;
|
||||
}
|
||||
if (isMaster(master[1])) {
|
||||
*ostream << "done";
|
||||
return RESULT_OK;
|
||||
}
|
||||
*ostream << slave.getStr();
|
||||
return RESULT_OK;
|
||||
}
|
||||
logError(lf_main, isDirectMode ? "direct: %s" : "hex: %s", getResultCode(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
result_t MainLoop::executeHex(const vector<string>& args, ostringstream* ostream) {
|
||||
size_t argPos = 1;
|
||||
result_t ret = parseHexAndSend(args, argPos, false, ostream);
|
||||
if (argPos == args.size()) {
|
||||
return ret;
|
||||
}
|
||||
*ostream << "usage: hex [-s QQ] ZZPBSBNN[DD]*\n"
|
||||
" Send arbitrary data in hex (only if enabled).\n"
|
||||
" Send arbitrary data in hex.\n"
|
||||
" -s QQ override source address QQ\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" DD data byte(s) to send";
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
result_t MainLoop::executeDirect(const vector<string>& args, ClientMode* mode, ostringstream* ostream) {
|
||||
if (*mode != cm_direct) {
|
||||
if (args.size() == 1) {
|
||||
*mode = cm_direct;
|
||||
m_busHandler->enableGrab(true); // needed for listening to all messages
|
||||
*ostream << "direct mode started";
|
||||
return RESULT_OK;
|
||||
}
|
||||
*ostream << "usage: direct\n"
|
||||
" Enter direct mode.";
|
||||
return RESULT_OK;
|
||||
}
|
||||
if (args.size() > 0) {
|
||||
string firstArg = args[0];
|
||||
if (firstArg == "stop") {
|
||||
*mode = cm_normal;
|
||||
*ostream << "direct mode stopped";
|
||||
return RESULT_OK;
|
||||
}
|
||||
if (firstArg != "") {
|
||||
for (size_t argPos = 0; argPos < args.size(); argPos++) {
|
||||
if (argPos > 0) {
|
||||
*ostream << " ";
|
||||
}
|
||||
*ostream << args[argPos];
|
||||
}
|
||||
*ostream << ":";
|
||||
if (!m_enableHex) {
|
||||
*ostream << "ERR: command not enabled";
|
||||
return RESULT_OK;
|
||||
}
|
||||
size_t argPos = 0;
|
||||
result_t ret = parseHexAndSend(args, argPos, true, ostream);
|
||||
if (ret == RESULT_OK && argPos != args.size()) {
|
||||
ret = RESULT_ERR_INVALID_ARG;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
*ostream << "usage: [-s QQ] ZZPBSBNN[DD]*\n"
|
||||
" or: stop\n"
|
||||
" Send arbitrary data in hex (only if enabled) or stop direct mode.\n"
|
||||
" -s QQ override source address QQ\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
@@ -1405,13 +1479,13 @@ result_t MainLoop::executeFind(const vector<string>& args, const string& levels,
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
result_t MainLoop::executeListen(const vector<string>& args, bool* listening, ostringstream* ostream) {
|
||||
result_t MainLoop::executeListen(const vector<string>& args, ClientMode* mode, ostringstream* ostream) {
|
||||
if (args.size() == 1) {
|
||||
if (*listening) {
|
||||
if (*mode == cm_listen) {
|
||||
*ostream << "listen continued";
|
||||
return RESULT_OK;
|
||||
}
|
||||
*listening = true;
|
||||
*mode = cm_listen;
|
||||
*ostream << "listen started";
|
||||
return RESULT_OK;
|
||||
}
|
||||
@@ -1421,7 +1495,7 @@ result_t MainLoop::executeListen(const vector<string>& args, bool* listening, os
|
||||
" Listen for updates or stop it.";
|
||||
return RESULT_OK;
|
||||
}
|
||||
*listening = false;
|
||||
*mode = cm_normal;
|
||||
*ostream << "listen stopped";
|
||||
return RESULT_OK;
|
||||
}
|
||||
@@ -1781,17 +1855,18 @@ result_t MainLoop::executeHelp(ostringstream* ostream) {
|
||||
*ostream << "usage:\n"
|
||||
" read|r Read value(s): read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N]"
|
||||
" [-i VALUE[;VALUE]*] NAME [FIELD[.N]]\n"
|
||||
" Read by new defintion: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N]"
|
||||
" Read by new defintion: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N] (if enabled)"
|
||||
" [-i VALUE[;VALUE]*] -def DEFINITION\n"
|
||||
" Read hex message: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
|
||||
" write|w Write value(s): write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" Write by new def.: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*]\n"
|
||||
" Write by new def.: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*] (if enabled)\n"
|
||||
" Write hex message: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
|
||||
" auth|a Authenticate user: auth USER SECRET\n"
|
||||
" hex Send hex data: hex [-s QQ] ZZPBSBNN[DD]*\n"
|
||||
" hex Send hex data: hex [-s QQ] ZZPBSBNN[DD]* (if enabled)\n"
|
||||
" find|f Find message(s): find [-v|-V] [-r] [-w] [-p] [-a] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e]"
|
||||
" [-c CIRCUIT] [-l LEVEL] [NAME]\n"
|
||||
" listen|l Listen for updates: listen [stop]\n"
|
||||
" direct Enter direct mode\n"
|
||||
" state|s Report bus state\n"
|
||||
" info|i Report information about the daemon, the configuration, and seen devices.\n"
|
||||
" grab|g Grab messages: grab [stop]\n"
|
||||
|
||||
+24
-4
@@ -144,13 +144,13 @@ class MainLoop : public Thread, DeviceListener {
|
||||
* @param data the data string to decode (may be empty).
|
||||
* @param connected set to false when the client connection shall be closed.
|
||||
* @param isHttp true for HTTP message.
|
||||
* @param listening set to true when the client is in listening mode.
|
||||
* @param mode set to the new client mode.
|
||||
* @param user set to the new user name when changed by authentication.
|
||||
* @param reload set to true when the configuration files were reloaded.
|
||||
* @param ostream the @a ostringstream to format the result string to.
|
||||
* @return the result code.
|
||||
*/
|
||||
result_t decodeMessage(const string& data, bool isHttp, bool* connected, bool* listening,
|
||||
result_t decodeMessage(const string& data, bool isHttp, bool* connected, ClientMode* mode,
|
||||
string* user, bool* reload, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
@@ -198,6 +198,17 @@ class MainLoop : public Thread, DeviceListener {
|
||||
*/
|
||||
result_t executeWrite(const vector<string>& args, const string levels, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Parse a hex or direct command and send it on the bus.
|
||||
* @param args the arguments passed to the command.
|
||||
* @param argPos the position in the arguments to the first parameter, will be set to other than args.size() on
|
||||
* invalid input.
|
||||
* @param isDirectMode true for direct mode, false for hex command.
|
||||
* @param ostream the @a ostringstream to format the result string to.
|
||||
* @return the result code.
|
||||
*/
|
||||
result_t parseHexAndSend(const vector<string>& args, size_t& argPos, bool isDirectMode, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the hex command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
@@ -206,6 +217,15 @@ class MainLoop : public Thread, DeviceListener {
|
||||
*/
|
||||
result_t executeHex(const vector<string>& args, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the direct command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param mode set to the new client mode.
|
||||
* @param ostream the @a ostringstream to format the result string to.
|
||||
* @return the result code.
|
||||
*/
|
||||
result_t executeDirect(const vector<string>& args, ClientMode* mode, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the find command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
@@ -218,11 +238,11 @@ class MainLoop : public Thread, DeviceListener {
|
||||
/**
|
||||
* Execute the listen command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param listening set to true when the client is in listening mode.
|
||||
* @param mode set to the new client mode.
|
||||
* @param ostream the @a ostringstream to format the result string to.
|
||||
* @return the result code.
|
||||
*/
|
||||
result_t executeListen(const vector<string>& args, bool* listening, ostringstream* ostream);
|
||||
result_t executeListen(const vector<string>& args, ClientMode* mode, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the state command.
|
||||
|
||||
Executable → Regular
+232
-84
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "ebusd/mqtthandler.h"
|
||||
#include <csignal>
|
||||
#include <deque>
|
||||
#include "lib/utils/log.h"
|
||||
|
||||
namespace ebusd {
|
||||
@@ -36,7 +37,9 @@ using std::dec;
|
||||
#define O_TOPI (O_PASS+1)
|
||||
#define O_RETA (O_TOPI+1)
|
||||
#define O_JSON (O_RETA+1)
|
||||
#define O_IGIN (O_JSON+1)
|
||||
#define O_LOGL (O_JSON+1)
|
||||
#define O_VERS (O_LOGL+1)
|
||||
#define O_IGIN (O_VERS+1)
|
||||
#define O_CHGS (O_IGIN+1)
|
||||
#define O_CAFI (O_CHGS+1)
|
||||
#define O_CERT (O_CAFI+1)
|
||||
@@ -48,7 +51,7 @@ static const struct argp_option g_mqtt_argp_options[] = {
|
||||
{nullptr, 0, nullptr, 0, "MQTT options:", 1 },
|
||||
{"mqtthost", O_HOST, "HOST", 0, "Connect to MQTT broker on HOST [localhost]", 0 },
|
||||
{"mqttport", O_PORT, "PORT", 0, "Connect to MQTT broker on PORT (usually 1883), 0 to disable [0]", 0 },
|
||||
{"mqttclientid",O_CLID, "ID", 0, "Set client ID for connection to MQTT broker [" PACKAGE_NAME "_"
|
||||
{"mqttclientid", O_CLID, "ID", 0, "Set client ID for connection to MQTT broker [" PACKAGE_NAME "_"
|
||||
PACKAGE_VERSION "_<pid>]", 0 },
|
||||
{"mqttuser", O_USER, "USER", 0, "Connect as USER to MQTT broker (no default)", 0 },
|
||||
{"mqttpass", O_PASS, "PASSWORD", 0, "Use PASSWORD when connecting to MQTT broker (no default)", 0 },
|
||||
@@ -56,6 +59,12 @@ static const struct argp_option g_mqtt_argp_options[] = {
|
||||
"Use MQTT TOPIC (prefix before /%circuit/%name or complete format) [ebusd]", 0 },
|
||||
{"mqttretain", O_RETA, nullptr, 0, "Retain all topics instead of only selected global ones", 0 },
|
||||
{"mqttjson", O_JSON, nullptr, 0, "Publish in JSON format instead of strings", 0 },
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001)
|
||||
{"mqttlog", O_LOGL, nullptr, 0, "Log library events", 0 },
|
||||
#endif
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001)
|
||||
{"mqttversion", O_VERS, "VERSION", 0, "Use protocol VERSION [3.1]", 0 },
|
||||
#endif
|
||||
{"mqttignoreinvalid", O_IGIN, nullptr, 0,
|
||||
"Ignore invalid parameters during init (e.g. for DNS not resolvable yet)", 0 },
|
||||
{"mqttchanges", O_CHGS, nullptr, 0, "Whether to only publish changed messages instead of all received", 0 },
|
||||
@@ -81,8 +90,14 @@ static vector<string> g_topicStrs;
|
||||
static vector<string> g_topicFields;
|
||||
static bool g_retain = false; //!< whether to retail all topics
|
||||
static OutputFormat g_publishFormat = 0; //!< the OutputFormat for publishing messages
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001)
|
||||
static bool g_logFromLib = false; //!< log library events
|
||||
#endif
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001)
|
||||
static int g_version = MQTT_PROTOCOL_V31; //!< protocol version to use
|
||||
#endif
|
||||
static bool g_ignoreInvalidParams = false; //!< ignore invalid parameters during init
|
||||
static bool g_onlyChanges = true; //!< whether to only publish changed messages instead of all received
|
||||
static bool g_onlyChanges = false; //!< whether to only publish changed messages instead of all received
|
||||
|
||||
#if (LIBMOSQUITTO_MAJOR >= 1)
|
||||
static const char* g_cafile = nullptr; //!< CA file for TLS
|
||||
@@ -162,6 +177,22 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) {
|
||||
g_publishFormat |= OF_JSON|OF_NAMES;
|
||||
break;
|
||||
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001)
|
||||
case O_LOGL:
|
||||
g_logFromLib = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001)
|
||||
case O_VERS: // --mqttversion=3.1.1
|
||||
if (arg == nullptr || arg[0] == 0 || (strcmp(arg, "3.1") != 0 && strcmp(arg, "3.1.1") != 0)) {
|
||||
argp_error(state, "invalid mqttversion");
|
||||
return EINVAL;
|
||||
}
|
||||
g_version = strcmp(arg, "3.1.1") == 0 ? MQTT_PROTOCOL_V311 : MQTT_PROTOCOL_V31;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case O_IGIN:
|
||||
g_ignoreInvalidParams = true;
|
||||
break;
|
||||
@@ -228,15 +259,37 @@ const struct argp_child* mqtthandler_getargs() {
|
||||
return &g_mqtt_argp_child;
|
||||
}
|
||||
|
||||
bool check(int code, const char* method) {
|
||||
if (code == MOSQ_ERR_SUCCESS) {
|
||||
return true;
|
||||
}
|
||||
if (code == MOSQ_ERR_ERRNO) {
|
||||
char* error = strerror(errno);
|
||||
logOtherError("mqtt", "%s: errno %d=%s", method, errno, error);
|
||||
return false;
|
||||
}
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001)
|
||||
const char* msg = mosquitto_strerror(code);
|
||||
logOtherError("mqtt", "%s: %s", method, msg);
|
||||
#else
|
||||
logOtherError("mqtt", "%s: error code %d", method, code);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mqtthandler_register(UserInfo* userInfo, BusHandler* busHandler, MessageMap* messages,
|
||||
list<DataHandler*>* handlers) {
|
||||
if (g_port > 0) {
|
||||
int major = -1;
|
||||
mosquitto_lib_version(&major, nullptr, nullptr);
|
||||
int minor = -1;
|
||||
int revision = -1;
|
||||
mosquitto_lib_version(&major, &minor, &revision);
|
||||
if (major != LIBMOSQUITTO_MAJOR) {
|
||||
logOtherError("mqtt", "invalid mosquitto version %d instead of %d", major, LIBMOSQUITTO_MAJOR);
|
||||
return false;
|
||||
}
|
||||
logOtherInfo("mqtt", "mosquitto version %d.%d.%d (compiled with %d.%d.%d)", major, minor, revision,
|
||||
LIBMOSQUITTO_MAJOR, LIBMOSQUITTO_MINOR, LIBMOSQUITTO_REVISION);
|
||||
handlers->push_back(new MqttHandler(userInfo, busHandler, messages));
|
||||
}
|
||||
return true;
|
||||
@@ -331,10 +384,49 @@ void on_connect(
|
||||
}
|
||||
}
|
||||
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001)
|
||||
void on_log(struct mosquitto *mosq, void *obj, int level, const char* msg) {
|
||||
switch (level) {
|
||||
case MOSQ_LOG_DEBUG:
|
||||
logOtherDebug("mqtt", "log %s", msg);
|
||||
break;
|
||||
case MOSQ_LOG_INFO:
|
||||
logOtherInfo("mqtt", "log %s", msg);
|
||||
break;
|
||||
case MOSQ_LOG_NOTICE:
|
||||
logOtherNotice("mqtt", "log %s", msg);
|
||||
break;
|
||||
case MOSQ_LOG_WARNING:
|
||||
logOtherNotice("mqtt", "log warning %s", msg);
|
||||
break;
|
||||
case MOSQ_LOG_ERR:
|
||||
logOtherError("mqtt", "log %s", msg);
|
||||
break;
|
||||
default:
|
||||
logOtherError("mqtt", "log other %s", msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void on_message(
|
||||
#if (LIBMOSQUITTO_MAJOR >= 1)
|
||||
struct mosquitto *mosq,
|
||||
#endif
|
||||
void *obj, const struct mosquitto_message *message) {
|
||||
MqttHandler* handler = reinterpret_cast<MqttHandler*>(obj);
|
||||
if (!handler || !message || !handler->isRunning()) {
|
||||
return;
|
||||
}
|
||||
string topic(message->topic);
|
||||
string data(message->payloadlen > 0 ? reinterpret_cast<char*>(message->payload) : "");
|
||||
handler->notifyTopic(topic, data);
|
||||
}
|
||||
|
||||
|
||||
MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* messages)
|
||||
: DataSink(userInfo, "mqtt"), DataSource(busHandler), WaitThread(), m_messages(messages), m_connected(false),
|
||||
m_initialConnectFailed(false), m_lastUpdateCheckResult(".") {
|
||||
m_initialConnectFailed(false), m_lastUpdateCheckResult("."), m_lastErrorLogTime(0) {
|
||||
m_publishByField = false;
|
||||
m_mosquitto = nullptr;
|
||||
if (g_topicFields.empty()) {
|
||||
@@ -359,10 +451,7 @@ MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap*
|
||||
}
|
||||
m_globalTopic = getTopic(nullptr, "global/");
|
||||
m_subscribeTopic = getTopic(nullptr, "#");
|
||||
m_mosquitto = nullptr;
|
||||
if (mosquitto_lib_init() != MOSQ_ERR_SUCCESS) {
|
||||
logOtherError("mqtt", "unable to initialize");
|
||||
} else {
|
||||
if (check(mosquitto_lib_init(), "unable to initialize")) {
|
||||
signal(SIGPIPE, SIG_IGN); // needed before libmosquitto v. 1.1.3
|
||||
ostringstream clientId;
|
||||
if (g_clientId) {
|
||||
@@ -380,8 +469,11 @@ MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap*
|
||||
}
|
||||
}
|
||||
if (m_mosquitto) {
|
||||
/*mosquitto_log_init(m_mosquitto, MOSQ_LOG_DEBUG | MOSQ_LOG_ERR | MOSQ_LOG_WARNING
|
||||
| MOSQ_LOG_NOTICE | MOSQ_LOG_INFO, MOSQ_LOG_STDERR);*/
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001)
|
||||
check(mosquitto_threaded_set(m_mosquitto, true), "threaded_set");
|
||||
check(mosquitto_opts_set(m_mosquitto, MOSQ_OPT_PROTOCOL_VERSION, reinterpret_cast<void*>(&g_version)),
|
||||
"opts_set protocol version");
|
||||
#endif
|
||||
if (g_username || g_password) {
|
||||
if (!g_username) {
|
||||
g_username = PACKAGE;
|
||||
@@ -409,8 +501,14 @@ MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap*
|
||||
logOtherError("mqtt", "unable to set TLS: %d", ret);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001)
|
||||
if (g_logFromLib) {
|
||||
mosquitto_log_callback_set(m_mosquitto, on_log);
|
||||
}
|
||||
#endif
|
||||
mosquitto_connect_callback_set(m_mosquitto, on_connect);
|
||||
mosquitto_message_callback_set(m_mosquitto, on_message);
|
||||
int ret;
|
||||
#if (LIBMOSQUITTO_MAJOR >= 1)
|
||||
ret = mosquitto_connect(m_mosquitto, g_host, g_port, 60);
|
||||
@@ -421,16 +519,9 @@ MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap*
|
||||
logOtherError("mqtt", "unable to connect (invalid parameters)");
|
||||
mosquitto_destroy(m_mosquitto);
|
||||
m_mosquitto = nullptr;
|
||||
} else if (ret != MOSQ_ERR_SUCCESS) {
|
||||
} else if (!check(ret, "unable to connect, retrying")) {
|
||||
m_connected = false;
|
||||
m_initialConnectFailed = g_ignoreInvalidParams;
|
||||
char* error;
|
||||
if (ret != MOSQ_ERR_ERRNO) {
|
||||
error = (char*)("unkown mosquitto error");
|
||||
} else {
|
||||
error = strerror(errno);
|
||||
}
|
||||
logOtherError("mqtt", "unable to connect, retrying: %s", error);
|
||||
} else {
|
||||
m_connected = true; // assume success until connect_callback says otherwise
|
||||
logOtherDebug("mqtt", "connection requested");
|
||||
@@ -453,27 +544,12 @@ void MqttHandler::start() {
|
||||
}
|
||||
}
|
||||
|
||||
void on_message(
|
||||
#if (LIBMOSQUITTO_MAJOR >= 1)
|
||||
struct mosquitto *mosq,
|
||||
#endif
|
||||
void *obj, const struct mosquitto_message *message) {
|
||||
MqttHandler* handler = reinterpret_cast<MqttHandler*>(obj);
|
||||
if (!handler || !message || !handler->isRunning()) {
|
||||
return;
|
||||
}
|
||||
string topic(message->topic);
|
||||
string data(message->payloadlen > 0 ? reinterpret_cast<char*>(message->payload) : "");
|
||||
handler->notifyTopic(topic, data);
|
||||
}
|
||||
|
||||
void MqttHandler::notifyConnected() {
|
||||
if (m_mosquitto && isRunning()) {
|
||||
const string sep = (g_publishFormat & OF_JSON) ? "\"" : "";
|
||||
publishTopic(m_globalTopic+"version", sep + (PACKAGE_STRING "." REVISION) + sep, true);
|
||||
publishTopic(m_globalTopic+"running", "true", true);
|
||||
mosquitto_message_callback_set(m_mosquitto, on_message);
|
||||
mosquitto_subscribe(m_mosquitto, nullptr, m_subscribeTopic.c_str(), 0);
|
||||
check(mosquitto_subscribe(m_mosquitto, nullptr, m_subscribeTopic.c_str(), 0), "subscribe");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,33 +559,43 @@ void MqttHandler::notifyTopic(const string& topic, const string& data) {
|
||||
return;
|
||||
}
|
||||
string direction = topic.substr(pos+1);
|
||||
bool isWrite = false;
|
||||
if (direction.empty()) {
|
||||
return;
|
||||
}
|
||||
isWrite = direction == "set";
|
||||
if (!isWrite && direction != "get") {
|
||||
bool isWrite = direction == "set";
|
||||
bool isList = !isWrite && direction == "list";
|
||||
if (!isWrite && !isList && direction != "get") {
|
||||
return;
|
||||
}
|
||||
|
||||
logOtherDebug("mqtt", "received topic %s", topic.c_str(), data.c_str());
|
||||
logOtherDebug("mqtt", "received topic %s with data %s", topic.c_str(), data.c_str());
|
||||
string remain = topic.substr(0, pos);
|
||||
size_t last = 0;
|
||||
string circuit, name;
|
||||
size_t idx;
|
||||
for (idx = 0; idx < g_topicStrs.size()+1; idx++) {
|
||||
bool finalField = false;
|
||||
for (size_t idx = 0; idx < g_topicStrs.size()+1 && !finalField; idx++) {
|
||||
string field;
|
||||
string chk;
|
||||
if (idx < g_topicStrs.size()) {
|
||||
chk = g_topicStrs[idx];
|
||||
pos = remain.find(chk, last);
|
||||
if (pos == string::npos) {
|
||||
return;
|
||||
if (!isList) {
|
||||
return;
|
||||
}
|
||||
if (idx == 0 && remain+"/" == chk) { // check for only first prefix, e.g. "ebusd/"
|
||||
break;
|
||||
}
|
||||
pos = remain.size();
|
||||
finalField = true;
|
||||
}
|
||||
} else if (idx-1 < g_topicFields.size()) {
|
||||
pos = remain.size();
|
||||
} else if (last < remain.size()) {
|
||||
return;
|
||||
if (!isList) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -521,7 +607,10 @@ void MqttHandler::notifyTopic(const string& topic, const string& data) {
|
||||
}
|
||||
} else {
|
||||
if (field.empty()) {
|
||||
return;
|
||||
if (!isList) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
string fieldName = g_topicFields[idx-1];
|
||||
if (fieldName == "circuit") {
|
||||
@@ -535,10 +624,25 @@ void MqttHandler::notifyTopic(const string& topic, const string& data) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isList) {
|
||||
logOtherInfo("mqtt", "received list topic for %s %s", circuit.c_str(), name.c_str());
|
||||
deque<Message*> messages;
|
||||
m_messages->findAll(circuit, name, m_levels, true, true, true, true, true, true, 0, 0, false, &messages);
|
||||
bool onlyWithData = !data.empty();
|
||||
for (const auto message : messages) {
|
||||
time_t lastup = message->getLastUpdateTime();
|
||||
if (onlyWithData && lastup == 0) {
|
||||
continue;
|
||||
}
|
||||
ostringstream ostream;
|
||||
publishMessage(message, &ostream, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
logOtherInfo("mqtt", "received topic for %s %s", circuit.c_str(), name.c_str());
|
||||
logOtherInfo("mqtt", "received %s topic for %s %s", direction.c_str(), circuit.c_str(), name.c_str());
|
||||
Message* message = m_messages->find(circuit, name, m_levels, isWrite);
|
||||
if (message == nullptr) {
|
||||
message = m_messages->find(circuit, name, m_levels, isWrite, true);
|
||||
@@ -548,7 +652,25 @@ void MqttHandler::notifyTopic(const string& topic, const string& data) {
|
||||
return;
|
||||
}
|
||||
if (!message->isPassive()) {
|
||||
result_t result = m_busHandler->readFromBus(message, data);
|
||||
string useData = data;
|
||||
if (!isWrite && !data.empty()) {
|
||||
size_t pos = useData.find_last_of('?');
|
||||
if (pos != string::npos && pos > 0 && useData[pos-1] != UI_FIELD_SEPARATOR) {
|
||||
pos = string::npos;
|
||||
}
|
||||
if (pos != string::npos) {
|
||||
string args = useData.substr(pos + 1);
|
||||
useData = useData.substr(0, pos > 0 ? pos - 1 : pos);
|
||||
if (!args.empty()) {
|
||||
result_t ret = RESULT_OK;
|
||||
size_t pollPriority = (size_t)parseInt(args.c_str(), 10, 1, 9, &ret);
|
||||
if (ret == RESULT_OK && pollPriority > 0 && message->setPollPriority(pollPriority)) {
|
||||
m_messages->addPollMessage(false, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result_t result = m_busHandler->readFromBus(message, useData);
|
||||
if (result != RESULT_OK) {
|
||||
logOtherError("mqtt", "%s %s %s: %s", isWrite?"write":"read", circuit.c_str(), name.c_str(),
|
||||
getResultCode(result));
|
||||
@@ -580,7 +702,7 @@ void MqttHandler::run() {
|
||||
bool allowReconnect = false;
|
||||
while (isRunning()) {
|
||||
bool wasConnected = m_connected;
|
||||
handleTraffic(allowReconnect);
|
||||
bool needsWait = handleTraffic(allowReconnect);
|
||||
bool reconnected = !wasConnected && m_connected;
|
||||
allowReconnect = false;
|
||||
time(&now);
|
||||
@@ -606,12 +728,12 @@ void MqttHandler::run() {
|
||||
lastSignal = now;
|
||||
if (!signal || reconnected) {
|
||||
signal = true;
|
||||
publishTopic(signalTopic, "true");
|
||||
publishTopic(signalTopic, "true", true);
|
||||
}
|
||||
} else {
|
||||
if (signal || reconnected) {
|
||||
signal = false;
|
||||
publishTopic(signalTopic, "false");
|
||||
publishTopic(signalTopic, "false", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -639,52 +761,60 @@ void MqttHandler::run() {
|
||||
m_updatedMessages.clear();
|
||||
}
|
||||
}
|
||||
if (!m_connected && !Wait(5)) {
|
||||
if ((!m_connected && !Wait(5)) || (needsWait && !Wait(1))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
publishTopic(signalTopic, "false", true);
|
||||
}
|
||||
|
||||
void MqttHandler::handleTraffic(bool allowReconnect) {
|
||||
if (m_mosquitto) {
|
||||
int ret;
|
||||
bool MqttHandler::handleTraffic(bool allowReconnect) {
|
||||
if (!m_mosquitto) {
|
||||
return false;
|
||||
}
|
||||
int ret;
|
||||
#if (LIBMOSQUITTO_MAJOR >= 1)
|
||||
ret = mosquitto_loop(m_mosquitto, -1, 1); // waits up to 1 second for network traffic
|
||||
ret = mosquitto_loop(m_mosquitto, -1, 1); // waits up to 1 second for network traffic
|
||||
#else
|
||||
ret = mosquitto_loop(m_mosquitto, -1); // waits up to 1 second for network traffic
|
||||
ret = mosquitto_loop(m_mosquitto, -1); // waits up to 1 second for network traffic
|
||||
#endif
|
||||
if (!m_connected && ret == MOSQ_ERR_NO_CONN && allowReconnect) {
|
||||
if (m_initialConnectFailed) {
|
||||
if (!m_connected && ret == MOSQ_ERR_NO_CONN && allowReconnect) {
|
||||
if (m_initialConnectFailed) {
|
||||
#if (LIBMOSQUITTO_MAJOR >= 1)
|
||||
ret = mosquitto_connect(m_mosquitto, g_host, g_port, 60);
|
||||
ret = mosquitto_connect(m_mosquitto, g_host, g_port, 60);
|
||||
#else
|
||||
ret = mosquitto_connect(m_mosquitto, g_host, g_port, 60, true);
|
||||
ret = mosquitto_connect(m_mosquitto, g_host, g_port, 60, true);
|
||||
#endif
|
||||
if (ret == MOSQ_ERR_INVAL) {
|
||||
logOtherError("mqtt", "unable to connect (invalid parameters), retrying");
|
||||
}
|
||||
if (ret == MOSQ_ERR_SUCCESS) {
|
||||
m_initialConnectFailed = false;
|
||||
}
|
||||
} else {
|
||||
ret = mosquitto_reconnect(m_mosquitto);
|
||||
if (ret == MOSQ_ERR_INVAL) {
|
||||
logOtherError("mqtt", "unable to connect (invalid parameters), retrying");
|
||||
}
|
||||
if (ret == MOSQ_ERR_SUCCESS) {
|
||||
m_initialConnectFailed = false;
|
||||
}
|
||||
}
|
||||
if (!m_connected && ret == MOSQ_ERR_SUCCESS) {
|
||||
m_connected = true;
|
||||
logOtherNotice("mqtt", "connection re-established");
|
||||
}
|
||||
if (!m_connected || ret == MOSQ_ERR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
if (ret == MOSQ_ERR_NO_CONN || ret == MOSQ_ERR_CONN_LOST || ret == MOSQ_ERR_CONN_REFUSED) {
|
||||
logOtherError("mqtt", "communication error: %s", ret == MOSQ_ERR_NO_CONN ? "not connected"
|
||||
: (ret == MOSQ_ERR_CONN_LOST ? "connection lost" : "connection refused"));
|
||||
m_connected = false;
|
||||
} else {
|
||||
logOtherError("mqtt", "communication error: %d", ret);
|
||||
ret = mosquitto_reconnect(m_mosquitto);
|
||||
}
|
||||
}
|
||||
if (!m_connected && ret == MOSQ_ERR_SUCCESS) {
|
||||
m_connected = true;
|
||||
logOtherNotice("mqtt", "connection re-established");
|
||||
}
|
||||
if (!m_connected || ret == MOSQ_ERR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
if (ret == MOSQ_ERR_NO_CONN || ret == MOSQ_ERR_CONN_LOST || ret == MOSQ_ERR_CONN_REFUSED) {
|
||||
logOtherError("mqtt", "communication error: %s", ret == MOSQ_ERR_NO_CONN ? "not connected"
|
||||
: (ret == MOSQ_ERR_CONN_LOST ? "connection lost" : "connection refused"));
|
||||
m_connected = false;
|
||||
} else {
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (now > m_lastErrorLogTime + 10) { // log at most every 10 seconds
|
||||
m_lastErrorLogTime = now;
|
||||
check(ret, "communication error");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
string MqttHandler::getTopic(const Message* message, const string& suffix, const string& fieldName) {
|
||||
@@ -708,10 +838,15 @@ string MqttHandler::getTopic(const Message* message, const string& suffix, const
|
||||
return ret.str();
|
||||
}
|
||||
|
||||
void MqttHandler::publishMessage(const Message* message, ostringstream* updates) {
|
||||
void MqttHandler::publishMessage(const Message* message, ostringstream* updates, bool includeWithoutData) {
|
||||
OutputFormat outputFormat = g_publishFormat;
|
||||
bool json = outputFormat & OF_JSON;
|
||||
bool noData = includeWithoutData && message->getLastUpdateTime() == 0;
|
||||
if (!m_publishByField) {
|
||||
if (noData) {
|
||||
publishEmptyTopic(getTopic(message)); // alternatively: , json ? "null" : "");
|
||||
return;
|
||||
}
|
||||
if (json) {
|
||||
*updates << "{";
|
||||
}
|
||||
@@ -732,6 +867,10 @@ void MqttHandler::publishMessage(const Message* message, ostringstream* updates)
|
||||
}
|
||||
for (size_t index = 0; index < message->getFieldCount(); index++) {
|
||||
string name = message->getFieldName(index);
|
||||
if (noData) {
|
||||
publishEmptyTopic(getTopic(message, "", name)); // alternatively: , json ? "null" : "");
|
||||
continue;
|
||||
}
|
||||
result_t result = message->decodeLastData(false, nullptr, index, outputFormat, updates);
|
||||
if (result != RESULT_OK) {
|
||||
logOtherError("mqtt", "decode %s %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
|
||||
@@ -745,9 +884,18 @@ void MqttHandler::publishMessage(const Message* message, ostringstream* updates)
|
||||
}
|
||||
|
||||
void MqttHandler::publishTopic(const string& topic, const string& data, bool retain) {
|
||||
logOtherDebug("mqtt", "publish %s %s", topic.c_str(), data.c_str());
|
||||
mosquitto_publish(m_mosquitto, nullptr, topic.c_str(), (uint32_t)data.size(),
|
||||
reinterpret_cast<const uint8_t*>(data.c_str()), 0, g_retain || retain);
|
||||
const char* topicStr = topic.c_str();
|
||||
const char* dataStr = data.c_str();
|
||||
const size_t len = strlen(dataStr);
|
||||
logOtherDebug("mqtt", "publish %s %s", topicStr, dataStr);
|
||||
check(mosquitto_publish(m_mosquitto, nullptr, topicStr, (uint32_t)len,
|
||||
reinterpret_cast<const uint8_t*>(dataStr), 0, g_retain || retain), "publish");
|
||||
}
|
||||
|
||||
void MqttHandler::publishEmptyTopic(const string& topic) {
|
||||
const char* topicStr = topic.c_str();
|
||||
logOtherDebug("mqtt", "publish empty %s", topicStr);
|
||||
check(mosquitto_publish(m_mosquitto, nullptr, topicStr, 0, nullptr, 0, g_retain), "publish empty");
|
||||
}
|
||||
|
||||
} // namespace ebusd
|
||||
|
||||
+13
-2
@@ -100,8 +100,9 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread {
|
||||
/**
|
||||
* Called regularly to handle MQTT traffic.
|
||||
* @param allowReconnect true when reconnecting to the broker is allowed.
|
||||
* @return true on error for waiting a bit until next call, or false otherwise.
|
||||
*/
|
||||
void handleTraffic(bool allowReconnect);
|
||||
bool handleTraffic(bool allowReconnect);
|
||||
|
||||
/**
|
||||
* Build the MQTT topic string for the @a Message.
|
||||
@@ -116,8 +117,9 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread {
|
||||
* Prepare a @a Message and publish as topic.
|
||||
* @param message the @a Message to publish.
|
||||
* @param updates the @a ostringstream for preparation.
|
||||
* @param includeWithoutData whether to publish messages without data as well.
|
||||
*/
|
||||
void publishMessage(const Message* message, ostringstream* updates);
|
||||
void publishMessage(const Message* message, ostringstream* updates, bool includeWithoutData = false);
|
||||
|
||||
/**
|
||||
* Publish a topic update to MQTT.
|
||||
@@ -127,6 +129,12 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread {
|
||||
*/
|
||||
void publishTopic(const string& topic, const string& data, bool retain = false);
|
||||
|
||||
/**
|
||||
* Publish a topic update to MQTT without any data.
|
||||
* @param topic the topic string.
|
||||
*/
|
||||
void publishEmptyTopic(const string& topic);
|
||||
|
||||
/** the @a MessageMap instance. */
|
||||
MessageMap* m_messages;
|
||||
|
||||
@@ -150,6 +158,9 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread {
|
||||
|
||||
/** the last update check result. */
|
||||
string m_lastUpdateCheckResult;
|
||||
|
||||
/** the last system time when a communication error was logged. */
|
||||
time_t m_lastErrorLogTime;
|
||||
};
|
||||
|
||||
} // namespace ebusd
|
||||
|
||||
@@ -65,7 +65,7 @@ bool NetMessage::add(const char* request) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return m_request.length() == 0 && m_listening;
|
||||
return m_request.length() == 0 && isListeningMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ void Connection::run() {
|
||||
#endif
|
||||
}
|
||||
|
||||
if (newData || message.isListening()) {
|
||||
if (newData || message.isListeningMode()) {
|
||||
char data[256];
|
||||
|
||||
if (!m_socket->isValid()) {
|
||||
@@ -208,7 +208,7 @@ Network::~Network() {
|
||||
stop();
|
||||
NetMessage* netMsg;
|
||||
while ((netMsg = m_netQueue->pop()) != nullptr) {
|
||||
netMsg->setResult("ERR: shutdown", "", false, 0, true);
|
||||
netMsg->setResult("ERR: shutdown", "", cm_normal, 0, true);
|
||||
}
|
||||
while (!m_connections.empty()) {
|
||||
Connection* connection = m_connections.back();
|
||||
|
||||
+25
-12
@@ -37,6 +37,13 @@ namespace ebusd {
|
||||
/** Forward declaration for @a Connection. */
|
||||
class Connection;
|
||||
|
||||
/** the possible client modes. */
|
||||
enum ClientMode {
|
||||
cm_normal, //!< normal mode
|
||||
cm_listen, //!< listening mode
|
||||
cm_direct, //!< direct mode
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for data/message transfer between @a Connection and @a MainLoop.
|
||||
*/
|
||||
@@ -47,7 +54,7 @@ class NetMessage {
|
||||
* @param isHttp whether this is a HTTP message.
|
||||
*/
|
||||
explicit NetMessage(bool isHttp)
|
||||
: m_isHttp(isHttp), m_resultSet(false), m_disconnect(false), m_listening(false), m_listenSince(0) {
|
||||
: m_isHttp(isHttp), m_resultSet(false), m_disconnect(false), m_mode(cm_normal), m_listenSince(0) {
|
||||
pthread_mutex_init(&m_mutex, nullptr);
|
||||
pthread_cond_init(&m_cond, nullptr);
|
||||
}
|
||||
@@ -117,16 +124,16 @@ class NetMessage {
|
||||
* Set the result string and notify the waiting thread.
|
||||
* @param result the result string.
|
||||
* @param user the new user name.
|
||||
* @param listening whether the client is in listening mode.
|
||||
* @param mode the new client mode.
|
||||
* @param listenUntil the end time to which to updates were added (exclusive).
|
||||
* @param disconnect true when the client shall be disconnected.
|
||||
*/
|
||||
void setResult(const string& result, const string& user, bool listening, time_t listenUntil, bool disconnect) {
|
||||
void setResult(const string& result, const string& user, ClientMode mode, time_t listenUntil, bool disconnect) {
|
||||
pthread_mutex_lock(&m_mutex);
|
||||
m_result = result;
|
||||
m_user = user;
|
||||
m_disconnect = disconnect;
|
||||
m_listening = listening;
|
||||
m_mode = mode;
|
||||
m_listenSince = listenUntil;
|
||||
m_resultSet = true;
|
||||
pthread_cond_signal(&m_cond);
|
||||
@@ -134,17 +141,23 @@ class NetMessage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the client is in listening mode.
|
||||
* @param listenSince set to the start time from which to add updates (inclusive).
|
||||
* @return whether the client is in listening mode.
|
||||
* Return the client mode.
|
||||
* @param listenSince set listening to the specified start time from which to add updates (inclusive).
|
||||
* @return the client mode.
|
||||
*/
|
||||
bool isListening(time_t* listenSince = nullptr) {
|
||||
ClientMode getMode(time_t* listenSince = nullptr) {
|
||||
if (listenSince) {
|
||||
*listenSince = m_listenSince;
|
||||
}
|
||||
return m_listening;
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this instance is in one of the listening modes.
|
||||
* @return whether this instance is in one of the listening modes.
|
||||
*/
|
||||
bool isListeningMode() { return m_mode == cm_listen || m_mode == cm_direct; }
|
||||
|
||||
/**
|
||||
* Return whether the client shall be disconnected.
|
||||
* @return true when the client shall be disconnected.
|
||||
@@ -177,8 +190,8 @@ class NetMessage {
|
||||
/** condition variable for exclusive lock. */
|
||||
pthread_cond_t m_cond;
|
||||
|
||||
/** whether the client is in listening mode. */
|
||||
bool m_listening;
|
||||
/** the client mode. */
|
||||
ClientMode m_mode;
|
||||
|
||||
/** start timestamp of listening update. */
|
||||
time_t m_listenSince;
|
||||
@@ -284,7 +297,7 @@ class Network : public Thread {
|
||||
/** @a Notify object for shutdown procedure. */
|
||||
Notify m_notify;
|
||||
|
||||
/** true if this instance is listening */
|
||||
/** true if this instance is listening. */
|
||||
bool m_listening;
|
||||
|
||||
/**
|
||||
|
||||
Executable → Regular
+25
-11
@@ -647,8 +647,11 @@ size_t SingleDataField::getLength(PartType partType, size_t maxLength) const {
|
||||
return remainder ? maxLength : m_length;
|
||||
}
|
||||
|
||||
bool SingleDataField::hasFullByteOffset(bool after) const {
|
||||
bool SingleDataField::hasFullByteOffset(bool after, int16_t& previousFirstBit) const {
|
||||
if (m_length > 1) {
|
||||
if (after) {
|
||||
previousFirstBit = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
int16_t firstBit;
|
||||
@@ -658,8 +661,15 @@ bool SingleDataField::hasFullByteOffset(bool after) const {
|
||||
} else {
|
||||
firstBit = 0;
|
||||
}
|
||||
return (m_dataType->getBitCount() % 8) == 0
|
||||
|| (after && firstBit + (m_dataType->getBitCount() % 8) >= 8);
|
||||
bool ret = (m_dataType->getBitCount() % 8) == 0
|
||||
|| (firstBit == previousFirstBit) || (after && firstBit + (m_dataType->getBitCount() % 8) >= 8);
|
||||
// std::cout<<(after?"after,":"before,")<<"prev="<<static_cast<unsigned>(previousFirstBit)<<",first="
|
||||
// <<static_cast<unsigned>(firstBit)<<",length="<<static_cast<unsigned>(m_dataType->getBitCount())
|
||||
// <<" => "<<(ret?"true":"false")<<"\n";
|
||||
if (after) {
|
||||
previousFirstBit = ret ? -1 : firstBit;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t SingleDataField::getCount(PartType partType, const char* fieldName) const {
|
||||
@@ -915,9 +925,10 @@ const DataFieldSet* DataFieldSet::clone() const {
|
||||
size_t DataFieldSet::getLength(PartType partType, size_t maxLength) const {
|
||||
size_t length = 0;
|
||||
bool previousFullByteOffset[] = { true, true, true, true };
|
||||
int16_t previousFirstBit[] = { -1, -1, -1, -1 };
|
||||
for (const auto field : m_fields) {
|
||||
if (field->getPartType() == partType) {
|
||||
if (!previousFullByteOffset[partType] && !field->hasFullByteOffset(false)) {
|
||||
if (!previousFullByteOffset[partType] && !field->hasFullByteOffset(false, previousFirstBit[partType])) {
|
||||
length--;
|
||||
}
|
||||
size_t fieldLength = field->getLength(partType, maxLength);
|
||||
@@ -928,7 +939,7 @@ size_t DataFieldSet::getLength(PartType partType, size_t maxLength) const {
|
||||
}
|
||||
length = length + fieldLength;
|
||||
|
||||
previousFullByteOffset[partType] = field->hasFullByteOffset(true);
|
||||
previousFullByteOffset[partType] = field->hasFullByteOffset(true, previousFirstBit[partType]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,12 +1021,13 @@ void DataFieldSet::dump(bool prependFieldSeparator, bool asJson, ostream* output
|
||||
result_t DataFieldSet::read(const SymbolString& data, size_t offset,
|
||||
const char* fieldName, ssize_t fieldIndex, unsigned int* output) const {
|
||||
bool previousFullByteOffset = true, found = false, findFieldIndex = fieldIndex >= 0;
|
||||
int16_t previousFirstBit = -1;
|
||||
PartType partType = data.isMaster() ? pt_masterData : pt_slaveData;
|
||||
for (const auto field : m_fields) {
|
||||
if (field->getPartType() != partType) {
|
||||
continue;
|
||||
}
|
||||
if (!previousFullByteOffset && !field->hasFullByteOffset(false)) {
|
||||
if (!previousFullByteOffset && !field->hasFullByteOffset(false, previousFirstBit)) {
|
||||
offset--;
|
||||
}
|
||||
result_t result = field->read(data, offset, fieldName, fieldIndex, output);
|
||||
@@ -1023,7 +1035,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
|
||||
return result;
|
||||
}
|
||||
offset += field->getLength(partType, data.getDataSize()-offset);
|
||||
previousFullByteOffset = field->hasFullByteOffset(true);
|
||||
previousFullByteOffset = field->hasFullByteOffset(true, previousFirstBit);
|
||||
if (result != RESULT_EMPTY) {
|
||||
found = true;
|
||||
}
|
||||
@@ -1049,6 +1061,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
|
||||
bool leadingSeparator, const char* fieldName, ssize_t fieldIndex,
|
||||
OutputFormat outputFormat, ssize_t outputIndex, ostream* output) const {
|
||||
bool previousFullByteOffset = true, found = false, findFieldIndex = fieldIndex >= 0;
|
||||
int16_t previousFirstBit = -1;
|
||||
if (outputIndex < 0 && (!m_uniqueNames || ((outputFormat & OF_JSON) && !(outputFormat & OF_NAMES)))) {
|
||||
outputIndex = 0;
|
||||
}
|
||||
@@ -1060,7 +1073,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!previousFullByteOffset && !field->hasFullByteOffset(false)) {
|
||||
if (!previousFullByteOffset && !field->hasFullByteOffset(false, previousFirstBit)) {
|
||||
offset--;
|
||||
}
|
||||
result_t result = field->read(data, offset, leadingSeparator, fieldName, fieldIndex,
|
||||
@@ -1069,7 +1082,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
|
||||
return result;
|
||||
}
|
||||
offset += field->getLength(partType, data.getDataSize()-offset);
|
||||
previousFullByteOffset = field->hasFullByteOffset(true);
|
||||
previousFullByteOffset = field->hasFullByteOffset(true, previousFirstBit);
|
||||
if (result != RESULT_EMPTY) {
|
||||
found = true;
|
||||
leadingSeparator = true;
|
||||
@@ -1099,12 +1112,13 @@ result_t DataFieldSet::write(char separator, size_t offset, istringstream* input
|
||||
string token;
|
||||
PartType partType = data->isMaster() ? pt_masterData : pt_slaveData;
|
||||
bool previousFullByteOffset = true;
|
||||
int16_t previousFirstBit = -1;
|
||||
size_t baseOffset = offset;
|
||||
for (const auto field : m_fields) {
|
||||
if (field->getPartType() != partType) {
|
||||
continue;
|
||||
}
|
||||
if (!previousFullByteOffset && !field->hasFullByteOffset(false)) {
|
||||
if (!previousFullByteOffset && !field->hasFullByteOffset(false, previousFirstBit)) {
|
||||
offset--;
|
||||
}
|
||||
result_t result;
|
||||
@@ -1124,7 +1138,7 @@ result_t DataFieldSet::write(char separator, size_t offset, istringstream* input
|
||||
return result;
|
||||
}
|
||||
offset += fieldLength;
|
||||
previousFullByteOffset = field->hasFullByteOffset(true);
|
||||
previousFullByteOffset = field->hasFullByteOffset(true, previousFirstBit);
|
||||
}
|
||||
|
||||
if (usedLength != nullptr) {
|
||||
|
||||
+4
-1
@@ -401,10 +401,13 @@ class SingleDataField : public DataField {
|
||||
/**
|
||||
* Get whether this field uses a full byte offset.
|
||||
* @param after @p true to check after consuming the bits, @p false to check before.
|
||||
* @param previousFirstBit the index to the first bit of the previous field, or -1
|
||||
* if the previous field used a full byte offset. Will be updated during the call
|
||||
* when after was true.
|
||||
* @return @p true if this field uses a full byte offset, @p false if this field
|
||||
* only consumes a part of a byte and a subsequent field may re-use the same offset.
|
||||
*/
|
||||
bool hasFullByteOffset(bool after) const;
|
||||
bool hasFullByteOffset(bool after, int16_t& previousFirstBit) const;
|
||||
|
||||
// @copydoc
|
||||
size_t getCount(PartType partType = pt_any, const char* fieldName = nullptr) const override;
|
||||
|
||||
@@ -696,7 +696,7 @@ result_t NumberDataType::readSymbols(size_t offset, size_t length, const SymbolS
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (isnan(val)) {
|
||||
if (val != val) { // !isnan(val)
|
||||
if (outputFormat & OF_JSON) {
|
||||
*output << "null";
|
||||
} else {
|
||||
|
||||
+20
-3
@@ -30,6 +30,9 @@
|
||||
#ifdef HAVE_LINUX_SERIAL
|
||||
# include <linux/serial.h>
|
||||
#endif
|
||||
#ifdef HAVE_FREEBSD_UFTDI
|
||||
# include <dev/usb/uftdiio.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
#ifdef HAVE_PPOLL
|
||||
# include <poll.h>
|
||||
@@ -392,7 +395,7 @@ result_t SerialDevice::open() {
|
||||
struct termios newSettings;
|
||||
|
||||
// open file descriptor
|
||||
m_fd = ::open(m_name, O_RDWR | O_NOCTTY);
|
||||
m_fd = ::open(m_name, O_RDWR | O_NOCTTY | O_NDELAY);
|
||||
|
||||
if (m_fd < 0) {
|
||||
return RESULT_ERR_NOTFOUND;
|
||||
@@ -415,13 +418,24 @@ result_t SerialDevice::open() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FREEBSD_UFTDI
|
||||
int param = 0;
|
||||
// flush tx/rx and set low latency on uftdi device
|
||||
if (ioctl(m_fd, UFTDIIOC_GET_LATENCY, ¶m) == 0) {
|
||||
ioctl(m_fd, UFTDIIOC_RESET_IO, ¶m);
|
||||
param = 1;
|
||||
ioctl(m_fd, UFTDIIOC_SET_LATENCY, ¶m);
|
||||
}
|
||||
#endif
|
||||
|
||||
// save current settings
|
||||
tcgetattr(m_fd, &m_oldSettings);
|
||||
|
||||
// create new settings
|
||||
memset(&newSettings, 0, sizeof(newSettings));
|
||||
|
||||
newSettings.c_cflag |= ((m_enhancedProto ? B115200 : B2400) | CS8 | CLOCAL | CREAD);
|
||||
cfsetspeed(&newSettings, m_enhancedProto ? B115200 : B2400);
|
||||
newSettings.c_cflag |= (CS8 | CLOCAL | CREAD);
|
||||
newSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // non-canonical mode
|
||||
newSettings.c_iflag |= IGNPAR; // ignore parity errors
|
||||
newSettings.c_oflag &= ~OPOST;
|
||||
@@ -434,7 +448,10 @@ result_t SerialDevice::open() {
|
||||
tcflush(m_fd, TCIFLUSH);
|
||||
|
||||
// activate new settings of serial device
|
||||
tcsetattr(m_fd, TCSAFLUSH, &newSettings);
|
||||
if (tcsetattr(m_fd, TCSAFLUSH, &newSettings)) {
|
||||
close();
|
||||
return RESULT_ERR_DEVICE;
|
||||
}
|
||||
|
||||
// set serial device into blocking mode
|
||||
fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) & ~O_NONBLOCK);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
@@ -120,7 +120,7 @@ void FileReader::tolower(string* str) {
|
||||
|
||||
static size_t hashFunction(const string& str) {
|
||||
size_t hash = 0;
|
||||
for (char c : str) {
|
||||
for (unsigned char c : str) {
|
||||
hash = (31 * hash) ^ c;
|
||||
}
|
||||
return hash;
|
||||
|
||||
Executable → Regular
+82
-55
@@ -45,12 +45,24 @@ using std::endl;
|
||||
/** the bit mask for the ID length and combined ID bytes in the message key. */
|
||||
#define ID_LENGTH_AND_IDS_MASK ((7LL << (8 * 7 + 5)) | 0xffffffffLL)
|
||||
|
||||
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active read message. */
|
||||
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active write message. */
|
||||
#define ID_SOURCE_ACTIVE_WRITE (0x1fLL << (8 * 7))
|
||||
|
||||
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active write message. */
|
||||
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active read message. */
|
||||
#define ID_SOURCE_ACTIVE_READ (0x1eLL << (8 * 7))
|
||||
|
||||
/**
|
||||
* the bits in the @a ID_SOURCE_MASK for arbitrary source and active write message
|
||||
* to a master (same value as ID_SOURCE_ACTIVE_WRITE for now).
|
||||
*/
|
||||
#define ID_SOURCE_ACTIVE_WRITE_MASTER (0x1fLL << (8 * 7))
|
||||
|
||||
/**
|
||||
* the bits in the @a ID_SOURCE_MASK for arbitrary source and active read message
|
||||
* to a master (same value as ID_SOURCE_ACTIVE_WRITE for now).
|
||||
*/
|
||||
#define ID_SOURCE_ACTIVE_READ_MASTER (0x1eLL << (8 * 7))
|
||||
|
||||
/** special value for invalid message key. */
|
||||
#define INVALID_KEY 0xffffffffffffffffLL
|
||||
|
||||
@@ -79,6 +91,9 @@ static const char* defaultMessageFieldMap[] = { // access level not included in
|
||||
"*name", "part", "type", "divisor/values", "unit", "comment",
|
||||
};
|
||||
|
||||
/** the m_pollOrder of the last polled message. */
|
||||
static unsigned int g_lastPollOrder = 0;
|
||||
|
||||
extern DataFieldTemplates* getTemplates(const string& filename);
|
||||
|
||||
extern result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
|
||||
@@ -99,7 +114,7 @@ Message::Message(const string& circuit, const string& level, const string& name,
|
||||
m_data(data), m_deleteData(deleteData),
|
||||
m_pollPriority(pollPriority),
|
||||
m_usedByCondition(false), m_isScanMessage(false), m_condition(condition),
|
||||
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollCount(0), m_lastPollTime(0) {
|
||||
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollOrder(0), m_lastPollTime(0) {
|
||||
if (circuit == "scan") {
|
||||
setScanMessage();
|
||||
m_pollPriority = 0;
|
||||
@@ -116,7 +131,7 @@ Message::Message(const string& circuit, const string& level, const string& name,
|
||||
m_data(data), m_deleteData(deleteData),
|
||||
m_pollPriority(0),
|
||||
m_usedByCondition(false), m_isScanMessage(true), m_condition(nullptr),
|
||||
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollCount(0), m_lastPollTime(0) {
|
||||
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollOrder(0), m_lastPollTime(0) {
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +173,8 @@ uint64_t Message::createKey(const vector<symbol_t>& id, bool isWrite, bool isPas
|
||||
if (isPassive) {
|
||||
key |= (uint64_t)getMasterNumber(srcAddress) << (8 * 7); // 0..25
|
||||
} else {
|
||||
key |= (isWrite ? 0x1fLL : 0x1eLL) << (8 * 7); // special values for active
|
||||
key |= isMaster(dstAddress) ? (isWrite ? ID_SOURCE_ACTIVE_WRITE_MASTER : ID_SOURCE_ACTIVE_READ_MASTER)
|
||||
: (isWrite ? ID_SOURCE_ACTIVE_WRITE : ID_SOURCE_ACTIVE_READ); // special values for active
|
||||
}
|
||||
key |= (uint64_t)dstAddress << (8 * 6);
|
||||
int exp = 5;
|
||||
@@ -199,7 +215,7 @@ uint64_t Message::createKey(const MasterSymbolString& master, size_t maxIdLength
|
||||
|
||||
uint64_t Message::createKey(symbol_t pb, symbol_t sb, bool broadcast) {
|
||||
uint64_t key = 0;
|
||||
key |= (broadcast ? 0x1fLL : 0x1eLL) << (8 * 7); // special values for active
|
||||
key |= broadcast ? ID_SOURCE_ACTIVE_WRITE : ID_SOURCE_ACTIVE_READ; // special values for active
|
||||
key |= (uint64_t)(broadcast ? BROADCAST : SYN) << (8 * 6);
|
||||
key |= (uint64_t)pb << (8 * 5);
|
||||
key |= (uint64_t)sb << (8 * 4);
|
||||
@@ -596,9 +612,29 @@ bool Message::setPollPriority(size_t priority) {
|
||||
}
|
||||
bool ret = m_pollPriority == 0 && usePriority > 0;
|
||||
m_pollPriority = usePriority;
|
||||
if (ret || m_pollOrder > g_lastPollOrder+(unsigned int)m_pollPriority) {
|
||||
// ensure a later increased or newly set priority does not prefer that message before all others
|
||||
m_pollOrder = g_lastPollOrder+(unsigned int)m_pollPriority;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Message::isLessPollWeight(const Message* other) const {
|
||||
if (m_pollOrder > other->m_pollOrder) {
|
||||
return true;
|
||||
}
|
||||
if (m_pollOrder < other->m_pollOrder) {
|
||||
return false;
|
||||
}
|
||||
if (m_pollPriority > other->m_pollPriority) {
|
||||
return true;
|
||||
}
|
||||
if (m_pollPriority < other->m_pollPriority) {
|
||||
return false;
|
||||
}
|
||||
return m_lastPollTime > other->m_lastPollTime;
|
||||
}
|
||||
|
||||
void Message::setUsedByCondition() {
|
||||
if (m_usedByCondition) {
|
||||
return;
|
||||
@@ -786,26 +822,6 @@ result_t Message::decodeLastDataNumField(const char* fieldName, ssize_t fieldInd
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Message::isLessPollWeight(const Message* other) const {
|
||||
size_t tprio = m_pollPriority;
|
||||
size_t oprio = other->m_pollPriority;
|
||||
size_t tw = tprio * m_pollCount;
|
||||
size_t ow = oprio * other->m_pollCount;
|
||||
if (tw > ow) {
|
||||
return true;
|
||||
}
|
||||
if (tw < ow) {
|
||||
return false;
|
||||
}
|
||||
if (tprio > oprio) {
|
||||
return true;
|
||||
}
|
||||
if (tprio < oprio) {
|
||||
return false;
|
||||
}
|
||||
return m_lastPollTime > other->m_lastPollTime;
|
||||
}
|
||||
|
||||
void Message::dumpHeader(const vector<string>* fieldNames, ostream* output) {
|
||||
bool first = true;
|
||||
if (fieldNames == nullptr) {
|
||||
@@ -1267,6 +1283,7 @@ void ChainedMessage::dumpField(const string& fieldName, bool withConditions, ost
|
||||
* @param sameIdExtAs the optional @a MasterSymbolString to check for having the same ID.
|
||||
* @param onlyAvailable true to include only available messages (default true), false to also include messages that
|
||||
* are currently not available (e.g. due to unresolved or false conditions).
|
||||
* @return the first available @a Message from the list.
|
||||
*/
|
||||
Message* getFirstAvailable(const vector<Message*>& messages, const MasterSymbolString* sameIdExtAs,
|
||||
const bool onlyAvailable = true) {
|
||||
@@ -1287,6 +1304,7 @@ Message* getFirstAvailable(const vector<Message*>& messages, const MasterSymbolS
|
||||
* @param sameIdExtAs the optional @a Message to check for having the same ID.
|
||||
* @param onlyAvailable true to include only available messages (default true), false to also include messages that
|
||||
* are currently not available (e.g. due to unresolved or false conditions).
|
||||
* @return the first available @a Message from the list.
|
||||
*/
|
||||
Message* getFirstAvailable(const vector<Message*>& messages, const Message* sameIdExtAs = nullptr,
|
||||
const bool onlyAvailable = true) {
|
||||
@@ -2566,6 +2584,14 @@ void MessageMap::findAll(const string& circuit, const string& name, const string
|
||||
}
|
||||
}
|
||||
|
||||
Message* MessageMap::getFirstAvailableFromIterator(const map<uint64_t, vector<Message*> >::const_iterator& it,
|
||||
const MasterSymbolString* sameIdExtAs, bool onlyAvailable) const {
|
||||
if (it != m_messagesByKey.end()) {
|
||||
return getFirstAvailable(it->second, sameIdExtAs, onlyAvailable);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination,
|
||||
bool withRead, bool withWrite, bool withPassive, bool onlyAvailable) const {
|
||||
if (anyDestination && master.size() >= 5 && master[4] == 0 && master[2] == 0x07 && master[3] == 0x04) {
|
||||
@@ -2576,6 +2602,7 @@ Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination,
|
||||
if (baseKey == INVALID_KEY) {
|
||||
return nullptr;
|
||||
}
|
||||
bool isWriteDest = isMaster(master[1]) || master[1] == BROADCAST;
|
||||
size_t maxIdLength = Message::getKeyLength(baseKey);
|
||||
for (size_t idLength = maxIdLength; true; idLength--) {
|
||||
uint64_t key = baseKey;
|
||||
@@ -2591,44 +2618,39 @@ Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination,
|
||||
}
|
||||
}
|
||||
}
|
||||
map<uint64_t, vector<Message*> >::const_iterator it;
|
||||
Message* message;
|
||||
if (withPassive) {
|
||||
it = m_messagesByKey.find(key);
|
||||
if (it != m_messagesByKey.end()) {
|
||||
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
|
||||
message = getFirstAvailableFromIterator(m_messagesByKey.find(key), &master, onlyAvailable);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
if ((key & ID_SOURCE_MASK) != 0) {
|
||||
key &= ~ID_SOURCE_MASK;
|
||||
if (withPassive) {
|
||||
// try again without specific source master
|
||||
message = getFirstAvailableFromIterator(m_messagesByKey.find(key), &master, onlyAvailable);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
if ((key & ID_SOURCE_MASK) != 0) {
|
||||
key &= ~ID_SOURCE_MASK;
|
||||
it = m_messagesByKey.find(key & ~ID_SOURCE_MASK); // try again without specific source master
|
||||
if (it != m_messagesByKey.end()) {
|
||||
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
key &= ~ID_SOURCE_MASK;
|
||||
}
|
||||
if (withRead) {
|
||||
it = m_messagesByKey.find(key | ID_SOURCE_ACTIVE_READ); // try again with special value for active read
|
||||
if (it != m_messagesByKey.end()) {
|
||||
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
// try again with special value for active read
|
||||
message = getFirstAvailableFromIterator(
|
||||
m_messagesByKey.find(key | (isWriteDest ? ID_SOURCE_ACTIVE_READ_MASTER : ID_SOURCE_ACTIVE_READ)),
|
||||
&master, onlyAvailable);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
if (withWrite) {
|
||||
it = m_messagesByKey.find(key | ID_SOURCE_ACTIVE_WRITE); // try again with special value for active write
|
||||
if (it != m_messagesByKey.end()) {
|
||||
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
// try again with special value for active write
|
||||
message = getFirstAvailableFromIterator(
|
||||
m_messagesByKey.find(key | (isWriteDest ? ID_SOURCE_ACTIVE_WRITE_MASTER : ID_SOURCE_ACTIVE_WRITE)),
|
||||
&master, onlyAvailable);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
if (idLength == 0) {
|
||||
@@ -2751,11 +2773,16 @@ Message* MessageMap::getNextPoll() {
|
||||
if (m_pollMessages.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
lock();
|
||||
Message* ret = m_pollMessages.top();
|
||||
m_pollMessages.pop();
|
||||
ret->m_pollCount++;
|
||||
if (ret->m_pollOrder > g_lastPollOrder) {
|
||||
g_lastPollOrder = ret->m_pollOrder;
|
||||
}
|
||||
ret->m_pollOrder += (unsigned int)ret->m_pollPriority;
|
||||
time(&(ret->m_lastPollTime));
|
||||
m_pollMessages.push(ret); // re-insert at new position
|
||||
unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Executable → Regular
+14
-3
@@ -642,8 +642,8 @@ class Message : public AttributedItem {
|
||||
/** the system time when the message content was last changed, 0 for never. */
|
||||
time_t m_lastChangeTime;
|
||||
|
||||
/** the number of times this messages was already polled for. */
|
||||
unsigned int m_pollCount;
|
||||
/** the polling order of this message (roughly number of polls * priority). */
|
||||
unsigned int m_pollOrder;
|
||||
|
||||
/** the system time when this message was last polled for, 0 for never. */
|
||||
time_t m_lastPollTime;
|
||||
@@ -1403,13 +1403,24 @@ class MessageMap : public MappedFileReader {
|
||||
* address), or 0 to ignore.
|
||||
* @param until the end time to which to add updates (exclusive, also removes messages with unset destination
|
||||
* address), or 0 to ignore.
|
||||
* @changedSince true to use the last change time for the since/until range, false to use the last seen time.
|
||||
* @param changedSince true to use the last change time for the since/until range, false to use the last seen time.
|
||||
* @param messages the @a deque to which to add the found @a Message instances.
|
||||
*/
|
||||
void findAll(const string& circuit, const string& name, const string& levels,
|
||||
bool completeMatch, bool withRead, bool withWrite, bool withPassive, bool includeEmptyLevel, bool onlyAvailable,
|
||||
time_t since, time_t until, bool changedSince, deque<Message*>* messages) const;
|
||||
|
||||
/**
|
||||
* Get the first available @a Message from the first map iterator entry.
|
||||
* @param it the map iterator with list of @a Message instances to check.
|
||||
* @param sameIdExtAs the optional @a MasterSymbolString to check for having the same ID.
|
||||
* @param onlyAvailable true to include only available messages (default true), false to also include messages that
|
||||
* are currently not available (e.g. due to unresolved or false conditions).
|
||||
* @return the first available @a Message from the first map iterator entry.
|
||||
*/
|
||||
Message* getFirstAvailableFromIterator(const map<uint64_t, vector<Message*> >::const_iterator& it,
|
||||
const MasterSymbolString* sameIdExtAs, bool onlyAvailable) const;
|
||||
|
||||
/**
|
||||
* Find the @a Message instance for the specified master data.
|
||||
* @param master the @a MasterSymbolString for identifying the @a Message.
|
||||
|
||||
@@ -228,6 +228,9 @@ int main() {
|
||||
{"x,,ttq", "23:45", "10fe0700015f", "00", ""},
|
||||
{"x,,ttq", "24:00", "10fe07000160", "00", ""},
|
||||
{"x,,ttq", "-:-", "10fe07000100", "00", ""},
|
||||
{"x,,ttq,,,,,,ttq", "23:00;05:45", "10fe0700025c17", "00", ""},
|
||||
{"x,,ttq,,,,,,bi7,,,,,,ttq", "23:00;0;05:45", "10fe0700025c17", "00", ""},
|
||||
{"x,,ttq,,,,,,bi7,,,,,,ttq", "23:00;1;05:45", "10fe070002dc17", "00", ""},
|
||||
{"x,,ttq", "", "10fe07000161", "00", "rw"},
|
||||
{"x,,ttq,2", "", "", "", "c"},
|
||||
{"x,,ttq,,,,y,,bi7", "23:45;0", "10fe0700015f", "00", ""}, // bit combination
|
||||
|
||||
Executable → Regular
+6
-2
@@ -173,7 +173,11 @@ int main() {
|
||||
{"w,,x,,,,,,b0,,BI0:1,,,,b1,,BI1:1,,,,b2,,BI2:6,,,,c0,,BI0:1,,,,c1,,BI1:1,,,,c2,,BI2:1", "1;1;1;0;0;0", "ff08b509030e0700", "00", "di" },
|
||||
{"w,,x,,,,,,b0,,BI0:1,,,,b1,,BI1:1,,,,b2,,BI2:6,,,,c0,,BI0:1,,,,c1,,BI1:1,,,,c2,,BI2:1", "1;0;0;0;0;1", "ff08b509030e0104", "00", "di" },
|
||||
{"w,,x,,,,,,b0,,BI0:1,,,,b1,,BI1:1,,,,b2,,BI2:6,,,,c0,,BI0:1,,,,c1,,BI1:1,,,,c2,,BI2:1", "0;0;1;0;1;1", "ff08b509030e0406", "00", "di" },
|
||||
{"*r,cir*cuit#level,na*me,com*ment,ff,75,b509,0d", "", "", "", "" },
|
||||
{"r,470,ccTimer.Monday,,,15,B515,0002,,,IGN:1,,,,from,,TTM", "", "", "", "M"},
|
||||
{"w,470,ccTimer.Monday,,,10,B515,0002,from,,TTM", "", "", "", "kM*"},
|
||||
{"", "19:00", "3115b515020002", "080272", "kd"},
|
||||
{"", "19:00", "3110b51503000272", "00", "kd"},
|
||||
{"*r,cir*cuit#level,na*me,com*ment,ff,75,b509,0d", "", "", "", ""},
|
||||
{"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,UCH", "r,cirCIRCUITcuit,naNAMEme,comCOMMENTment,ff,75,b509,0d0100,field,s,UCH,,,: field=42", "ff75b509030d0100", "012a", "DN"},
|
||||
{"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,UCH",
|
||||
// "\"naNAMEme\": {r,cirCIRCUITcuit,naNAMEme,comCOMMENTment,ff,75,b509,0d0100,field,s,UCH,,,: field=42"
|
||||
@@ -420,7 +424,7 @@ int main() {
|
||||
result = message->decodeLastData(false, nullptr, -1,
|
||||
(decodeVerbose?OF_NAMES|OF_UNITS|OF_COMMENTS:0)|(decodeJson?OF_NAMES|OF_JSON:0), &output);
|
||||
if (result != RESULT_OK) {
|
||||
cout << " \"" << check[2] << "\" / \"" << check[3] << "\": decode error: "
|
||||
cout << " \"" << check[2] << "\" / \"" << check[3] << "\": decode error " << (message->isWrite() ? "write: " : "read: ")
|
||||
<< getResultCode(result) << endl;
|
||||
error = true;
|
||||
continue;
|
||||
|
||||
+49
-21
@@ -27,12 +27,13 @@
|
||||
#include <sys/time.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include "lib/utils/clock.h"
|
||||
|
||||
namespace ebusd {
|
||||
|
||||
/** the name of each @a LogFacility. */
|
||||
static const char *facilityNames[] = {
|
||||
static const char *s_facilityNames[] = {
|
||||
"main",
|
||||
"network",
|
||||
"bus",
|
||||
@@ -43,7 +44,7 @@ static const char *facilityNames[] = {
|
||||
};
|
||||
|
||||
/** the name of each @a LogLevel. */
|
||||
static const char* levelNames[] = {
|
||||
static const char* s_levelNames[] = {
|
||||
"none",
|
||||
"error",
|
||||
"notice",
|
||||
@@ -52,19 +53,32 @@ static const char* levelNames[] = {
|
||||
nullptr
|
||||
};
|
||||
|
||||
/** the syslog level of each @a LogLevel. */
|
||||
static const int s_syslogLevels[] = {
|
||||
LOG_INFO,
|
||||
LOG_ERR,
|
||||
LOG_NOTICE,
|
||||
LOG_INFO,
|
||||
LOG_DEBUG,
|
||||
0
|
||||
};
|
||||
|
||||
/** the current log level by log facility. */
|
||||
static LogLevel s_facilityLogLevel[] = { ll_notice, ll_notice, ll_notice, ll_notice, ll_notice, };
|
||||
|
||||
/** the current log FILE. */
|
||||
/** the current log FILE, or nullptr if closed or syslog is used. */
|
||||
static FILE* s_logFile = stdout;
|
||||
|
||||
/** whether to log to syslog. */
|
||||
static bool s_useSyslog = false;
|
||||
|
||||
LogFacility parseLogFacility(const char* facility) {
|
||||
if (!facility) {
|
||||
return lf_COUNT;
|
||||
}
|
||||
char *input = strdup(facility);
|
||||
char *opt = reinterpret_cast<char*>(input), *value = nullptr;
|
||||
int val = getsubopt(&opt, (char *const *)facilityNames, &value);
|
||||
int val = getsubopt(&opt, (char *const *)s_facilityNames, &value);
|
||||
if (val < 0 || val >= lf_COUNT || value || *opt) {
|
||||
free(input);
|
||||
return lf_COUNT;
|
||||
@@ -78,7 +92,7 @@ int parseLogFacilities(const char* facilities) {
|
||||
char *opt = reinterpret_cast<char*>(input), *value = nullptr;
|
||||
int newFacilites = 0;
|
||||
while (*opt) {
|
||||
int val = getsubopt(&opt, (char *const *)facilityNames, &value);
|
||||
int val = getsubopt(&opt, (char *const *)s_facilityNames, &value);
|
||||
if (val < 0 || val > lf_COUNT || value) {
|
||||
free(input);
|
||||
return -1;
|
||||
@@ -99,7 +113,7 @@ LogLevel parseLogLevel(const char* level) {
|
||||
}
|
||||
char *input = strdup(level);
|
||||
char *opt = reinterpret_cast<char*>(input), *value = nullptr;
|
||||
int val = getsubopt(&opt, (char *const *)levelNames, &value);
|
||||
int val = getsubopt(&opt, (char *const *)s_levelNames, &value);
|
||||
if (val < 0 || val >= ll_COUNT || value || *opt) {
|
||||
free(input);
|
||||
return ll_COUNT;
|
||||
@@ -109,11 +123,11 @@ LogLevel parseLogLevel(const char* level) {
|
||||
}
|
||||
|
||||
const char* getLogFacilityStr(LogFacility facility) {
|
||||
return facilityNames[facility];
|
||||
return s_facilityNames[facility];
|
||||
}
|
||||
|
||||
const char* getLogLevelStr(LogLevel level) {
|
||||
return levelNames[level];
|
||||
return s_levelNames[level];
|
||||
}
|
||||
|
||||
bool setFacilitiesLogLevel(int facilities, LogLevel level) {
|
||||
@@ -132,6 +146,12 @@ LogLevel getFacilityLogLevel(LogFacility facility) {
|
||||
}
|
||||
|
||||
bool setLogFile(const char* filename) {
|
||||
if (filename[0] == 0) {
|
||||
closeLogFile();
|
||||
openlog("ebusd", LOG_NDELAY|LOG_PID, LOG_USER);
|
||||
s_useSyslog = true;
|
||||
return true;
|
||||
}
|
||||
FILE* newFile = fopen(filename, "a");
|
||||
if (newFile == nullptr) {
|
||||
return false;
|
||||
@@ -148,27 +168,35 @@ void closeLogFile() {
|
||||
}
|
||||
s_logFile = nullptr;
|
||||
}
|
||||
if (s_useSyslog) {
|
||||
closelog();
|
||||
s_useSyslog = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool needsLog(const LogFacility facility, const LogLevel level) {
|
||||
return s_facilityLogLevel[facility] >= level;
|
||||
}
|
||||
|
||||
void logWrite(const char* facility, const char* level, const char* message, va_list ap) {
|
||||
if (s_logFile == nullptr) {
|
||||
void logWrite(const char* facility, const LogLevel level, const char* message, va_list ap) {
|
||||
if (s_logFile == nullptr && !s_useSyslog) {
|
||||
return;
|
||||
}
|
||||
struct timespec ts;
|
||||
struct tm td;
|
||||
clockGettime(&ts);
|
||||
localtime_r(&ts.tv_sec, &td);
|
||||
char* buf;
|
||||
if (vasprintf(&buf, message, ap) >= 0 && buf) {
|
||||
fprintf(s_logFile, "%04d-%02d-%02d %02d:%02d:%02d.%03ld [%s %s] %s\n",
|
||||
td.tm_year+1900, td.tm_mon+1, td.tm_mday,
|
||||
td.tm_hour, td.tm_min, td.tm_sec, ts.tv_nsec/1000000,
|
||||
facility, level, buf);
|
||||
fflush(s_logFile);
|
||||
if (s_useSyslog) {
|
||||
syslog(s_syslogLevels[level], "[%s %s] %s", facility, s_levelNames[level], buf);
|
||||
} else {
|
||||
struct timespec ts;
|
||||
struct tm td;
|
||||
clockGettime(&ts);
|
||||
localtime_r(&ts.tv_sec, &td);
|
||||
fprintf(s_logFile, "%04d-%02d-%02d %02d:%02d:%02d.%03ld [%s %s] %s\n",
|
||||
td.tm_year+1900, td.tm_mon+1, td.tm_mday,
|
||||
td.tm_hour, td.tm_min, td.tm_sec, ts.tv_nsec/1000000,
|
||||
facility, s_levelNames[level], buf);
|
||||
fflush(s_logFile);
|
||||
}
|
||||
}
|
||||
if (buf) {
|
||||
free(buf);
|
||||
@@ -178,14 +206,14 @@ void logWrite(const char* facility, const char* level, const char* message, va_l
|
||||
void logWrite(const LogFacility facility, const LogLevel level, const char* message, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
logWrite(facilityNames[facility], levelNames[level], message, ap);
|
||||
logWrite(s_facilityNames[facility], level, message, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void logWrite(const char* facility, const LogLevel level, const char* message, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
logWrite(facility, levelNames[level], message, ap);
|
||||
logWrite(facility, level, message, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ LogLevel getFacilityLogLevel(LogFacility facility);
|
||||
|
||||
/**
|
||||
* Set the log file to use.
|
||||
* @param filename the name of the log file to use.
|
||||
* @param filename the name of the log file to use, or the empty string for syslog.
|
||||
* @return true on success, false on error.
|
||||
*/
|
||||
bool setLogFile(const char* filename);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
Reference in New Issue
Block a user